Split apart the frontend Editor type into SubscriptionsRouter and EditorHandle (#3923)

* Remove the unused Editor.raw/wasmMemory/wasmImport

* Split out Editor.subscriptions

* Replace editor.handle.* with editor.* (1 of 2)

* Replace editor.handle.* with editor.* (2 of 2)

* Replace Editor typedef with EditorHandle import

* Pluralize subscription-router and rename subscriptionsRef->subscriptionsRouter and editorRef->editorHandle

* Remove editor.ts

* Update the readme

* Fix demo art loading bug
This commit is contained in:
Keavon Chambers
2026-03-20 23:34:13 -07:00
committed by GitHub
parent 64fd12a1a0
commit ed7987c881
40 changed files with 549 additions and 584 deletions

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount, onDestroy, setContext } from "svelte";
import type { Editor } from "@graphite/editor";
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
import { createClipboardManager, destroyClipboardManager } from "@graphite/managers/clipboard";
import { createFontsManager, destroyFontsManager } from "@graphite/managers/fonts";
import { createHyperlinkManager, destroyHyperlinkManager } from "@graphite/managers/hyperlink";
@@ -16,39 +16,42 @@
import { createNodeGraphStore, destroyNodeGraphStore } from "@graphite/stores/node-graph";
import { createPortfolioStore, destroyPortfolioStore } from "@graphite/stores/portfolio";
import { createTooltipStore, destroyTooltipStore } from "@graphite/stores/tooltip";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import MainWindow from "@graphite/components/window/MainWindow.svelte";
// Graphite Wasm editor
export let editor: Editor;
// Graphite Wasm editor and subscriptions router
export let subscriptions: SubscriptionsRouter;
export let editor: EditorHandle;
setContext("subscriptions", subscriptions);
setContext("editor", editor);
const stores = {
dialog: createDialogStore(editor),
tooltip: createTooltipStore(editor),
document: createDocumentStore(editor),
fullscreen: createFullscreenStore(editor),
nodeGraph: createNodeGraphStore(editor),
portfolio: createPortfolioStore(editor),
appWindow: createAppWindowStore(editor),
dialog: createDialogStore(subscriptions, editor),
tooltip: createTooltipStore(subscriptions),
document: createDocumentStore(subscriptions),
fullscreen: createFullscreenStore(subscriptions),
nodeGraph: createNodeGraphStore(subscriptions),
portfolio: createPortfolioStore(subscriptions, editor),
appWindow: createAppWindowStore(subscriptions),
};
Object.entries(stores).forEach(([key, store]) => setContext(key, store));
onMount(() => {
createClipboardManager(editor);
createHyperlinkManager(editor);
createLocalizationManager(editor);
createPanicManager(editor);
createPersistenceManager(editor, stores.portfolio);
createFontsManager(editor);
createInputManager(editor, stores.dialog, stores.portfolio, stores.document);
createClipboardManager(subscriptions, editor);
createHyperlinkManager(subscriptions);
createLocalizationManager(subscriptions, editor);
createPanicManager(subscriptions);
createPersistenceManager(subscriptions, editor, stores.portfolio);
createFontsManager(subscriptions, editor);
createInputManager(subscriptions, editor, stores.dialog, stores.portfolio, stores.document);
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready.
// The backend handles idempotency, so this is safe to call again during HMR re-mounts.
editor.handle.initAfterFrontendReady();
editor.initAfterFrontendReady();
// Re-send all UI layouts from Rust so the frontend has them after an HMR re-mount
editor.handle.resendAllLayouts();
editor.resendAllLayouts();
});
onDestroy(() => {

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { getContext } from "svelte";
import type { LabeledShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, LabeledShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
import type { TooltipStore } from "@graphite/stores/tooltip";
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
@@ -11,7 +10,7 @@
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const tooltip = getContext<TooltipStore>("tooltip");
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
let self: FloatingMenu | undefined;
@@ -32,7 +31,7 @@
// TODO: Once all TODOs are replaced with real text, remove this function
function filterTodo(text: string | undefined): string | undefined {
if (text?.trim().toUpperCase() === "TODO" && !editor.handle.inDevelopmentMode()) return "";
if (text?.trim().toUpperCase() === "TODO" && !editor.inDevelopmentMode()) return "";
return text;
}

View File

@@ -2,25 +2,25 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
let dataPanelLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("DataPanel", (data) => {
subscriptions.subscribeLayoutUpdate("DataPanel", (data) => {
patchLayout(dataPanelLayout, data);
dataPanelLayout = dataPanelLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("DataPanel");
subscriptions.unsubscribeLayoutUpdate("DataPanel");
});
</script>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { getContext, onMount, onDestroy, tick } from "svelte";
import type { Color, MenuDirection, MouseCursorIcon } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { Color, EditorHandle, MenuDirection, MouseCursorIcon } from "@graphite/../wasm/pkg/graphite_wasm";
import type { AppWindowStore } from "@graphite/stores/app-window";
import type { DocumentStore } from "@graphite/stores/document";
import type { MessageBody } from "@graphite/subscription-router";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import type { MessageBody } from "/src/subscriptions-router";
import { fillChoiceColor, createColor } from "@graphite/utility-functions/colors";
import { pasteFile } from "@graphite/utility-functions/files";
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
@@ -26,7 +26,8 @@
let viewport: HTMLDivElement | undefined;
let gradientStopPicker: ColorPicker | undefined;
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
const editor = getContext<EditorHandle>("editor");
const appWindow = getContext<AppWindowStore>("appWindow");
const document = getContext<DocumentStore>("document");
@@ -142,13 +143,13 @@
function panCanvasX(newValue: number) {
const delta = newValue - scrollbarPos.x;
scrollbarPos.x = newValue;
editor.handle.panCanvas(-delta * scrollbarMultiplier.x, 0);
editor.panCanvas(-delta * scrollbarMultiplier.x, 0);
}
function panCanvasY(newValue: number) {
const delta = newValue - scrollbarPos.y;
scrollbarPos.y = newValue;
editor.handle.panCanvas(0, -delta * scrollbarMultiplier.y);
editor.panCanvas(0, -delta * scrollbarMultiplier.y);
}
function canvasPointerDown(e: PointerEvent) {
@@ -342,7 +343,7 @@
export function triggerTextCommit() {
if (!textInput) return;
const textCleaned = textInputCleanup(textInput.innerText);
editor.handle.onChangeText(textCleaned, false);
editor.onChangeText(textCleaned, false);
}
export async function displayEditableTextbox(data: MessageBody<"DisplayEditableTextbox">) {
@@ -372,7 +373,7 @@
textInput.oninput = () => {
if (!textInput) return;
editor.handle.updateBounds(textInputCleanup(textInput.innerText));
editor.updateBounds(textInputCleanup(textInput.innerText));
};
textInputMatrix = data.transform;
@@ -454,12 +455,12 @@
updatePixelRatio();
// Update rendered SVGs
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentArtwork", async (data) => {
subscriptions.subscribeFrontendMessage("UpdateDocumentArtwork", async (data) => {
await tick();
updateDocumentArtwork(data.svg);
});
editor.subscriptions.subscribeFrontendMessage("UpdateEyedropperSamplingState", async (data) => {
subscriptions.subscribeFrontendMessage("UpdateEyedropperSamplingState", async (data) => {
await tick();
const { image, mousePosition, primaryColor, secondaryColor, setColorChoice } = data;
@@ -467,25 +468,25 @@
const rgb = await updateEyedropperSamplingState(imageData, mousePosition, primaryColor, secondaryColor);
if (setColorChoice && rgb) {
if (setColorChoice === "Primary") editor.handle.updatePrimaryColor(...rgb, 1);
if (setColorChoice === "Secondary") editor.handle.updateSecondaryColor(...rgb, 1);
if (setColorChoice === "Primary") editor.updatePrimaryColor(...rgb, 1);
if (setColorChoice === "Secondary") editor.updateSecondaryColor(...rgb, 1);
}
});
// Gradient stop color picker
editor.subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
gradientStopPickerColor = data.color;
gradientStopPickerPosition = { x: data.position[0], y: data.position[1] };
});
// Update scrollbars and rulers
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentScrollbars", async (data) => {
subscriptions.subscribeFrontendMessage("UpdateDocumentScrollbars", async (data) => {
await tick();
const { position, size, multiplier } = data;
updateDocumentScrollbars(position, size, multiplier);
});
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => {
subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => {
await tick();
const { origin, spacing, interval, visible } = data;
@@ -493,24 +494,24 @@
});
// Update mouse cursor icon
editor.subscriptions.subscribeFrontendMessage("UpdateMouseCursor", async (data) => {
subscriptions.subscribeFrontendMessage("UpdateMouseCursor", async (data) => {
await tick();
updateMouseCursor(data.cursor);
});
// Text entry
editor.subscriptions.subscribeFrontendMessage("TriggerTextCommit", async () => {
subscriptions.subscribeFrontendMessage("TriggerTextCommit", async () => {
await tick();
triggerTextCommit();
});
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextbox", async (data) => {
subscriptions.subscribeFrontendMessage("DisplayEditableTextbox", async (data) => {
await tick();
displayEditableTextbox(data);
});
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
await tick();
if (textInput && data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {
@@ -521,10 +522,10 @@
textInput.style.fontFamily = "text-font";
}
});
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxTransform", async (data) => {
subscriptions.subscribeFrontendMessage("DisplayEditableTextboxTransform", async (data) => {
textInputMatrix = data.transform;
});
editor.subscriptions.subscribeFrontendMessage("DisplayRemoveEditableTextbox", async () => {
subscriptions.subscribeFrontendMessage("DisplayRemoveEditableTextbox", async () => {
await tick();
displayRemoveEditableTextbox();
@@ -547,17 +548,17 @@
removeUpdatePixelRatio?.();
addedFontFaces.forEach((face) => window.document.fonts.delete(face));
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentArtwork");
editor.subscriptions.unsubscribeFrontendMessage("UpdateEyedropperSamplingState");
editor.subscriptions.unsubscribeFrontendMessage("UpdateGradientStopColorPickerPosition");
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentScrollbars");
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentRulers");
editor.subscriptions.unsubscribeFrontendMessage("UpdateMouseCursor");
editor.subscriptions.unsubscribeFrontendMessage("TriggerTextCommit");
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextbox");
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxUpdateFontData");
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxTransform");
editor.subscriptions.unsubscribeFrontendMessage("DisplayRemoveEditableTextbox");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentArtwork");
subscriptions.unsubscribeFrontendMessage("UpdateEyedropperSamplingState");
subscriptions.unsubscribeFrontendMessage("UpdateGradientStopColorPickerPosition");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentScrollbars");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentRulers");
subscriptions.unsubscribeFrontendMessage("UpdateMouseCursor");
subscriptions.unsubscribeFrontendMessage("TriggerTextCommit");
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextbox");
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxUpdateFontData");
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxTransform");
subscriptions.unsubscribeFrontendMessage("DisplayRemoveEditableTextbox");
});
</script>
@@ -628,7 +629,7 @@
open={Boolean(gradientStopPickerPosition && gradientStopPickerColor)}
on:open={({ detail }) => {
if (!detail) {
editor.handle.closeGradientStopColorPicker();
editor.closeGradientStopColorPicker();
gradientStopPickerPosition = undefined;
gradientStopPickerColor = undefined;
}
@@ -636,10 +637,10 @@
colorOrGradient={{ Solid: gradientStopPickerColor || createColor(0, 0, 0, 1) }}
on:colorOrGradient={({ detail }) => {
const color = fillChoiceColor(detail);
if (color) editor.handle.updateGradientStopColor(color.red, color.green, color.blue, color.alpha);
if (color) editor.updateGradientStopColor(color.red, color.green, color.blue, color.alpha);
}}
on:startHistoryTransaction={() => editor.handle.startGradientStopColorTransaction()}
on:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}
on:startHistoryTransaction={() => editor.startGradientStopColorTransaction()}
on:commitHistoryTransaction={() => editor.commitGradientStopColorTransaction()}
bind:this={gradientStopPicker}
/>
</div>
@@ -682,10 +683,10 @@
direction="Vertical"
thumbLength={scrollbarSize.y}
thumbPosition={scrollbarPos.y}
on:trackShift={({ detail }) => editor.handle.panCanvasByFraction(0, detail)}
on:trackShift={({ detail }) => editor.panCanvasByFraction(0, detail)}
on:thumbPosition={({ detail }) => panCanvasY(detail)}
on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(false)}
on:thumbDragAbort={() => editor.handle.panCanvasAbort(false)}
on:thumbDragStart={() => editor.panCanvasAbortPrepare(false)}
on:thumbDragAbort={() => editor.panCanvasAbort(false)}
/>
</LayoutCol>
</LayoutRow>
@@ -694,10 +695,10 @@
direction="Horizontal"
thumbLength={scrollbarSize.x}
thumbPosition={scrollbarPos.x}
on:trackShift={({ detail }) => editor.handle.panCanvasByFraction(detail, 0)}
on:trackShift={({ detail }) => editor.panCanvasByFraction(detail, 0)}
on:thumbPosition={({ detail }) => panCanvasX(detail)}
on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)}
on:thumbDragAbort={() => editor.handle.panCanvasAbort(true)}
on:thumbDragStart={() => editor.panCanvasAbortPrepare(true)}
on:thumbDragAbort={() => editor.panCanvasAbort(true)}
/>
</LayoutRow>
</LayoutCol>

View File

@@ -2,10 +2,10 @@
import { getContext, onMount, onDestroy, tick } from "svelte";
import { SvelteMap } from "svelte/reactivity";
import type { LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { NodeGraphStore } from "@graphite/stores/node-graph";
import type { TooltipStore } from "@graphite/stores/tooltip";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { pasteFile } from "@graphite/utility-functions/files";
import { operatingSystem } from "@graphite/utility-functions/platform";
import { patchLayout } from "@graphite/utility-functions/widgets";
@@ -41,7 +41,8 @@
startY: number;
};
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
const editor = getContext<EditorHandle>("editor");
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
const tooltip = getContext<TooltipStore>("tooltip");
@@ -69,26 +70,26 @@
let layersPanelBottomBarLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", (data) => {
subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", (data) => {
patchLayout(layersPanelControlBarLeftLayout, data);
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
});
editor.subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", (data) => {
subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", (data) => {
patchLayout(layersPanelControlBarRightLayout, data);
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
});
editor.subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", (data) => {
subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", (data) => {
patchLayout(layersPanelBottomBarLayout, data);
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
});
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
rebuildLayerHierarchy(data.layerStructure);
});
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
const targetLayer = data.data;
const targetId = targetLayer.id;
@@ -107,11 +108,11 @@
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
removeEventListener("pointerup", draggingPointerUp);
removeEventListener("pointermove", draggingPointerMove);
@@ -125,17 +126,17 @@
});
function toggleNodeVisibilityLayerPanel(id: bigint) {
editor.handle.toggleNodeVisibilityLayerPanel(id);
editor.toggleNodeVisibilityLayerPanel(id);
}
function toggleLayerLock(id: bigint) {
editor.handle.toggleLayerLock(id);
editor.toggleLayerLock(id);
}
function handleExpandArrowClickWithModifiers(e: MouseEvent, id: bigint) {
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
const collapseRecursive = e.altKey || accel;
editor.handle.toggleLayerExpansion(id, collapseRecursive);
editor.toggleLayerExpansion(id, collapseRecursive);
e.stopPropagation();
}
@@ -162,7 +163,7 @@
layers = layers;
const name = (e.target instanceof HTMLInputElement && e.target.value) || "";
editor.handle.setLayerName(listing.entry.id, name);
editor.setLayerName(listing.entry.id, name);
listing.entry.alias = name;
}
@@ -200,7 +201,7 @@
}
function clipLayer(listing: LayerListingInfo) {
editor.handle.clipLayer(listing.entry.id);
editor.clipLayer(listing.entry.id);
}
function clippingKeyPress(e: KeyboardEvent) {
@@ -247,7 +248,7 @@
// Don't select while we are entering text to rename the layer
if (listing.editingName) return;
editor.handle.selectLayer(listing.entry.id, accel, shift);
editor.selectLayer(listing.entry.id, accel, shift);
}
async function deselectAllLayers() {
@@ -256,7 +257,7 @@
return;
}
editor.handle.deselectAllLayers();
editor.deselectAllLayers();
}
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
@@ -389,7 +390,7 @@
// Commit the move
select?.();
editor.handle.moveLayerInTree(insertParentId, insertIndex);
editor.moveLayerInTree(insertParentId, insertIndex);
// Prevent the subsequent click event from processing
justFinishedDrag = true;
@@ -445,7 +446,7 @@
const inputElement = document.activeElement;
if (inputElement instanceof HTMLInputElement) {
const name = inputElement.value || "";
editor.handle.setLayerName(currentListing.entry.id, name);
editor.setLayerName(currentListing.entry.id, name);
currentListing.entry.alias = name;
}

View File

@@ -2,25 +2,25 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
let propertiesPanelLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("PropertiesPanel", (data) => {
subscriptions.subscribeLayoutUpdate("PropertiesPanel", (data) => {
patchLayout(propertiesPanelLayout, data);
propertiesPanelLayout = propertiesPanelLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
});
</script>

View File

@@ -2,8 +2,8 @@
import { getContext, onMount, onDestroy } from "svelte";
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { pasteFile } from "@graphite/utility-functions/files";
import { patchLayout } from "@graphite/utility-functions/widgets";
@@ -13,19 +13,20 @@
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
const editor = getContext<EditorHandle>("editor");
let welcomePanelButtonsLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", (data) => {
subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", (data) => {
patchLayout(welcomePanelButtonsLayout, data);
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
});
function dropFile(e: DragEvent) {

View File

@@ -3,8 +3,7 @@
import { cubicInOut } from "svelte/easing";
import { fade } from "svelte/transition";
import type { FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/../wasm/pkg/graphite_wasm";
import type { DocumentStore } from "@graphite/stores/document";
import { closeContextMenu } from "@graphite/stores/node-graph";
import type { NodeGraphStore } from "@graphite/stores/node-graph";
@@ -20,7 +19,7 @@
const GRID_SIZE = 24;
const FADE_TRANSITION = { duration: 200, easing: cubicInOut };
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
const documentState = getContext<DocumentStore>("document");
@@ -83,7 +82,7 @@
if (editingNameImportIndex !== undefined) {
if (!(event.target instanceof HTMLInputElement)) return;
let text = event.target.value;
editor.handle.setImportName(editingNameImportIndex, text);
editor.setImportName(editingNameImportIndex, text);
editingNameImportIndex = undefined;
}
}
@@ -92,7 +91,7 @@
if (editingNameExportIndex !== undefined) {
if (!(event.target instanceof HTMLInputElement)) return;
let text = event.target.value;
editor.handle.setExportName(editingNameExportIndex, text);
editor.setExportName(editingNameExportIndex, text);
editingNameExportIndex = undefined;
}
}
@@ -111,7 +110,7 @@
function createNode(identifier: string) {
if ($nodeGraph.contextMenuInformation === undefined) return;
editor.handle.createNode(identifier, $nodeGraph.contextMenuInformation.contextMenuCoordinates[0], $nodeGraph.contextMenuInformation.contextMenuCoordinates[1]);
editor.createNode(identifier, $nodeGraph.contextMenuInformation.contextMenuCoordinates[0], $nodeGraph.contextMenuInformation.contextMenuCoordinates[1]);
}
function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, exposedSecondaryInputs: number, primaryOutputExists: boolean, exposedSecondaryOutputs: number): string {
@@ -174,11 +173,11 @@
}
function outputConnectedToText(output: FrontendGraphOutput): string {
return editor.handle.inDevelopmentMode() ? output.connectedTo.join("\n") : "";
return editor.inDevelopmentMode() ? output.connectedTo.join("\n") : "";
}
function inputConnectedToText(input: FrontendGraphInput): string {
return editor.handle.inDevelopmentMode() ? input.connectedTo : "";
return editor.inDevelopmentMode() ? input.connectedTo : "";
}
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
@@ -220,7 +219,7 @@
<TextButton
label="Merge Selected Nodes"
action={() => {
editor.handle.mergeSelectedNodes();
editor.mergeSelectedNodes();
closeContextMenu();
}}
flush={true}
@@ -230,7 +229,7 @@
label={currentlyIsNode ? "Display as Layer" : "Display as Node"}
action={() => {
if ($nodeGraph.contextMenuInformation?.contextMenuData.type === "ModifyNode") {
editor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, currentlyIsNode);
editor.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, currentlyIsNode);
}
closeContextMenu();
}}
@@ -244,9 +243,9 @@
label={allLocked ? "Unlock" : "Lock"}
action={() => {
if ($nodeGraph.selected.includes(nodeId)) {
editor.handle.toggleSelectedLocked();
editor.toggleSelectedLocked();
} else {
editor.handle.toggleLayerLock(nodeId);
editor.toggleLayerLock(nodeId);
}
closeContextMenu();
}}
@@ -383,7 +382,7 @@
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24}
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
>
<IconButton size={24} icon="Add" action={() => editor.handle.addPrimaryImport()} />
<IconButton size={24} icon="Add" action={() => editor.addPrimaryImport()} />
</div>
{/if}
{/each}
@@ -454,7 +453,7 @@
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24}
>
<IconButton size={24} icon="Add" action={() => editor.handle.addPrimaryExport()} />
<IconButton size={24} icon="Add" action={() => editor.addPrimaryExport()} />
</div>
{/if}
{/each}
@@ -465,14 +464,14 @@
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.imports.length}
>
<IconButton size={24} icon="Add" action={() => editor.handle.addSecondaryImport()} />
<IconButton size={24} icon="Add" action={() => editor.addSecondaryImport()} />
</div>
<div
class="plus"
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.exports.length}
>
<IconButton size={24} icon="Add" action={() => editor.handle.addSecondaryExport()} />
<IconButton size={24} icon="Add" action={() => editor.addSecondaryExport()} />
</div>
{/if}
@@ -522,7 +521,7 @@
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
data-tooltip-label={nodeNameTooltipLabel(node)}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : ""}
${(description || "").trim()}${editor.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : ""}
`.trim()}
data-node={node.id}
>
@@ -685,7 +684,7 @@
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
data-tooltip-label={nodeNameTooltipLabel(node)}
data-tooltip-description={`
${(description || "").trim()}${editor.handle.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : ""}
${(description || "").trim()}${editor.inDevelopmentMode() ? `\n\nID: ${node.id}. Position: (${node.position[0]}, ${node.position[1]}).` : ""}
`.trim()}
data-node={node.id}
>

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { getContext } from "svelte";
import type { LayoutTarget, WidgetSection as WidgetSectionData } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, LayoutTarget, WidgetSection as WidgetSectionData } from "@graphite/../wasm/pkg/graphite_wasm";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
@@ -18,7 +17,7 @@
let expanded = true;
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
</script>
<!-- TODO: Implement collapsable sections with properties system -->
@@ -31,7 +30,7 @@
tooltipDescription={widgetData.pinned ? "Unpin this node so it's no longer shown here when nothing is selected." : "Pin this node so it's shown here when nothing is selected."}
size={24}
action={(e) => {
editor.handle.setNodePinned(widgetData.id, !widgetData.pinned);
editor.setNodePinned(widgetData.id, !widgetData.pinned);
e?.stopPropagation();
}}
class="show-only-on-hover"
@@ -41,7 +40,7 @@
tooltipDescription="Delete this node from the layer chain."
size={24}
action={(e) => {
editor.handle.deleteNode(widgetData.id);
editor.deleteNode(widgetData.id);
e?.stopPropagation();
}}
class="show-only-on-hover"
@@ -52,7 +51,7 @@
tooltipDescription={widgetData.visible ? "Hide this node." : "Show this node."}
size={24}
action={(e) => {
editor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);
editor.toggleNodeVisibilityLayerPanel(widgetData.id);
e?.stopPropagation();
}}
class={widgetData.visible ? "show-only-on-hover" : ""}

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { getContext } from "svelte";
import type { LayoutTarget, Widget, WidgetInstance } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, LayoutTarget, Widget, WidgetInstance } from "@graphite/../wasm/pkg/graphite_wasm";
import { parseFillChoice } from "@graphite/utility-functions/colors";
import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte";
@@ -35,7 +34,7 @@
// A Widget tagged enum unwrapped into a correlated [kind, props] tuple
type UnwrappedWidget = { [K in WidgetKind]: [kind: K, props: WidgetProps<K>] }[WidgetKind];
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
export let widgets: WidgetInstance[];
export let direction: "row" | "column";
@@ -52,15 +51,15 @@
.join(" ");
function widgetValueCommit(widgetIndex: number, value: unknown) {
editor.handle.widgetValueCommit(layoutTarget, widgets[widgetIndex].widgetId, value);
editor.widgetValueCommit(layoutTarget, widgets[widgetIndex].widgetId, value);
}
function widgetValueUpdate(widgetIndex: number, value: unknown, resendWidget: boolean) {
editor.handle.widgetValueUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
editor.widgetValueUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
}
function widgetValueCommitAndUpdate(widgetIndex: number, value: unknown, resendWidget: boolean) {
editor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
editor.widgetValueCommitAndUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
}
// Extracts the kind and props from a Widget tagged enum, validated against the widget registry.

View File

@@ -2,8 +2,7 @@
import { createEventDispatcher, onMount, onDestroy, getContext } from "svelte";
import { evaluateMathExpression, isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
import type { NumberInputMode, NumberInputIncrementBehavior, ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { ActionShortcut, EditorHandle, NumberInputIncrementBehavior, NumberInputMode } from "@graphite/../wasm/pkg/graphite_wasm";
import { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS } from "@graphite/managers/input";
import { browserVersion } from "@graphite/utility-functions/platform";
@@ -17,7 +16,7 @@
const dispatch = createEventDispatcher<{ value: number | undefined; startHistoryTransaction: undefined }>();
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
// Content
/// When `value` is not provided (i.e. it's `undefined`), a dash is displayed.
@@ -408,7 +407,7 @@
// Enter dragging state
if (usePointerLock) target.requestPointerLock();
if (isPlatformNative()) {
editor.handle.appWindowPointerLock();
editor.appWindowPointerLock();
}
initialValueBeforeDragging = value;
cumulativeDragDelta = 0;

View File

@@ -1,15 +1,14 @@
<script lang="ts">
import { getContext } from "svelte";
import type { Color } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { Color, EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
import { fillChoiceColor, colorToRgbaCSS } from "@graphite/utility-functions/colors";
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
// Content
export let primary: Color;
@@ -29,11 +28,11 @@
}
function primaryColorChanged(color: Color) {
editor.handle.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
editor.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
}
function secondaryColorChanged(color: Color) {
editor.handle.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
editor.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
}
</script>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, tick } from "svelte";
import type { Editor } from "@graphite/editor";
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -25,7 +25,7 @@
const BUTTON_LEFT = 0;
const BUTTON_MIDDLE = 1;
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
export let tabMinWidths = false;
export let tabCloseButtons = false;
@@ -56,7 +56,7 @@
}
</script>
<LayoutCol on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
<LayoutCol on:pointerdown={() => panelType && editor.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
<LayoutRow class="tab-group" scrollableX={true} on:click={onEmptySpaceAction} on:auxclick={onEmptySpaceAction}>
{#each tabLabels as tabLabel, tabIndex}

View File

@@ -2,32 +2,33 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
let statusBarHintsLayout: Layout = [];
let statusBarInfoLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("StatusBarHints", (data) => {
subscriptions.subscribeLayoutUpdate("StatusBarHints", (data) => {
patchLayout(statusBarHintsLayout, data);
statusBarHintsLayout = statusBarHintsLayout;
});
editor.subscriptions.subscribeLayoutUpdate("StatusBarInfo", (data) => {
subscriptions.subscribeLayoutUpdate("StatusBarInfo", (data) => {
patchLayout(statusBarInfoLayout, data);
statusBarInfoLayout = statusBarInfoLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarHints");
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarInfo");
subscriptions.unsubscribeLayoutUpdate("StatusBarHints");
subscriptions.unsubscribeLayoutUpdate("StatusBarInfo");
});
</script>

View File

@@ -2,12 +2,12 @@
import { getContext, onMount, onDestroy } from "svelte";
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { AppWindowStore } from "@graphite/stores/app-window";
import { enterFullscreen, exitFullscreen } from "@graphite/stores/fullscreen";
import type { FullscreenStore } from "@graphite/stores/fullscreen";
import type { TooltipStore } from "@graphite/stores/tooltip";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -16,8 +16,9 @@
const keyboardLockApiSupported = navigator.keyboard !== undefined && "lock" in navigator.keyboard;
const editor = getContext<EditorHandle>("editor");
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
const appWindow = getContext<AppWindowStore>("appWindow");
const editor = getContext<Editor>("editor");
const fullscreen = getContext<FullscreenStore>("fullscreen");
const tooltip = getContext<TooltipStore>("tooltip");
@@ -29,14 +30,14 @@
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
onMount(() => {
editor.subscriptions.subscribeLayoutUpdate("MenuBar", (data) => {
subscriptions.subscribeLayoutUpdate("MenuBar", (data) => {
patchLayout(menuBarLayout, data);
menuBarLayout = menuBarLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("MenuBar");
subscriptions.unsubscribeLayoutUpdate("MenuBar");
});
</script>
@@ -48,7 +49,7 @@
{/if}
</LayoutRow>
<!-- Window frame -->
<LayoutRow class="window-frame" on:mousedown={() => !isFullscreen && editor.handle.appWindowDrag()} on:dblclick={() => !isFullscreen && editor.handle.appWindowMaximize()} />
<LayoutRow class="window-frame" on:mousedown={() => !isFullscreen && editor.appWindowDrag()} on:dblclick={() => !isFullscreen && editor.appWindowMaximize()} />
<!-- Window buttons -->
<LayoutRow class="window-buttons" classes={{ fullscreen: showFullscreenButton, windows: $appWindow.platform === "Windows", linux: $appWindow.platform === "Linux" }}>
{#if $appWindow.platform !== "Mac"}
@@ -60,20 +61,20 @@
: undefined}
tooltipShortcut={$tooltip.fullscreenShortcut}
on:click={() => {
if (isPlatformNative()) editor.handle.appWindowFullscreen();
if (isPlatformNative()) editor.appWindowFullscreen();
else ($fullscreen.windowFullscreen ? exitFullscreen : enterFullscreen)();
}}
>
<IconLabel icon={isFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
</LayoutRow>
{:else}
<LayoutRow tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
<LayoutRow tooltipLabel="Minimize" on:click={() => editor.appWindowMinimize()}>
<IconLabel icon="WindowButtonWinMinimize" />
</LayoutRow>
<LayoutRow tooltipLabel={$appWindow.maximized ? ($appWindow.platform === "Windows" ? "Restore Down" : "Unmaximize") : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
<LayoutRow tooltipLabel={$appWindow.maximized ? ($appWindow.platform === "Windows" ? "Restore Down" : "Unmaximize") : "Maximize"} on:click={() => editor.appWindowMaximize()}>
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
</LayoutRow>
<LayoutRow tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
<LayoutRow tooltipLabel="Close" on:click={() => editor.appWindowClose()}>
<IconLabel icon="WindowButtonWinClose" />
</LayoutRow>
{/if}

View File

@@ -1,8 +1,7 @@
<script lang="ts">
import { getContext, onDestroy } from "svelte";
import type { OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { EditorHandle, OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
import type { PortfolioStore } from "@graphite/stores/portfolio";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@@ -35,13 +34,13 @@
$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
const name = doc.details.name;
const unsaved = !doc.details.isSaved;
if (!editor.handle.inDevelopmentMode()) return { name, unsaved };
if (!editor.inDevelopmentMode()) return { name, unsaved };
const tooltipDescription = `Document ID: ${doc.id}`;
return { name, unsaved, tooltipLabel: name, tooltipDescription };
});
const editor = getContext<Editor>("editor");
const editor = getContext<EditorHandle>("editor");
const portfolio = getContext<PortfolioStore>("portfolio");
function resizePanel(e: PointerEvent) {
@@ -151,9 +150,9 @@
tabCloseButtons={true}
tabMinWidths={true}
tabLabels={documentTabLabels}
emptySpaceAction={() => editor.handle.newDocumentDialog()}
clickAction={(tabIndex) => editor.handle.selectDocument($portfolio.documents[tabIndex].id)}
closeAction={(tabIndex) => editor.handle.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
emptySpaceAction={() => editor.newDocumentDialog()}
clickAction={(tabIndex) => editor.selectDocument($portfolio.documents[tabIndex].id)}
closeAction={(tabIndex) => editor.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
tabActiveIndex={$portfolio.activeDocumentIndex}
bind:this={documentPanel}
/>