mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Remove editor instances concept and clean up JS interop code
This commit is contained in:
parent
597c96a7db
commit
19eb6ce0ab
25 changed files with 256 additions and 325 deletions
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
|
||||
import { initWasm, createEditor } from "@graphite/wasm-communication/editor";
|
||||
import { type Editor as GraphiteEditor, initWasm, createEditor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
import Editor from "@graphite/components/Editor.svelte";
|
||||
|
||||
let editor: ReturnType<typeof createEditor> | undefined = undefined;
|
||||
let editor: GraphiteEditor | undefined = undefined;
|
||||
|
||||
onMount(async () => {
|
||||
await initWasm();
|
||||
|
@ -14,8 +14,8 @@
|
|||
});
|
||||
|
||||
onDestroy(() => {
|
||||
// Destroy the WASM editor instance
|
||||
editor?.instance.free();
|
||||
// Destroy the WASM editor handle
|
||||
editor?.handle.free();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@
|
|||
import { createNodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import { createPortfolioState } from "@graphite/state-providers/portfolio";
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
import type { createEditor } from "@graphite/wasm-communication/editor";
|
||||
import { type Editor } from "@graphite/wasm-communication/editor";
|
||||
|
||||
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
||||
|
||||
// Graphite WASM editor instance
|
||||
export let editor: ReturnType<typeof createEditor>;
|
||||
// Graphite WASM editor
|
||||
export let editor: Editor;
|
||||
setContext("editor", editor);
|
||||
|
||||
// State provider systems
|
||||
|
@ -48,7 +48,7 @@
|
|||
|
||||
onMount(() => {
|
||||
// Initialize certain setup tasks required by the editor backend to be ready for the user now that the frontend is ready
|
||||
editor.instance.initAfterFrontendReady(operatingSystem());
|
||||
editor.handle.initAfterFrontendReady(operatingSystem());
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
|
|
|
@ -252,7 +252,7 @@
|
|||
// TODO: Replace this temporary solution that only works in Chromium-based browsers with the custom color sampler used by the Eyedropper tool
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!(window as any).EyeDropper) {
|
||||
editor.instance.eyedropperSampleForColorPicker();
|
||||
editor.handle.eyedropperSampleForColorPicker();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,14 +127,14 @@
|
|||
const file = item.getAsFile();
|
||||
if (file?.type.includes("svg")) {
|
||||
const svgData = await file.text();
|
||||
editor.instance.pasteSvg(svgData, e.clientX, e.clientY);
|
||||
editor.handle.pasteSvg(svgData, e.clientX, e.clientY);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file?.type.startsWith("image")) {
|
||||
const imageData = await extractPixelData(file);
|
||||
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height, e.clientX, e.clientY);
|
||||
editor.handle.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height, e.clientX, e.clientY);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -142,23 +142,23 @@
|
|||
function panCanvasX(newValue: number) {
|
||||
const delta = newValue - scrollbarPos.x;
|
||||
scrollbarPos.x = newValue;
|
||||
editor.instance.panCanvas(-delta * scrollbarMultiplier.x, 0);
|
||||
editor.handle.panCanvas(-delta * scrollbarMultiplier.x, 0);
|
||||
}
|
||||
|
||||
function panCanvasY(newValue: number) {
|
||||
const delta = newValue - scrollbarPos.y;
|
||||
scrollbarPos.y = newValue;
|
||||
editor.instance.panCanvas(0, -delta * scrollbarMultiplier.y);
|
||||
editor.handle.panCanvas(0, -delta * scrollbarMultiplier.y);
|
||||
}
|
||||
|
||||
function pageX(delta: number) {
|
||||
const move = delta < 0 ? 1 : -1;
|
||||
editor.instance.panCanvasByFraction(move, 0);
|
||||
editor.handle.panCanvasByFraction(move, 0);
|
||||
}
|
||||
|
||||
function pageY(delta: number) {
|
||||
const move = delta < 0 ? 1 : -1;
|
||||
editor.instance.panCanvasByFraction(0, move);
|
||||
editor.handle.panCanvasByFraction(0, move);
|
||||
}
|
||||
|
||||
function canvasPointerDown(e: PointerEvent) {
|
||||
|
@ -290,7 +290,7 @@
|
|||
export function triggerTextCommit() {
|
||||
if (!textInput) return;
|
||||
const textCleaned = textInputCleanup(textInput.innerText);
|
||||
editor.instance.onChangeText(textCleaned);
|
||||
editor.handle.onChangeText(textCleaned);
|
||||
}
|
||||
|
||||
export async function displayEditableTextbox(displayEditableTextbox: DisplayEditableTextbox) {
|
||||
|
@ -314,7 +314,7 @@
|
|||
|
||||
textInput.oninput = () => {
|
||||
if (!textInput) return;
|
||||
editor.instance.updateBounds(textInputCleanup(textInput.innerText));
|
||||
editor.handle.updateBounds(textInputCleanup(textInput.innerText));
|
||||
};
|
||||
textInputMatrix = displayEditableTextbox.transform;
|
||||
const newFont = new FontFace("text-font", `url(${displayEditableTextbox.url})`);
|
||||
|
@ -371,8 +371,8 @@
|
|||
const rgb = await updateEyedropperSamplingState(mousePosition, primaryColor, secondaryColor);
|
||||
|
||||
if (setColorChoice && rgb) {
|
||||
if (setColorChoice === "Primary") editor.instance.updatePrimaryColor(...rgb, 1);
|
||||
if (setColorChoice === "Secondary") editor.instance.updateSecondaryColor(...rgb, 1);
|
||||
if (setColorChoice === "Primary") editor.handle.updatePrimaryColor(...rgb, 1);
|
||||
if (setColorChoice === "Secondary") editor.handle.updateSecondaryColor(...rgb, 1);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -130,15 +130,15 @@
|
|||
}
|
||||
|
||||
function toggleLayerVisibility(id: bigint) {
|
||||
editor.instance.toggleLayerVisibility(id);
|
||||
editor.handle.toggleLayerVisibility(id);
|
||||
}
|
||||
|
||||
function toggleLayerLock(id: bigint) {
|
||||
editor.instance.toggleLayerLock(id);
|
||||
editor.handle.toggleLayerLock(id);
|
||||
}
|
||||
|
||||
function handleExpandArrowClick(id: bigint) {
|
||||
editor.instance.toggleLayerExpansion(id);
|
||||
editor.handle.toggleLayerExpansion(id);
|
||||
}
|
||||
|
||||
async function onEditLayerName(listing: LayerListingInfo) {
|
||||
|
@ -164,7 +164,7 @@
|
|||
layers = layers;
|
||||
|
||||
const name = (e.target instanceof HTMLInputElement && e.target.value) || "";
|
||||
editor.instance.setLayerName(listing.entry.id, name);
|
||||
editor.handle.setLayerName(listing.entry.id, name);
|
||||
listing.entry.name = name;
|
||||
}
|
||||
|
||||
|
@ -196,11 +196,11 @@
|
|||
// Don't select while we are entering text to rename the layer
|
||||
if (listing.editingName) return;
|
||||
|
||||
editor.instance.selectLayer(listing.entry.id, accel, shift);
|
||||
editor.handle.selectLayer(listing.entry.id, accel, shift);
|
||||
}
|
||||
|
||||
async function deselectAllLayers() {
|
||||
editor.instance.deselectAllLayers();
|
||||
editor.handle.deselectAllLayers();
|
||||
}
|
||||
|
||||
function isNestingLayer(layerClassification: LayerClassification) {
|
||||
|
@ -322,7 +322,7 @@
|
|||
const { select, insertParentId, insertIndex } = draggingData;
|
||||
|
||||
select?.();
|
||||
editor.instance.moveLayerInTree(insertParentId, insertIndex);
|
||||
editor.handle.moveLayerInTree(insertParentId, insertIndex);
|
||||
}
|
||||
draggingData = undefined;
|
||||
fakeHighlight = undefined;
|
||||
|
|
|
@ -363,7 +363,7 @@
|
|||
|
||||
// Alt-click sets the clicked node as previewed
|
||||
if (lmb && e.altKey && nodeId !== undefined) {
|
||||
editor.instance.togglePreview(nodeId);
|
||||
editor.handle.togglePreview(nodeId);
|
||||
}
|
||||
|
||||
// Clicked on a port dot
|
||||
|
@ -440,7 +440,7 @@
|
|||
}
|
||||
|
||||
// Update the selection in the backend if it was modified
|
||||
if (modifiedSelected) editor.instance.selectNodes(new BigUint64Array(updatedSelected));
|
||||
if (modifiedSelected) editor.handle.selectNodes(new BigUint64Array(updatedSelected));
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -449,7 +449,7 @@
|
|||
if (lmb) {
|
||||
previousSelection = $nodeGraph.selected;
|
||||
// Clear current selection
|
||||
if (!e.shiftKey) editor.instance.selectNodes(new BigUint64Array(0));
|
||||
if (!e.shiftKey) editor.handle.selectNodes(new BigUint64Array(0));
|
||||
|
||||
const graphBounds = graph?.getBoundingClientRect();
|
||||
boxSelection = { startX: e.x - (graphBounds?.x || 0), startY: e.y - (graphBounds?.y || 0), endX: e.x - (graphBounds?.x || 0), endY: e.y - (graphBounds?.y || 0) };
|
||||
|
@ -466,7 +466,7 @@
|
|||
// const nodeId = node?.getAttribute("data-node") || undefined;
|
||||
// if (nodeId !== undefined) {
|
||||
// const id = BigInt(nodeId);
|
||||
// editor.instance.enterNestedNetwork(id);
|
||||
// editor.handle.enterNestedNetwork(id);
|
||||
// }
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,7 @@
|
|||
completeBoxSelection();
|
||||
boxSelection = undefined;
|
||||
} else if ((e.buttons & 2) !== 0) {
|
||||
editor.instance.selectNodes(new BigUint64Array(previousSelection));
|
||||
editor.handle.selectNodes(new BigUint64Array(previousSelection));
|
||||
boxSelection = undefined;
|
||||
} else {
|
||||
const graphBounds = graph?.getBoundingClientRect();
|
||||
|
@ -534,7 +534,7 @@
|
|||
}
|
||||
|
||||
function completeBoxSelection() {
|
||||
editor.instance.selectNodes(new BigUint64Array($nodeGraph.selected.concat($nodeGraph.nodes.filter((_, nodeIndex) => intersetNodeAABB(boxSelection, nodeIndex)).map((node) => node.id))));
|
||||
editor.handle.selectNodes(new BigUint64Array($nodeGraph.selected.concat($nodeGraph.nodes.filter((_, nodeIndex) => intersetNodeAABB(boxSelection, nodeIndex)).map((node) => node.id))));
|
||||
}
|
||||
|
||||
function showSelected(selected: bigint[], boxSelect: Box | undefined, node: bigint, nodeIndex: number): boolean {
|
||||
|
@ -542,7 +542,7 @@
|
|||
}
|
||||
|
||||
function toggleLayerVisibility(id: bigint) {
|
||||
editor.instance.toggleLayerVisibility(id);
|
||||
editor.handle.toggleLayerVisibility(id);
|
||||
}
|
||||
|
||||
function connectorToNodeIndex(svg: SVGSVGElement): { nodeId: bigint; index: number } | undefined {
|
||||
|
@ -589,7 +589,7 @@
|
|||
const selectedNodeBounds = selectedNode.getBoundingClientRect();
|
||||
const containerBoundsBounds = theNodesContainer.getBoundingClientRect();
|
||||
|
||||
return editor.instance.rectangleIntersects(
|
||||
return editor.handle.rectangleIntersects(
|
||||
new Float64Array(wireCurveLocations.map((loc) => loc.x)),
|
||||
new Float64Array(wireCurveLocations.map((loc) => loc.y)),
|
||||
selectedNodeBounds.top - containerBoundsBounds.y,
|
||||
|
@ -603,9 +603,9 @@
|
|||
if (link) {
|
||||
const isLayer = $nodeGraph.nodes.find((n) => n.id === selectedNodeId)?.isLayer;
|
||||
|
||||
editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, isLayer ? 1 : 0);
|
||||
editor.instance.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
if (!isLayer) editor.instance.shiftNode(selectedNodeId);
|
||||
editor.handle.connectNodesByLink(link.linkStart, 0, selectedNodeId, isLayer ? 1 : 0);
|
||||
editor.handle.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
if (!isLayer) editor.handle.shiftNode(selectedNodeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -614,7 +614,7 @@
|
|||
|
||||
const initialDisconnecting = disconnecting;
|
||||
if (disconnecting) {
|
||||
editor.instance.disconnectNodes(BigInt(disconnecting.nodeId), disconnecting.inputIndex);
|
||||
editor.handle.disconnectNodes(BigInt(disconnecting.nodeId), disconnecting.inputIndex);
|
||||
}
|
||||
disconnecting = undefined;
|
||||
|
||||
|
@ -625,7 +625,7 @@
|
|||
if (from !== undefined && to !== undefined) {
|
||||
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
|
||||
const { nodeId: inputConnectedNodeID, index: inputNodeConnectionIndex } = to;
|
||||
editor.instance.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
|
||||
editor.handle.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
|
||||
}
|
||||
} else if (linkInProgressFromConnector && !initialDisconnecting) {
|
||||
// If the add node menu is already open, we don't want to open it again
|
||||
|
@ -645,11 +645,11 @@
|
|||
} else if (draggingNodes) {
|
||||
if (draggingNodes.startX === e.x && draggingNodes.startY === e.y) {
|
||||
if (selectIfNotDragged !== undefined && ($nodeGraph.selected.length !== 1 || $nodeGraph.selected[0] !== selectIfNotDragged)) {
|
||||
editor.instance.selectNodes(new BigUint64Array([selectIfNotDragged]));
|
||||
editor.handle.selectNodes(new BigUint64Array([selectIfNotDragged]));
|
||||
}
|
||||
}
|
||||
|
||||
if ($nodeGraph.selected.length > 0 && (draggingNodes.roundX !== 0 || draggingNodes.roundY !== 0)) editor.instance.moveSelectedNodes(draggingNodes.roundX, draggingNodes.roundY);
|
||||
if ($nodeGraph.selected.length > 0 && (draggingNodes.roundX !== 0 || draggingNodes.roundY !== 0)) editor.handle.moveSelectedNodes(draggingNodes.roundX, draggingNodes.roundY);
|
||||
|
||||
checkInsertBetween();
|
||||
|
||||
|
@ -670,7 +670,7 @@
|
|||
const inputNodeConnectionIndex = 0;
|
||||
const x = Math.round(nodeListLocation.x / GRID_SIZE);
|
||||
const y = Math.round(nodeListLocation.y / GRID_SIZE) - 1;
|
||||
const inputConnectedNodeID = editor.instance.createNode(nodeType, x, y);
|
||||
const inputConnectedNodeID = editor.handle.createNode(nodeType, x, y);
|
||||
nodeListLocation = undefined;
|
||||
|
||||
if (!linkInProgressFromConnector) return;
|
||||
|
@ -678,7 +678,7 @@
|
|||
|
||||
if (from !== undefined) {
|
||||
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
|
||||
editor.instance.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
|
||||
editor.handle.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
|
||||
}
|
||||
|
||||
linkInProgressFromConnector = undefined;
|
||||
|
@ -882,7 +882,7 @@
|
|||
</div>
|
||||
<div class="details">
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<span title={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} bind:offsetWidth={layerNameLabelWidths[String(node.id)]}>
|
||||
<span title={editor.handle.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined} bind:offsetWidth={layerNameLabelWidths[String(node.id)]}>
|
||||
{node.alias || "Layer"}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -929,7 +929,7 @@
|
|||
<div class="primary" class:no-parameter-section={exposedInputsOutputs.length === 0}>
|
||||
<IconLabel icon={nodeIcon(node.name)} />
|
||||
<!-- TODO: Allow the user to edit the name, just like in the Layers panel -->
|
||||
<TextLabel tooltip={editor.instance.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.alias || node.name}</TextLabel>
|
||||
<TextLabel tooltip={editor.handle.inDevelopmentMode() ? `Node ID: ${node.id}` : undefined}>{node.alias || node.name}</TextLabel>
|
||||
</div>
|
||||
<!-- Parameter rows -->
|
||||
{#if exposedInputsOutputs.length > 0}
|
||||
|
|
|
@ -58,15 +58,15 @@
|
|||
}
|
||||
|
||||
function widgetValueCommit(index: number, value: unknown) {
|
||||
editor.instance.widgetValueCommit(layoutTarget, widgets[index].widgetId, value);
|
||||
editor.handle.widgetValueCommit(layoutTarget, widgets[index].widgetId, value);
|
||||
}
|
||||
|
||||
function widgetValueUpdate(index: number, value: unknown) {
|
||||
editor.instance.widgetValueUpdate(layoutTarget, widgets[index].widgetId, value);
|
||||
editor.handle.widgetValueUpdate(layoutTarget, widgets[index].widgetId, value);
|
||||
}
|
||||
|
||||
function widgetValueCommitAndUpdate(index: number, value: unknown) {
|
||||
editor.instance.widgetValueCommitAndUpdate(layoutTarget, widgets[index].widgetId, value);
|
||||
editor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[index].widgetId, value);
|
||||
}
|
||||
|
||||
// TODO: This seems to work, but verify the correctness and terseness of this, it's adapted from https://stackoverflow.com/a/67434028/775283
|
||||
|
|
|
@ -27,11 +27,11 @@
|
|||
}
|
||||
|
||||
function primaryColorChanged(color: Color) {
|
||||
editor.instance.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
|
||||
editor.handle.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
|
||||
}
|
||||
|
||||
function secondaryColorChanged(color: Color) {
|
||||
editor.instance.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
|
||||
editor.handle.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
...entry,
|
||||
|
||||
// Shared names with fields that need to be converted from the type used in `MenuBarEntry` to that of `MenuListEntry`
|
||||
action: () => editor.instance.widgetValueCommitAndUpdate(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
||||
action: () => editor.handle.widgetValueCommitAndUpdate(updateMenuBarLayout.layoutTarget, entry.action.widgetId, undefined),
|
||||
children: entry.children ? entry.children.map((entries) => entries.map((entry) => menuBarEntryToMenuListEntry(entry))) : undefined,
|
||||
|
||||
// New fields in `MenuListEntry`
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="New Document" icon="File" flush={true} action={() => editor.instance.newDocumentDialog()} />
|
||||
<TextButton label="New Document" icon="File" flush={true} action={() => editor.handle.newDocumentDialog()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(true), { key: "KeyN", label: "N" }]]} />
|
||||
|
@ -127,7 +127,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<TextButton label="Open Document" icon="Folder" flush={true} action={() => editor.instance.openDocument()} />
|
||||
<TextButton label="Open Document" icon="Folder" flush={true} action={() => editor.handle.openDocument()} />
|
||||
</td>
|
||||
<td>
|
||||
<UserInputLabel keysWithLabelsGroups={[[...platformModifiers(false), { key: "KeyO", label: "O" }]]} />
|
||||
|
@ -135,7 +135,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<TextButton label="Open Demo Artwork" icon="Image" flush={true} action={() => editor.instance.demoArtworkDialog()} />
|
||||
<TextButton label="Open Demo Artwork" icon="Image" flush={true} action={() => editor.handle.demoArtworkDialog()} />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => {
|
||||
const name = doc.displayName;
|
||||
|
||||
if (!editor.instance.inDevelopmentMode()) return { name };
|
||||
if (!editor.handle.inDevelopmentMode()) return { name };
|
||||
|
||||
const tooltip = `Document ID: ${doc.id}`;
|
||||
return { name, tooltip };
|
||||
|
@ -105,8 +105,8 @@
|
|||
tabCloseButtons={true}
|
||||
tabMinWidths={true}
|
||||
tabLabels={documentTabLabels}
|
||||
clickAction={(tabIndex) => editor.instance.selectDocument($portfolio.documents[tabIndex].id)}
|
||||
closeAction={(tabIndex) => editor.instance.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
|
||||
clickAction={(tabIndex) => editor.handle.selectDocument($portfolio.documents[tabIndex].id)}
|
||||
closeAction={(tabIndex) => editor.handle.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
|
||||
tabActiveIndex={$portfolio.activeDocumentIndex}
|
||||
bind:this={documentPanel}
|
||||
/>
|
||||
|
|
|
@ -108,7 +108,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (await shouldRedirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onKeyDown(key, modifiers, e.repeat);
|
||||
editor.handle.onKeyDown(key, modifiers, e.repeat);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (await shouldRedirectKeyboardEventToBackend(e)) {
|
||||
e.preventDefault();
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onKeyUp(key, modifiers, e.repeat);
|
||||
editor.handle.onKeyUp(key, modifiers, e.repeat);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
}
|
||||
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
editor.handle.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
}
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
|
@ -170,13 +170,13 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
}
|
||||
|
||||
if (!inTextInput) {
|
||||
if (textToolInteractiveInputElement) editor.instance.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText));
|
||||
if (textToolInteractiveInputElement) editor.handle.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText));
|
||||
else viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
|
||||
}
|
||||
|
||||
if (viewportPointerInteractionOngoing) {
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
editor.handle.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (textToolInteractiveInputElement) return;
|
||||
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
editor.handle.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
|
||||
}
|
||||
|
||||
function onPotentialDoubleClick(e: MouseEvent) {
|
||||
|
@ -202,7 +202,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (e.button === 2) buttons = 2; // RMB
|
||||
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
||||
editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
||||
}
|
||||
|
||||
// Mouse events
|
||||
|
@ -222,7 +222,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (isTargetingCanvas) {
|
||||
e.preventDefault();
|
||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||
editor.instance.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
||||
editor.handle.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,18 +250,18 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
const flattened = boundsOfViewports.flat();
|
||||
const data = Float64Array.from(flattened);
|
||||
|
||||
if (boundsOfViewports.length > 0) editor.instance.boundsOfViewports(data);
|
||||
if (boundsOfViewports.length > 0) editor.handle.boundsOfViewports(data);
|
||||
}
|
||||
|
||||
async function onBeforeUnload(e: BeforeUnloadEvent) {
|
||||
const activeDocument = get(portfolio).documents[get(portfolio).activeDocumentIndex];
|
||||
if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id);
|
||||
if (activeDocument && !activeDocument.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
|
||||
|
||||
// Skip the message if the editor crashed, since work is already lost
|
||||
if (await editor.instance.hasCrashed()) return;
|
||||
if (await editor.handle.hasCrashed()) return;
|
||||
|
||||
// Skip the message during development, since it's annoying when testing
|
||||
if (await editor.instance.inDevelopmentMode()) return;
|
||||
if (await editor.handle.inDevelopmentMode()) return;
|
||||
|
||||
const allDocumentsSaved = get(portfolio).documents.reduce((acc, doc) => acc && doc.isSaved, true);
|
||||
if (!allDocumentsSaved) {
|
||||
|
@ -279,9 +279,9 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
if (item.type === "text/plain") {
|
||||
item.getAsString((text) => {
|
||||
if (text.startsWith("graphite/layer: ")) {
|
||||
editor.instance.pasteSerializedData(text.substring(16, text.length));
|
||||
editor.handle.pasteSerializedData(text.substring(16, text.length));
|
||||
} else if (text.startsWith("graphite/nodes: ")) {
|
||||
editor.instance.pasteSerializedNodes(text.substring(16, text.length));
|
||||
editor.handle.pasteSerializedNodes(text.substring(16, text.length));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -290,14 +290,14 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
|
||||
if (file?.type === "svg") {
|
||||
const text = await file.text();
|
||||
editor.instance.pasteSvg(text);
|
||||
editor.handle.pasteSvg(text);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (file?.type.startsWith("image")) {
|
||||
const imageData = await extractPixelData(file);
|
||||
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
editor.handle.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -329,7 +329,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
const text = reader.result as string;
|
||||
|
||||
if (text.startsWith("graphite/layer: ")) {
|
||||
editor.instance.pasteSerializedData(text.substring(16, text.length));
|
||||
editor.handle.pasteSerializedData(text.substring(16, text.length));
|
||||
}
|
||||
};
|
||||
reader.readAsText(blob);
|
||||
|
@ -343,7 +343,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const text = reader.result as string;
|
||||
editor.instance.pasteSvg(text);
|
||||
editor.handle.pasteSvg(text);
|
||||
};
|
||||
reader.readAsText(blob);
|
||||
|
||||
|
@ -356,7 +356,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
reader.onload = async () => {
|
||||
if (reader.result instanceof ArrayBuffer) {
|
||||
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
|
||||
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
editor.handle.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
}
|
||||
};
|
||||
reader.readAsArrayBuffer(blob);
|
||||
|
@ -381,7 +381,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
|||
};
|
||||
const message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);
|
||||
|
||||
editor.instance.errorDialog("Cannot access clipboard", message);
|
||||
editor.handle.errorDialog("Cannot access clipboard", message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,6 @@ export function createLocalizationManager(editor: Editor) {
|
|||
// Subscribe to process backend event
|
||||
editor.subscriptions.subscribeJsMessage(TriggerAboutGraphiteLocalizedCommitDate, (triggerAboutGraphiteLocalizedCommitDate) => {
|
||||
const localized = localizeTimestamp(triggerAboutGraphiteLocalizedCommitDate.commitDate);
|
||||
editor.instance.requestAboutGraphiteDialogWithLocalizedCommitDate(localized.timestamp, localized.year);
|
||||
editor.handle.requestAboutGraphiteDialogWithLocalizedCommitDate(localized.timestamp, localized.year);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
|||
const orderedSavedDocuments = documentOrder.flatMap((id) => (previouslySavedDocuments[id] ? [previouslySavedDocuments[id]] : []));
|
||||
|
||||
orderedSavedDocuments?.forEach(async (doc: TriggerIndexedDbWriteDocument) => {
|
||||
editor.instance.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document);
|
||||
editor.handle.openAutoSavedDocument(BigInt(doc.details.id), doc.details.name, doc.details.isSaved, doc.document);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
|
|||
const preferences = await get<Record<string, unknown>>("preferences", graphiteStore);
|
||||
if (!preferences) return;
|
||||
|
||||
editor.instance.loadPreferences(JSON.stringify(preferences));
|
||||
editor.handle.loadPreferences(JSON.stringify(preferences));
|
||||
}
|
||||
|
||||
// FRONTEND MESSAGE SUBSCRIPTIONS
|
||||
|
|
|
@ -13,7 +13,7 @@ export function createDialogState(editor: Editor) {
|
|||
buttons: defaultWidgetLayout(),
|
||||
column1: defaultWidgetLayout(),
|
||||
column2: defaultWidgetLayout(),
|
||||
// Special case for the crash dialog because we cannot handle button widget callbacks from Rust once the editor instance has panicked
|
||||
// Special case for the crash dialog because we cannot handle button widget callbacks from Rust once the editor has panicked
|
||||
panicDetails: "",
|
||||
});
|
||||
|
||||
|
@ -27,7 +27,7 @@ export function createDialogState(editor: Editor) {
|
|||
}
|
||||
|
||||
// Creates a crash dialog from JS once the editor has panicked.
|
||||
// Normal dialogs are created in the Rust backend, but for the crash dialog, the editor instance has panicked so it cannot respond to widget callbacks.
|
||||
// Normal dialogs are created in the Rust backend, but for the crash dialog, the editor has panicked so it cannot respond to widget callbacks.
|
||||
function createCrashDialog(panicDetails: string) {
|
||||
update((state) => {
|
||||
state.visible = true;
|
||||
|
|
|
@ -52,9 +52,9 @@ export function createFontsState(editor: Editor) {
|
|||
const url = await getFontFileUrl(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle);
|
||||
if (url) {
|
||||
const response = await (await fetch(url)).arrayBuffer();
|
||||
editor.instance.onFontLoad(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle, url, new Uint8Array(response), triggerFontLoad.isDefault);
|
||||
editor.handle.onFontLoad(triggerFontLoad.font.fontFamily, triggerFontLoad.font.fontStyle, url, new Uint8Array(response), triggerFontLoad.isDefault);
|
||||
} else {
|
||||
editor.instance.errorDialog("Failed to load font", `The font ${triggerFontLoad.font.fontFamily} with style ${triggerFontLoad.font.fontStyle} does not exist`);
|
||||
editor.handle.errorDialog("Failed to load font", `The font ${triggerFontLoad.font.fontFamily} with style ${triggerFontLoad.font.fontStyle} does not exist`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -50,31 +50,31 @@ export function createPortfolioState(editor: Editor) {
|
|||
const data = await fetch(url);
|
||||
const content = await data.text();
|
||||
|
||||
editor.instance.openDocumentFile(name, content);
|
||||
editor.handle.openDocumentFile(name, content);
|
||||
} catch {
|
||||
// Needs to be delayed until the end of the current call stack so the existing demo artwork dialog can be closed first, otherwise this dialog won't show
|
||||
setTimeout(() => {
|
||||
editor.instance.errorDialog("Failed to open document", "The file could not be reached over the internet. You may be offline, or it may be missing.");
|
||||
editor.handle.errorDialog("Failed to open document", "The file could not be reached over the internet. You may be offline, or it may be missing.");
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => {
|
||||
const extension = editor.instance.fileSaveSuffix();
|
||||
const extension = editor.handle.fileSaveSuffix();
|
||||
const data = await upload(extension, "text");
|
||||
editor.instance.openDocumentFile(data.filename, data.content);
|
||||
editor.handle.openDocumentFile(data.filename, data.content);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerImport, async () => {
|
||||
const data = await upload("image/*", "data");
|
||||
|
||||
if (data.type.includes("svg")) {
|
||||
const svg = new TextDecoder().decode(data.content);
|
||||
editor.instance.pasteSvg(svg);
|
||||
editor.handle.pasteSvg(svg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const imageData = await extractPixelData(new Blob([data.content], { type: data.type }));
|
||||
editor.instance.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
editor.handle.pasteImage(new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(TriggerDownloadTextFile, (triggerFileDownload) => {
|
||||
downloadFileText(triggerFileDownload.name, triggerFileDownload.document);
|
||||
|
|
|
@ -1,30 +1,18 @@
|
|||
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
||||
import { type JsMessageType } from "@graphite/wasm-communication/messages";
|
||||
import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/wasm-communication/subscription-router";
|
||||
import init, { setRandomSeed, wasmMemory, JsEditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||
import init, { setRandomSeed, wasmMemory, EditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||
|
||||
export type WasmRawInstance = WebAssembly.Memory;
|
||||
export type WasmEditorInstance = JsEditorHandle;
|
||||
export type Editor = Readonly<ReturnType<typeof createEditor>>;
|
||||
export type Editor = {
|
||||
raw: WebAssembly.Memory;
|
||||
handle: EditorHandle;
|
||||
subscriptions: SubscriptionRouter;
|
||||
};
|
||||
|
||||
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
|
||||
let wasmImport: WebAssembly.Memory | undefined;
|
||||
|
||||
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
|
||||
export async function dispatchTauri(message: unknown) {
|
||||
if (!tauri) return;
|
||||
|
||||
try {
|
||||
const response = await (await tauri).invoke("handle_message", { message });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).editorInstance?.tauriResponse(response);
|
||||
} catch {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to dispatch Tauri message");
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called asynchronously before `createEditor()`
|
||||
// Should be called asynchronously before `createEditor()`.
|
||||
export async function initWasm() {
|
||||
// Skip if the WASM module is already initialized
|
||||
if (wasmImport !== undefined) return;
|
||||
|
@ -40,27 +28,26 @@ export async function initWasm() {
|
|||
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
const randomSeed = BigInt(randomSeedFloat);
|
||||
setRandomSeed(randomSeed);
|
||||
if (!tauri) return;
|
||||
await (await tauri).invoke("set_random_seed", { seed: randomSeedFloat });
|
||||
}
|
||||
|
||||
// Should be called after running `initWasm()` and its promise resolving
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function createEditor() {
|
||||
// Raw: Object containing several callable functions from `editor_api.rs` defined directly on the WASM module, not the editor instance (generated by wasm-bindgen)
|
||||
// Should be called after running `initWasm()` and its promise resolving.
|
||||
export function createEditor(): Editor {
|
||||
// Raw: object containing several callable functions from `editor_api.rs` defined directly on the WASM module, not the `EditorHandle` struct (generated by wasm-bindgen)
|
||||
if (!wasmImport) throw new Error("Editor WASM backend was not initialized at application startup");
|
||||
const raw: WasmRawInstance = wasmImport;
|
||||
const raw: WebAssembly.Memory = wasmImport;
|
||||
|
||||
// Instance: Object containing many functions from `editor_api.rs` that are part of the editor instance (generated by wasm-bindgen)
|
||||
const instance: WasmEditorInstance = new JsEditorHandle((messageType: JsMessageType, messageData: Record<string, unknown>) => {
|
||||
// This callback is called by WASM when a FrontendMessage is received from the WASM wrapper editor instance
|
||||
// We pass along the first two arguments then add our own `raw` and `instance` context for the last two arguments
|
||||
subscriptions.handleJsMessage(messageType, messageData, raw, instance);
|
||||
// Handle: object containing many functions from `editor_api.rs` that are part of the `EditorHandle` struct (generated by wasm-bindgen)
|
||||
const handle: EditorHandle = new EditorHandle((messageType: JsMessageType, messageData: Record<string, unknown>) => {
|
||||
// This callback is called by WASM when a FrontendMessage is received from the WASM wrapper `EditorHandle`
|
||||
// We pass along the first two arguments then add our own `raw` and `handle` context for the last two arguments
|
||||
subscriptions.handleJsMessage(messageType, messageData, raw, handle);
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).editorInstance = instance;
|
||||
|
||||
// Subscriptions: Allows subscribing to messages in JS that are sent from the WASM backend
|
||||
// TODO: Remove?
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).editorHandle = handle;
|
||||
|
||||
// Subscriptions: allows subscribing to messages in JS that are sent from the WASM backend
|
||||
const subscriptions: SubscriptionRouter = createSubscriptionRouter();
|
||||
|
||||
// Check if the URL hash fragment has any demo artwork to be loaded
|
||||
|
@ -75,7 +62,7 @@ export function createEditor() {
|
|||
|
||||
const filename = url.pathname.split("/").pop() || "Untitled";
|
||||
const content = await data.text();
|
||||
instance.openDocumentFile(filename, content);
|
||||
handle.openDocumentFile(filename, content);
|
||||
|
||||
// Remove the hash fragment from the URL
|
||||
history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
|
||||
|
@ -84,14 +71,10 @@ export function createEditor() {
|
|||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
raw,
|
||||
instance,
|
||||
subscriptions,
|
||||
};
|
||||
return { raw, handle, subscriptions };
|
||||
}
|
||||
|
||||
export function injectImaginatePollServerStatus() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(window as any).editorInstance?.injectImaginatePollServerStatus();
|
||||
(window as any).editorHandle?.injectImaginatePollServerStatus();
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { Transform, Type, plainToClass } from "class-transformer";
|
||||
|
||||
import { type PopoverButtonStyle, type IconName, type IconSize } from "@graphite/utility-functions/icons";
|
||||
import { type WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
|
||||
import { type EditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||
|
||||
export class JsMessage {
|
||||
// The marker provides a way to check if an object is a sub-class constructor for a jsMessage.
|
||||
|
@ -1275,7 +1275,7 @@ function createMenuLayoutRecursive(children: any[][]): MenuBarEntry[][] {
|
|||
|
||||
// `any` is used since the type of the object should be known from the Rust side
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type JSMessageFactory = (data: any, wasm: WasmRawInstance, instance: WasmEditorInstance) => JsMessage;
|
||||
type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHandle) => JsMessage;
|
||||
type MessageMaker = typeof JsMessage | JSMessageFactory;
|
||||
|
||||
export const messageMakers: Record<string, MessageMaker> = {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { plainToInstance } from "class-transformer";
|
||||
|
||||
import { type WasmEditorInstance, type WasmRawInstance } from "@graphite/wasm-communication/editor";
|
||||
import { type JsMessageType, messageMakers, type JsMessage } from "@graphite/wasm-communication/messages";
|
||||
import { type EditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js";
|
||||
|
||||
type JsMessageCallback<T extends JsMessage> = (messageData: T) => void;
|
||||
// Don't know a better way of typing this since it can be any subclass of JsMessage
|
||||
|
@ -17,7 +17,7 @@ export function createSubscriptionRouter() {
|
|||
subscriptions[messageType.name] = callback;
|
||||
};
|
||||
|
||||
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WasmRawInstance, instance: WasmEditorInstance) => {
|
||||
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WebAssembly.Memory, handle: EditorHandle) => {
|
||||
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
|
||||
const messageMaker = messageMakers[messageType];
|
||||
if (!messageMaker) {
|
||||
|
@ -42,7 +42,7 @@ export function createSubscriptionRouter() {
|
|||
// If the `messageMaker` is a `JsMessage` class then we use the class-transformer library's `plainToInstance` function in order to convert the JSON data into the destination class.
|
||||
// If it is not a `JsMessage` then it should be a custom function that creates a JsMessage from a JSON, so we call the function itself with the raw JSON as an argument.
|
||||
// The resulting `message` is an instance of a class that extends `JsMessage`.
|
||||
const message = messageIsClass ? plainToInstance(messageMaker, unwrappedMessageData) : messageMaker(unwrappedMessageData, wasm, instance);
|
||||
const message = messageIsClass ? plainToInstance(messageMaker, unwrappedMessageData) : messageMaker(unwrappedMessageData, wasm, handle);
|
||||
|
||||
// If we have constructed a valid message, then we try and execute the callback that the frontend has associated with this message.
|
||||
// The frontend should always have a callback for all messages, but due to message ordering, we might have to delay a few stack frames until we do.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue