mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 09:08:13 +08:00
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:
+26
-8
@@ -1,24 +1,42 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import { initWasm, createEditor } from "@graphite/editor";
|
import init, { EditorHandle, receiveNativeMessage } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor as GraphiteEditor } from "@graphite/editor";
|
import type { FrontendMessage } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
import { loadDemoArtwork } from "@graphite/utility-functions/network";
|
||||||
|
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||||
|
import { createSubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
import type { MessageName, SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
import Editor from "@graphite/components/Editor.svelte";
|
import Editor from "@graphite/components/Editor.svelte";
|
||||||
|
|
||||||
let editor: GraphiteEditor | undefined = undefined;
|
let subscriptions: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editor: EditorHandle | undefined = undefined;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await initWasm();
|
// Initialize the Wasm module
|
||||||
|
const wasm = await init();
|
||||||
|
for (const [name, f] of Object.entries(wasm)) {
|
||||||
|
if (name.startsWith("__node_registry")) f();
|
||||||
|
}
|
||||||
|
window.imageCanvases = {};
|
||||||
|
window.receiveNativeMessage = receiveNativeMessage;
|
||||||
|
|
||||||
editor = createEditor();
|
// Create the editor and subscriptions router
|
||||||
|
const randomSeed = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
|
||||||
|
subscriptions = createSubscriptionsRouter();
|
||||||
|
editor = EditorHandle.create(operatingSystem(), randomSeed, (messageType: MessageName, messageData: FrontendMessage) => {
|
||||||
|
subscriptions?.handleFrontendMessage(messageType, messageData);
|
||||||
|
});
|
||||||
|
|
||||||
|
await loadDemoArtwork(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor?.destroy();
|
editor?.free();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if editor !== undefined}
|
{#if subscriptions !== undefined && editor !== undefined}
|
||||||
<Editor {editor} />
|
<Editor {subscriptions} {editor} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
+11
-17
@@ -2,45 +2,39 @@
|
|||||||
|
|
||||||
## Svelte components: `components/`
|
## Svelte components: `components/`
|
||||||
|
|
||||||
Svelte components that build the Graphite editor GUI. These each contain a TypeScript section, a Svelte-templated HTML template section, and an SCSS stylesheet section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur.
|
Svelte components that build the Graphite editor GUI from layouts, panels, widgets, and menus. These each contain a TypeScript section, a Svelte-templated HTML template section, and an SCSS stylesheet section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur.
|
||||||
|
|
||||||
## Managers: `managers/`
|
## Managers: `managers/`
|
||||||
|
|
||||||
TypeScript files which manage the input/output of browser APIs and link this functionality with the editor backend. These files subscribe to backend messages to execute JS APIs, and in response to these APIs or user interactions, they may call functions into the backend (defined in `/frontend/wasm/editor_api.rs`).
|
TypeScript files, constructed by the editor frontend, which manage the input/output of browser APIs and link this functionality with the editor backend. These files subscribe to frontend messages to execute JS APIs, and in response to these APIs or user interactions, they may call functions in the backend (defined in `/frontend/wasm/editor_api.rs`).
|
||||||
|
|
||||||
Each manager module exports a factory function (e.g. `createClipboardManager(editor)`) that sets up message subscriptions and returns a `{ destroy }` object. In `Editor.svelte`, each manager is created at startup and its `destroy()` method is called on unmount to clean up subscriptions and side-effects (e.g. event listeners). Managers use self-accepting HMR to tear down and re-create with updated code during development.
|
Each manager module stores its dependencies (like `subscriptionsRouter` and `editorHandle`) in module-level variables and exports a `create*()` and `destroy*()` function pair. `Editor.svelte` calls each `create*()` constructor in its `onMount` and calls each `destroy*()` in its `onDestroy`. Managers replace themselves during HMR updates if they are modified live during development.
|
||||||
|
|
||||||
## Stores: `stores/`
|
## Stores: `stores/`
|
||||||
|
|
||||||
TypeScript files which provide reactive state to Svelte components. Each module persists a Svelte writable store at module level (surviving HMR via `import.meta.hot.data`) and exports a factory function (e.g. `createDialogStore(editor)`) that sets up backend message subscriptions and returns an object containing the store's `subscribe` method, any action methods for components to call, and a `destroy` method.
|
TypeScript files, constructed by the editor frontend, which provide reactive state to Svelte components. Each module persists a Svelte writable store at module level (surviving HMR via `import.meta.hot.data`) and exports a `create*()` function that sets up frontend message subscriptions and returns `{ subscribe }` (the shape required by Svelte's custom store contract). A corresponding `destroy*()` function is also exported. Some stores also export standalone action functions (like `createCrashDialog()` or `toggleFullscreen()`) as module-level exports.
|
||||||
|
|
||||||
In `Editor.svelte`, each store is created and passed to Svelte's `setContext()`. Components access stores via `getContext<DialogStore>("dialog")` and use the `subscribe` method for reactive state and action methods (like `createCrashDialog()`) to trigger state changes.
|
In `Editor.svelte`, each store is created synchronously during component initialization (not in `onMount`, since child components need `getContext` access during their own initialization) and passed to Svelte's `setContext()`. Components access stores via calls like `getContext<DialogStore>("dialog")`. Unlike managers, stores do not replace themselves during HMR; instead, `Editor.svelte` is remounted to replace them entirely.
|
||||||
|
|
||||||
## *Managers vs. stores*
|
## *Managers vs. stores*
|
||||||
|
|
||||||
*Both managers and stores subscribe to backend messages and may interact with browser APIs. The difference is that stores expose reactive state to components via `setContext()`/`getContext()`, while managers are self-contained systems that operate for the lifetime of the application and aren't accessed by Svelte components.*
|
*Both managers and stores subscribe to frontend messages and may interact with browser APIs. The difference is that stores expose reactive state to components via `setContext()`/`getContext()`, while managers are self-contained systems that operate for the lifetime of the application and aren't accessed by Svelte components.*
|
||||||
|
|
||||||
## Utility functions: `utility-functions/`
|
## Utility functions: `utility-functions/`
|
||||||
|
|
||||||
TypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function.
|
TypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function.
|
||||||
|
|
||||||
## Wasm editor: `editor.ts`
|
## Subscriptions router: `subscriptions-router.ts`
|
||||||
|
|
||||||
Instantiates the Wasm and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the Wasm bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same Wasm module instance. The function returns an object where `raw` is the Wasm memory, `handle` provides access to callable backend functions, and `subscriptions` is the subscription router (described below).
|
Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. The router's other function, `handleFrontendMessage(messageType, messageData)`, is called via the callback passed to `EditorHandle.create()` in `App.svelte` when the backend sends a `FrontendMessage`. When this occurs, the subscriptions router delivers the message to the subscriber by executing its registered `callback` function.
|
||||||
|
|
||||||
`initWasm()` occurs in `main.ts` right before the Svelte application is mounted, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the stores described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.handle` or `editor.subscriptions`.
|
|
||||||
|
|
||||||
## Subscription router: `subscription-router.ts`
|
|
||||||
|
|
||||||
Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. The router's other function, `handleFrontendMessage(messageType, messageData)`, is called via the callback passed to `EditorHandle.create()` in `editor.ts` when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber by executing its registered `callback` function.
|
|
||||||
|
|
||||||
## Svelte app entry point: `App.svelte`
|
## Svelte app entry point: `App.svelte`
|
||||||
|
|
||||||
The entry point for the Svelte application.
|
The entry point for the Svelte application. Initializes the Wasm module, creates the `EditorHandle` backend instance and the subscriptions router, and renders `Editor.svelte` once both are ready. The `EditorHandle` is the wasm-bindgen interface to the Rust editor backend (defined in `/frontend/wasm/editor_api.rs`), providing access to callable backend functions. Both the editor and subscriptions router are passed as props to `Editor.svelte` and set as Svelte contexts for use throughout the component tree.
|
||||||
|
|
||||||
## Editor base instance: `Editor.svelte`
|
## Editor base instance: `Editor.svelte`
|
||||||
|
|
||||||
This is where we define global CSS style rules, construct all stores and managers with the editor instance, set store contexts for component access, and clean up all `destroy()` methods on unmount.
|
This is where we define global CSS style rules, construct all stores and managers, set store contexts for component access, and call each module's `destroy*()` function on unmount (on HMR during development).
|
||||||
|
|
||||||
## Global type augmentations: `global.d.ts`
|
## Global type augmentations: `global.d.ts`
|
||||||
|
|
||||||
@@ -48,4 +42,4 @@ Extends built-in browser type definitions using TypeScript's interface merging.
|
|||||||
|
|
||||||
## JS bundle entry point: `main.ts`
|
## JS bundle entry point: `main.ts`
|
||||||
|
|
||||||
The entry point for the entire project's code bundle. Here we simply mount the Svelte application with `export default mount(App, { target: document.body });`.
|
The entry point for the entire project's code bundle. Mounts the Svelte application with `export default mount(App, { target: document.body })`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy, setContext } from "svelte";
|
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 { createClipboardManager, destroyClipboardManager } from "@graphite/managers/clipboard";
|
||||||
import { createFontsManager, destroyFontsManager } from "@graphite/managers/fonts";
|
import { createFontsManager, destroyFontsManager } from "@graphite/managers/fonts";
|
||||||
import { createHyperlinkManager, destroyHyperlinkManager } from "@graphite/managers/hyperlink";
|
import { createHyperlinkManager, destroyHyperlinkManager } from "@graphite/managers/hyperlink";
|
||||||
@@ -16,39 +16,42 @@
|
|||||||
import { createNodeGraphStore, destroyNodeGraphStore } from "@graphite/stores/node-graph";
|
import { createNodeGraphStore, destroyNodeGraphStore } from "@graphite/stores/node-graph";
|
||||||
import { createPortfolioStore, destroyPortfolioStore } from "@graphite/stores/portfolio";
|
import { createPortfolioStore, destroyPortfolioStore } from "@graphite/stores/portfolio";
|
||||||
import { createTooltipStore, destroyTooltipStore } from "@graphite/stores/tooltip";
|
import { createTooltipStore, destroyTooltipStore } from "@graphite/stores/tooltip";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
||||||
|
|
||||||
// Graphite Wasm editor
|
// Graphite Wasm editor and subscriptions router
|
||||||
export let editor: Editor;
|
export let subscriptions: SubscriptionsRouter;
|
||||||
|
export let editor: EditorHandle;
|
||||||
|
setContext("subscriptions", subscriptions);
|
||||||
setContext("editor", editor);
|
setContext("editor", editor);
|
||||||
|
|
||||||
const stores = {
|
const stores = {
|
||||||
dialog: createDialogStore(editor),
|
dialog: createDialogStore(subscriptions, editor),
|
||||||
tooltip: createTooltipStore(editor),
|
tooltip: createTooltipStore(subscriptions),
|
||||||
document: createDocumentStore(editor),
|
document: createDocumentStore(subscriptions),
|
||||||
fullscreen: createFullscreenStore(editor),
|
fullscreen: createFullscreenStore(subscriptions),
|
||||||
nodeGraph: createNodeGraphStore(editor),
|
nodeGraph: createNodeGraphStore(subscriptions),
|
||||||
portfolio: createPortfolioStore(editor),
|
portfolio: createPortfolioStore(subscriptions, editor),
|
||||||
appWindow: createAppWindowStore(editor),
|
appWindow: createAppWindowStore(subscriptions),
|
||||||
};
|
};
|
||||||
Object.entries(stores).forEach(([key, store]) => setContext(key, store));
|
Object.entries(stores).forEach(([key, store]) => setContext(key, store));
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
createClipboardManager(editor);
|
createClipboardManager(subscriptions, editor);
|
||||||
createHyperlinkManager(editor);
|
createHyperlinkManager(subscriptions);
|
||||||
createLocalizationManager(editor);
|
createLocalizationManager(subscriptions, editor);
|
||||||
createPanicManager(editor);
|
createPanicManager(subscriptions);
|
||||||
createPersistenceManager(editor, stores.portfolio);
|
createPersistenceManager(subscriptions, editor, stores.portfolio);
|
||||||
createFontsManager(editor);
|
createFontsManager(subscriptions, editor);
|
||||||
createInputManager(editor, stores.dialog, stores.portfolio, stores.document);
|
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.
|
// 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.
|
// 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
|
// Re-send all UI layouts from Rust so the frontend has them after an HMR re-mount
|
||||||
editor.handle.resendAllLayouts();
|
editor.resendAllLayouts();
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
import type { LabeledShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, LabeledShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { TooltipStore } from "@graphite/stores/tooltip";
|
import type { TooltipStore } from "@graphite/stores/tooltip";
|
||||||
|
|
||||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
@@ -11,7 +10,7 @@
|
|||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
|
|
||||||
const tooltip = getContext<TooltipStore>("tooltip");
|
const tooltip = getContext<TooltipStore>("tooltip");
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
|
|
||||||
let self: FloatingMenu | undefined;
|
let self: FloatingMenu | undefined;
|
||||||
|
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
|
|
||||||
// TODO: Once all TODOs are replaced with real text, remove this function
|
// TODO: Once all TODOs are replaced with real text, remove this function
|
||||||
function filterTodo(text: string | undefined): string | undefined {
|
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;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
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 { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||||
|
|
||||||
let dataPanelLayout: Layout = [];
|
let dataPanelLayout: Layout = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("DataPanel", (data) => {
|
subscriptions.subscribeLayoutUpdate("DataPanel", (data) => {
|
||||||
patchLayout(dataPanelLayout, data);
|
patchLayout(dataPanelLayout, data);
|
||||||
dataPanelLayout = dataPanelLayout;
|
dataPanelLayout = dataPanelLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("DataPanel");
|
subscriptions.unsubscribeLayoutUpdate("DataPanel");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||||
|
|
||||||
import type { Color, MenuDirection, MouseCursorIcon } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { Color, EditorHandle, MenuDirection, MouseCursorIcon } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { AppWindowStore } from "@graphite/stores/app-window";
|
import type { AppWindowStore } from "@graphite/stores/app-window";
|
||||||
import type { DocumentStore } from "@graphite/stores/document";
|
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 { fillChoiceColor, createColor } from "@graphite/utility-functions/colors";
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||||
@@ -26,7 +26,8 @@
|
|||||||
let viewport: HTMLDivElement | undefined;
|
let viewport: HTMLDivElement | undefined;
|
||||||
let gradientStopPicker: ColorPicker | 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 appWindow = getContext<AppWindowStore>("appWindow");
|
||||||
const document = getContext<DocumentStore>("document");
|
const document = getContext<DocumentStore>("document");
|
||||||
|
|
||||||
@@ -142,13 +143,13 @@
|
|||||||
function panCanvasX(newValue: number) {
|
function panCanvasX(newValue: number) {
|
||||||
const delta = newValue - scrollbarPos.x;
|
const delta = newValue - scrollbarPos.x;
|
||||||
scrollbarPos.x = newValue;
|
scrollbarPos.x = newValue;
|
||||||
editor.handle.panCanvas(-delta * scrollbarMultiplier.x, 0);
|
editor.panCanvas(-delta * scrollbarMultiplier.x, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function panCanvasY(newValue: number) {
|
function panCanvasY(newValue: number) {
|
||||||
const delta = newValue - scrollbarPos.y;
|
const delta = newValue - scrollbarPos.y;
|
||||||
scrollbarPos.y = newValue;
|
scrollbarPos.y = newValue;
|
||||||
editor.handle.panCanvas(0, -delta * scrollbarMultiplier.y);
|
editor.panCanvas(0, -delta * scrollbarMultiplier.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function canvasPointerDown(e: PointerEvent) {
|
function canvasPointerDown(e: PointerEvent) {
|
||||||
@@ -342,7 +343,7 @@
|
|||||||
export function triggerTextCommit() {
|
export function triggerTextCommit() {
|
||||||
if (!textInput) return;
|
if (!textInput) return;
|
||||||
const textCleaned = textInputCleanup(textInput.innerText);
|
const textCleaned = textInputCleanup(textInput.innerText);
|
||||||
editor.handle.onChangeText(textCleaned, false);
|
editor.onChangeText(textCleaned, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function displayEditableTextbox(data: MessageBody<"DisplayEditableTextbox">) {
|
export async function displayEditableTextbox(data: MessageBody<"DisplayEditableTextbox">) {
|
||||||
@@ -372,7 +373,7 @@
|
|||||||
|
|
||||||
textInput.oninput = () => {
|
textInput.oninput = () => {
|
||||||
if (!textInput) return;
|
if (!textInput) return;
|
||||||
editor.handle.updateBounds(textInputCleanup(textInput.innerText));
|
editor.updateBounds(textInputCleanup(textInput.innerText));
|
||||||
};
|
};
|
||||||
|
|
||||||
textInputMatrix = data.transform;
|
textInputMatrix = data.transform;
|
||||||
@@ -454,12 +455,12 @@
|
|||||||
updatePixelRatio();
|
updatePixelRatio();
|
||||||
|
|
||||||
// Update rendered SVGs
|
// Update rendered SVGs
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentArtwork", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDocumentArtwork", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
updateDocumentArtwork(data.svg);
|
updateDocumentArtwork(data.svg);
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateEyedropperSamplingState", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateEyedropperSamplingState", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const { image, mousePosition, primaryColor, secondaryColor, setColorChoice } = data;
|
const { image, mousePosition, primaryColor, secondaryColor, setColorChoice } = data;
|
||||||
@@ -467,25 +468,25 @@
|
|||||||
const rgb = await updateEyedropperSamplingState(imageData, mousePosition, primaryColor, secondaryColor);
|
const rgb = await updateEyedropperSamplingState(imageData, mousePosition, primaryColor, secondaryColor);
|
||||||
|
|
||||||
if (setColorChoice && rgb) {
|
if (setColorChoice && rgb) {
|
||||||
if (setColorChoice === "Primary") editor.handle.updatePrimaryColor(...rgb, 1);
|
if (setColorChoice === "Primary") editor.updatePrimaryColor(...rgb, 1);
|
||||||
if (setColorChoice === "Secondary") editor.handle.updateSecondaryColor(...rgb, 1);
|
if (setColorChoice === "Secondary") editor.updateSecondaryColor(...rgb, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gradient stop color picker
|
// Gradient stop color picker
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
|
||||||
gradientStopPickerColor = data.color;
|
gradientStopPickerColor = data.color;
|
||||||
gradientStopPickerPosition = { x: data.position[0], y: data.position[1] };
|
gradientStopPickerPosition = { x: data.position[0], y: data.position[1] };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update scrollbars and rulers
|
// Update scrollbars and rulers
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentScrollbars", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDocumentScrollbars", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const { position, size, multiplier } = data;
|
const { position, size, multiplier } = data;
|
||||||
updateDocumentScrollbars(position, size, multiplier);
|
updateDocumentScrollbars(position, size, multiplier);
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDocumentRulers", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const { origin, spacing, interval, visible } = data;
|
const { origin, spacing, interval, visible } = data;
|
||||||
@@ -493,24 +494,24 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update mouse cursor icon
|
// Update mouse cursor icon
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateMouseCursor", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateMouseCursor", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
updateMouseCursor(data.cursor);
|
updateMouseCursor(data.cursor);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Text entry
|
// Text entry
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerTextCommit", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerTextCommit", async () => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
triggerTextCommit();
|
triggerTextCommit();
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextbox", async (data) => {
|
subscriptions.subscribeFrontendMessage("DisplayEditableTextbox", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
displayEditableTextbox(data);
|
displayEditableTextbox(data);
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
|
subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (textInput && data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {
|
if (textInput && data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {
|
||||||
@@ -521,10 +522,10 @@
|
|||||||
textInput.style.fontFamily = "text-font";
|
textInput.style.fontFamily = "text-font";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxTransform", async (data) => {
|
subscriptions.subscribeFrontendMessage("DisplayEditableTextboxTransform", async (data) => {
|
||||||
textInputMatrix = data.transform;
|
textInputMatrix = data.transform;
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayRemoveEditableTextbox", async () => {
|
subscriptions.subscribeFrontendMessage("DisplayRemoveEditableTextbox", async () => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
displayRemoveEditableTextbox();
|
displayRemoveEditableTextbox();
|
||||||
@@ -547,17 +548,17 @@
|
|||||||
removeUpdatePixelRatio?.();
|
removeUpdatePixelRatio?.();
|
||||||
addedFontFaces.forEach((face) => window.document.fonts.delete(face));
|
addedFontFaces.forEach((face) => window.document.fonts.delete(face));
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentArtwork");
|
subscriptions.unsubscribeFrontendMessage("UpdateDocumentArtwork");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateEyedropperSamplingState");
|
subscriptions.unsubscribeFrontendMessage("UpdateEyedropperSamplingState");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateGradientStopColorPickerPosition");
|
subscriptions.unsubscribeFrontendMessage("UpdateGradientStopColorPickerPosition");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentScrollbars");
|
subscriptions.unsubscribeFrontendMessage("UpdateDocumentScrollbars");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentRulers");
|
subscriptions.unsubscribeFrontendMessage("UpdateDocumentRulers");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateMouseCursor");
|
subscriptions.unsubscribeFrontendMessage("UpdateMouseCursor");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerTextCommit");
|
subscriptions.unsubscribeFrontendMessage("TriggerTextCommit");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextbox");
|
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextbox");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxUpdateFontData");
|
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxUpdateFontData");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxTransform");
|
subscriptions.unsubscribeFrontendMessage("DisplayEditableTextboxTransform");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayRemoveEditableTextbox");
|
subscriptions.unsubscribeFrontendMessage("DisplayRemoveEditableTextbox");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -628,7 +629,7 @@
|
|||||||
open={Boolean(gradientStopPickerPosition && gradientStopPickerColor)}
|
open={Boolean(gradientStopPickerPosition && gradientStopPickerColor)}
|
||||||
on:open={({ detail }) => {
|
on:open={({ detail }) => {
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
editor.handle.closeGradientStopColorPicker();
|
editor.closeGradientStopColorPicker();
|
||||||
gradientStopPickerPosition = undefined;
|
gradientStopPickerPosition = undefined;
|
||||||
gradientStopPickerColor = undefined;
|
gradientStopPickerColor = undefined;
|
||||||
}
|
}
|
||||||
@@ -636,10 +637,10 @@
|
|||||||
colorOrGradient={{ Solid: gradientStopPickerColor || createColor(0, 0, 0, 1) }}
|
colorOrGradient={{ Solid: gradientStopPickerColor || createColor(0, 0, 0, 1) }}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
const color = fillChoiceColor(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:startHistoryTransaction={() => editor.startGradientStopColorTransaction()}
|
||||||
on:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}
|
on:commitHistoryTransaction={() => editor.commitGradientStopColorTransaction()}
|
||||||
bind:this={gradientStopPicker}
|
bind:this={gradientStopPicker}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -682,10 +683,10 @@
|
|||||||
direction="Vertical"
|
direction="Vertical"
|
||||||
thumbLength={scrollbarSize.y}
|
thumbLength={scrollbarSize.y}
|
||||||
thumbPosition={scrollbarPos.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:thumbPosition={({ detail }) => panCanvasY(detail)}
|
||||||
on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(false)}
|
on:thumbDragStart={() => editor.panCanvasAbortPrepare(false)}
|
||||||
on:thumbDragAbort={() => editor.handle.panCanvasAbort(false)}
|
on:thumbDragAbort={() => editor.panCanvasAbort(false)}
|
||||||
/>
|
/>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
@@ -694,10 +695,10 @@
|
|||||||
direction="Horizontal"
|
direction="Horizontal"
|
||||||
thumbLength={scrollbarSize.x}
|
thumbLength={scrollbarSize.x}
|
||||||
thumbPosition={scrollbarPos.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:thumbPosition={({ detail }) => panCanvasX(detail)}
|
||||||
on:thumbDragStart={() => editor.handle.panCanvasAbortPrepare(true)}
|
on:thumbDragStart={() => editor.panCanvasAbortPrepare(true)}
|
||||||
on:thumbDragAbort={() => editor.handle.panCanvasAbort(true)}
|
on:thumbDragAbort={() => editor.panCanvasAbort(true)}
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||||
import { SvelteMap } from "svelte/reactivity";
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
|
|
||||||
import type { LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { NodeGraphStore } from "@graphite/stores/node-graph";
|
import type { NodeGraphStore } from "@graphite/stores/node-graph";
|
||||||
import type { TooltipStore } from "@graphite/stores/tooltip";
|
import type { TooltipStore } from "@graphite/stores/tooltip";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
@@ -41,7 +41,8 @@
|
|||||||
startY: number;
|
startY: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||||
|
const editor = getContext<EditorHandle>("editor");
|
||||||
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
||||||
const tooltip = getContext<TooltipStore>("tooltip");
|
const tooltip = getContext<TooltipStore>("tooltip");
|
||||||
|
|
||||||
@@ -69,26 +70,26 @@
|
|||||||
let layersPanelBottomBarLayout: Layout = [];
|
let layersPanelBottomBarLayout: Layout = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", (data) => {
|
subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", (data) => {
|
||||||
patchLayout(layersPanelControlBarLeftLayout, data);
|
patchLayout(layersPanelControlBarLeftLayout, data);
|
||||||
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
|
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", (data) => {
|
subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", (data) => {
|
||||||
patchLayout(layersPanelControlBarRightLayout, data);
|
patchLayout(layersPanelControlBarRightLayout, data);
|
||||||
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
|
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", (data) => {
|
subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", (data) => {
|
||||||
patchLayout(layersPanelBottomBarLayout, data);
|
patchLayout(layersPanelBottomBarLayout, data);
|
||||||
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
|
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
|
||||||
rebuildLayerHierarchy(data.layerStructure);
|
rebuildLayerHierarchy(data.layerStructure);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
|
||||||
const targetLayer = data.data;
|
const targetLayer = data.data;
|
||||||
const targetId = targetLayer.id;
|
const targetId = targetLayer.id;
|
||||||
|
|
||||||
@@ -107,11 +108,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
|
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
|
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
|
subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
|
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
|
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
|
||||||
|
|
||||||
removeEventListener("pointerup", draggingPointerUp);
|
removeEventListener("pointerup", draggingPointerUp);
|
||||||
removeEventListener("pointermove", draggingPointerMove);
|
removeEventListener("pointermove", draggingPointerMove);
|
||||||
@@ -125,17 +126,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
function toggleNodeVisibilityLayerPanel(id: bigint) {
|
function toggleNodeVisibilityLayerPanel(id: bigint) {
|
||||||
editor.handle.toggleNodeVisibilityLayerPanel(id);
|
editor.toggleNodeVisibilityLayerPanel(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLayerLock(id: bigint) {
|
function toggleLayerLock(id: bigint) {
|
||||||
editor.handle.toggleLayerLock(id);
|
editor.toggleLayerLock(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleExpandArrowClickWithModifiers(e: MouseEvent, id: bigint) {
|
function handleExpandArrowClickWithModifiers(e: MouseEvent, id: bigint) {
|
||||||
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
const accel = operatingSystem() === "Mac" ? e.metaKey : e.ctrlKey;
|
||||||
const collapseRecursive = e.altKey || accel;
|
const collapseRecursive = e.altKey || accel;
|
||||||
editor.handle.toggleLayerExpansion(id, collapseRecursive);
|
editor.toggleLayerExpansion(id, collapseRecursive);
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +163,7 @@
|
|||||||
layers = layers;
|
layers = layers;
|
||||||
|
|
||||||
const name = (e.target instanceof HTMLInputElement && e.target.value) || "";
|
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;
|
listing.entry.alias = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clipLayer(listing: LayerListingInfo) {
|
function clipLayer(listing: LayerListingInfo) {
|
||||||
editor.handle.clipLayer(listing.entry.id);
|
editor.clipLayer(listing.entry.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clippingKeyPress(e: KeyboardEvent) {
|
function clippingKeyPress(e: KeyboardEvent) {
|
||||||
@@ -247,7 +248,7 @@
|
|||||||
// Don't select while we are entering text to rename the layer
|
// Don't select while we are entering text to rename the layer
|
||||||
if (listing.editingName) return;
|
if (listing.editingName) return;
|
||||||
|
|
||||||
editor.handle.selectLayer(listing.entry.id, accel, shift);
|
editor.selectLayer(listing.entry.id, accel, shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deselectAllLayers() {
|
async function deselectAllLayers() {
|
||||||
@@ -256,7 +257,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.handle.deselectAllLayers();
|
editor.deselectAllLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
|
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
|
||||||
@@ -389,7 +390,7 @@
|
|||||||
|
|
||||||
// Commit the move
|
// Commit the move
|
||||||
select?.();
|
select?.();
|
||||||
editor.handle.moveLayerInTree(insertParentId, insertIndex);
|
editor.moveLayerInTree(insertParentId, insertIndex);
|
||||||
|
|
||||||
// Prevent the subsequent click event from processing
|
// Prevent the subsequent click event from processing
|
||||||
justFinishedDrag = true;
|
justFinishedDrag = true;
|
||||||
@@ -445,7 +446,7 @@
|
|||||||
const inputElement = document.activeElement;
|
const inputElement = document.activeElement;
|
||||||
if (inputElement instanceof HTMLInputElement) {
|
if (inputElement instanceof HTMLInputElement) {
|
||||||
const name = inputElement.value || "";
|
const name = inputElement.value || "";
|
||||||
editor.handle.setLayerName(currentListing.entry.id, name);
|
editor.setLayerName(currentListing.entry.id, name);
|
||||||
currentListing.entry.alias = name;
|
currentListing.entry.alias = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,25 +2,25 @@
|
|||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
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 { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||||
|
|
||||||
let propertiesPanelLayout: Layout = [];
|
let propertiesPanelLayout: Layout = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("PropertiesPanel", (data) => {
|
subscriptions.subscribeLayoutUpdate("PropertiesPanel", (data) => {
|
||||||
patchLayout(propertiesPanelLayout, data);
|
patchLayout(propertiesPanelLayout, data);
|
||||||
propertiesPanelLayout = propertiesPanelLayout;
|
propertiesPanelLayout = propertiesPanelLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
|
subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
@@ -13,19 +13,20 @@
|
|||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.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 = [];
|
let welcomePanelButtonsLayout: Layout = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", (data) => {
|
subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", (data) => {
|
||||||
patchLayout(welcomePanelButtonsLayout, data);
|
patchLayout(welcomePanelButtonsLayout, data);
|
||||||
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
|
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
|
subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
|
||||||
});
|
});
|
||||||
|
|
||||||
function dropFile(e: DragEvent) {
|
function dropFile(e: DragEvent) {
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
import { cubicInOut } from "svelte/easing";
|
import { cubicInOut } from "svelte/easing";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
import type { FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { DocumentStore } from "@graphite/stores/document";
|
import type { DocumentStore } from "@graphite/stores/document";
|
||||||
import { closeContextMenu } from "@graphite/stores/node-graph";
|
import { closeContextMenu } from "@graphite/stores/node-graph";
|
||||||
import type { NodeGraphStore } from "@graphite/stores/node-graph";
|
import type { NodeGraphStore } from "@graphite/stores/node-graph";
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
const GRID_SIZE = 24;
|
const GRID_SIZE = 24;
|
||||||
const FADE_TRANSITION = { duration: 200, easing: cubicInOut };
|
const FADE_TRANSITION = { duration: 200, easing: cubicInOut };
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
||||||
const documentState = getContext<DocumentStore>("document");
|
const documentState = getContext<DocumentStore>("document");
|
||||||
|
|
||||||
@@ -83,7 +82,7 @@
|
|||||||
if (editingNameImportIndex !== undefined) {
|
if (editingNameImportIndex !== undefined) {
|
||||||
if (!(event.target instanceof HTMLInputElement)) return;
|
if (!(event.target instanceof HTMLInputElement)) return;
|
||||||
let text = event.target.value;
|
let text = event.target.value;
|
||||||
editor.handle.setImportName(editingNameImportIndex, text);
|
editor.setImportName(editingNameImportIndex, text);
|
||||||
editingNameImportIndex = undefined;
|
editingNameImportIndex = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,7 +91,7 @@
|
|||||||
if (editingNameExportIndex !== undefined) {
|
if (editingNameExportIndex !== undefined) {
|
||||||
if (!(event.target instanceof HTMLInputElement)) return;
|
if (!(event.target instanceof HTMLInputElement)) return;
|
||||||
let text = event.target.value;
|
let text = event.target.value;
|
||||||
editor.handle.setExportName(editingNameExportIndex, text);
|
editor.setExportName(editingNameExportIndex, text);
|
||||||
editingNameExportIndex = undefined;
|
editingNameExportIndex = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@
|
|||||||
function createNode(identifier: string) {
|
function createNode(identifier: string) {
|
||||||
if ($nodeGraph.contextMenuInformation === undefined) return;
|
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 {
|
function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, exposedSecondaryInputs: number, primaryOutputExists: boolean, exposedSecondaryOutputs: number): string {
|
||||||
@@ -174,11 +173,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function outputConnectedToText(output: FrontendGraphOutput): string {
|
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 {
|
function inputConnectedToText(input: FrontendGraphInput): string {
|
||||||
return editor.handle.inDevelopmentMode() ? input.connectedTo : "";
|
return editor.inDevelopmentMode() ? input.connectedTo : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
function zipWithUndefined(arr1: FrontendGraphInput[], arr2: FrontendGraphOutput[]) {
|
||||||
@@ -220,7 +219,7 @@
|
|||||||
<TextButton
|
<TextButton
|
||||||
label="Merge Selected Nodes"
|
label="Merge Selected Nodes"
|
||||||
action={() => {
|
action={() => {
|
||||||
editor.handle.mergeSelectedNodes();
|
editor.mergeSelectedNodes();
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
}}
|
}}
|
||||||
flush={true}
|
flush={true}
|
||||||
@@ -230,7 +229,7 @@
|
|||||||
label={currentlyIsNode ? "Display as Layer" : "Display as Node"}
|
label={currentlyIsNode ? "Display as Layer" : "Display as Node"}
|
||||||
action={() => {
|
action={() => {
|
||||||
if ($nodeGraph.contextMenuInformation?.contextMenuData.type === "ModifyNode") {
|
if ($nodeGraph.contextMenuInformation?.contextMenuData.type === "ModifyNode") {
|
||||||
editor.handle.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, currentlyIsNode);
|
editor.setToNodeOrLayer($nodeGraph.contextMenuInformation.contextMenuData.data.nodeId, currentlyIsNode);
|
||||||
}
|
}
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
}}
|
}}
|
||||||
@@ -244,9 +243,9 @@
|
|||||||
label={allLocked ? "Unlock" : "Lock"}
|
label={allLocked ? "Unlock" : "Lock"}
|
||||||
action={() => {
|
action={() => {
|
||||||
if ($nodeGraph.selected.includes(nodeId)) {
|
if ($nodeGraph.selected.includes(nodeId)) {
|
||||||
editor.handle.toggleSelectedLocked();
|
editor.toggleSelectedLocked();
|
||||||
} else {
|
} else {
|
||||||
editor.handle.toggleLayerLock(nodeId);
|
editor.toggleLayerLock(nodeId);
|
||||||
}
|
}
|
||||||
closeContextMenu();
|
closeContextMenu();
|
||||||
}}
|
}}
|
||||||
@@ -383,7 +382,7 @@
|
|||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24}
|
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24}
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -454,7 +453,7 @@
|
|||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -465,14 +464,14 @@
|
|||||||
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.importPosition[0] - 12) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.importPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.imports.length}
|
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>
|
||||||
<div
|
<div
|
||||||
class="plus"
|
class="plus"
|
||||||
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
style:--offset-left={($nodeGraph.updateImportsExports.exportPosition[0] - 12) / 24}
|
||||||
style:--offset-top={($nodeGraph.updateImportsExports.exportPosition[1] - 12) / 24 + $nodeGraph.updateImportsExports.exports.length}
|
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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -522,7 +521,7 @@
|
|||||||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||||
data-tooltip-label={nodeNameTooltipLabel(node)}
|
data-tooltip-label={nodeNameTooltipLabel(node)}
|
||||||
data-tooltip-description={`
|
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()}
|
`.trim()}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
>
|
>
|
||||||
@@ -685,7 +684,7 @@
|
|||||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
data-tooltip-label={nodeNameTooltipLabel(node)}
|
data-tooltip-label={nodeNameTooltipLabel(node)}
|
||||||
data-tooltip-description={`
|
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()}
|
`.trim()}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
import type { LayoutTarget, WidgetSection as WidgetSectionData } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, LayoutTarget, WidgetSection as WidgetSectionData } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||||
@@ -18,7 +17,7 @@
|
|||||||
|
|
||||||
let expanded = true;
|
let expanded = true;
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- TODO: Implement collapsable sections with properties system -->
|
<!-- 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."}
|
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}
|
size={24}
|
||||||
action={(e) => {
|
action={(e) => {
|
||||||
editor.handle.setNodePinned(widgetData.id, !widgetData.pinned);
|
editor.setNodePinned(widgetData.id, !widgetData.pinned);
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
}}
|
}}
|
||||||
class="show-only-on-hover"
|
class="show-only-on-hover"
|
||||||
@@ -41,7 +40,7 @@
|
|||||||
tooltipDescription="Delete this node from the layer chain."
|
tooltipDescription="Delete this node from the layer chain."
|
||||||
size={24}
|
size={24}
|
||||||
action={(e) => {
|
action={(e) => {
|
||||||
editor.handle.deleteNode(widgetData.id);
|
editor.deleteNode(widgetData.id);
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
}}
|
}}
|
||||||
class="show-only-on-hover"
|
class="show-only-on-hover"
|
||||||
@@ -52,7 +51,7 @@
|
|||||||
tooltipDescription={widgetData.visible ? "Hide this node." : "Show this node."}
|
tooltipDescription={widgetData.visible ? "Hide this node." : "Show this node."}
|
||||||
size={24}
|
size={24}
|
||||||
action={(e) => {
|
action={(e) => {
|
||||||
editor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);
|
editor.toggleNodeVisibilityLayerPanel(widgetData.id);
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
}}
|
}}
|
||||||
class={widgetData.visible ? "show-only-on-hover" : ""}
|
class={widgetData.visible ? "show-only-on-hover" : ""}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
import type { LayoutTarget, Widget, WidgetInstance } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, LayoutTarget, Widget, WidgetInstance } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import { parseFillChoice } from "@graphite/utility-functions/colors";
|
import { parseFillChoice } from "@graphite/utility-functions/colors";
|
||||||
|
|
||||||
import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte";
|
import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte";
|
||||||
@@ -35,7 +34,7 @@
|
|||||||
// A Widget tagged enum unwrapped into a correlated [kind, props] tuple
|
// A Widget tagged enum unwrapped into a correlated [kind, props] tuple
|
||||||
type UnwrappedWidget = { [K in WidgetKind]: [kind: K, props: WidgetProps<K>] }[WidgetKind];
|
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 widgets: WidgetInstance[];
|
||||||
export let direction: "row" | "column";
|
export let direction: "row" | "column";
|
||||||
@@ -52,15 +51,15 @@
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
function widgetValueCommit(widgetIndex: number, value: unknown) {
|
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) {
|
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) {
|
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.
|
// Extracts the kind and props from a Widget tagged enum, validated against the widget registry.
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
import { createEventDispatcher, onMount, onDestroy, getContext } from "svelte";
|
import { createEventDispatcher, onMount, onDestroy, getContext } from "svelte";
|
||||||
|
|
||||||
import { evaluateMathExpression, isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
import { evaluateMathExpression, isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { NumberInputMode, NumberInputIncrementBehavior, ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { ActionShortcut, EditorHandle, NumberInputIncrementBehavior, NumberInputMode } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS } from "@graphite/managers/input";
|
import { PRESS_REPEAT_DELAY_MS, PRESS_REPEAT_INTERVAL_MS } from "@graphite/managers/input";
|
||||||
import { browserVersion } from "@graphite/utility-functions/platform";
|
import { browserVersion } from "@graphite/utility-functions/platform";
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher<{ value: number | undefined; startHistoryTransaction: undefined }>();
|
const dispatch = createEventDispatcher<{ value: number | undefined; startHistoryTransaction: undefined }>();
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
/// When `value` is not provided (i.e. it's `undefined`), a dash is displayed.
|
/// When `value` is not provided (i.e. it's `undefined`), a dash is displayed.
|
||||||
@@ -408,7 +407,7 @@
|
|||||||
// Enter dragging state
|
// Enter dragging state
|
||||||
if (usePointerLock) target.requestPointerLock();
|
if (usePointerLock) target.requestPointerLock();
|
||||||
if (isPlatformNative()) {
|
if (isPlatformNative()) {
|
||||||
editor.handle.appWindowPointerLock();
|
editor.appWindowPointerLock();
|
||||||
}
|
}
|
||||||
initialValueBeforeDragging = value;
|
initialValueBeforeDragging = value;
|
||||||
cumulativeDragDelta = 0;
|
cumulativeDragDelta = 0;
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
import type { Color } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { Color, EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import { fillChoiceColor, colorToRgbaCSS } from "@graphite/utility-functions/colors";
|
import { fillChoiceColor, colorToRgbaCSS } from "@graphite/utility-functions/colors";
|
||||||
|
|
||||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
export let primary: Color;
|
export let primary: Color;
|
||||||
@@ -29,11 +28,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function primaryColorChanged(color: Color) {
|
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) {
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, tick } from "svelte";
|
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 LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
const BUTTON_LEFT = 0;
|
const BUTTON_LEFT = 0;
|
||||||
const BUTTON_MIDDLE = 1;
|
const BUTTON_MIDDLE = 1;
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
|
|
||||||
export let tabMinWidths = false;
|
export let tabMinWidths = false;
|
||||||
export let tabCloseButtons = false;
|
export let tabCloseButtons = false;
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</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-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||||
<LayoutRow class="tab-group" scrollableX={true} on:click={onEmptySpaceAction} on:auxclick={onEmptySpaceAction}>
|
<LayoutRow class="tab-group" scrollableX={true} on:click={onEmptySpaceAction} on:auxclick={onEmptySpaceAction}>
|
||||||
{#each tabLabels as tabLabel, tabIndex}
|
{#each tabLabels as tabLabel, tabIndex}
|
||||||
|
|||||||
@@ -2,32 +2,33 @@
|
|||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
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 { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||||
|
|
||||||
let statusBarHintsLayout: Layout = [];
|
let statusBarHintsLayout: Layout = [];
|
||||||
let statusBarInfoLayout: Layout = [];
|
let statusBarInfoLayout: Layout = [];
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("StatusBarHints", (data) => {
|
subscriptions.subscribeLayoutUpdate("StatusBarHints", (data) => {
|
||||||
patchLayout(statusBarHintsLayout, data);
|
patchLayout(statusBarHintsLayout, data);
|
||||||
statusBarHintsLayout = statusBarHintsLayout;
|
statusBarHintsLayout = statusBarHintsLayout;
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeLayoutUpdate("StatusBarInfo", (data) => {
|
|
||||||
|
subscriptions.subscribeLayoutUpdate("StatusBarInfo", (data) => {
|
||||||
patchLayout(statusBarInfoLayout, data);
|
patchLayout(statusBarInfoLayout, data);
|
||||||
statusBarInfoLayout = statusBarInfoLayout;
|
statusBarInfoLayout = statusBarInfoLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarHints");
|
subscriptions.unsubscribeLayoutUpdate("StatusBarHints");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarInfo");
|
subscriptions.unsubscribeLayoutUpdate("StatusBarInfo");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { AppWindowStore } from "@graphite/stores/app-window";
|
import type { AppWindowStore } from "@graphite/stores/app-window";
|
||||||
import { enterFullscreen, exitFullscreen } from "@graphite/stores/fullscreen";
|
import { enterFullscreen, exitFullscreen } from "@graphite/stores/fullscreen";
|
||||||
import type { FullscreenStore } from "@graphite/stores/fullscreen";
|
import type { FullscreenStore } from "@graphite/stores/fullscreen";
|
||||||
import type { TooltipStore } from "@graphite/stores/tooltip";
|
import type { TooltipStore } from "@graphite/stores/tooltip";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
@@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
const keyboardLockApiSupported = navigator.keyboard !== undefined && "lock" in navigator.keyboard;
|
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 appWindow = getContext<AppWindowStore>("appWindow");
|
||||||
const editor = getContext<Editor>("editor");
|
|
||||||
const fullscreen = getContext<FullscreenStore>("fullscreen");
|
const fullscreen = getContext<FullscreenStore>("fullscreen");
|
||||||
const tooltip = getContext<TooltipStore>("tooltip");
|
const tooltip = getContext<TooltipStore>("tooltip");
|
||||||
|
|
||||||
@@ -29,14 +30,14 @@
|
|||||||
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
|
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeLayoutUpdate("MenuBar", (data) => {
|
subscriptions.subscribeLayoutUpdate("MenuBar", (data) => {
|
||||||
patchLayout(menuBarLayout, data);
|
patchLayout(menuBarLayout, data);
|
||||||
menuBarLayout = menuBarLayout;
|
menuBarLayout = menuBarLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("MenuBar");
|
subscriptions.unsubscribeLayoutUpdate("MenuBar");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<!-- Window frame -->
|
<!-- 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 -->
|
<!-- Window buttons -->
|
||||||
<LayoutRow class="window-buttons" classes={{ fullscreen: showFullscreenButton, windows: $appWindow.platform === "Windows", linux: $appWindow.platform === "Linux" }}>
|
<LayoutRow class="window-buttons" classes={{ fullscreen: showFullscreenButton, windows: $appWindow.platform === "Windows", linux: $appWindow.platform === "Linux" }}>
|
||||||
{#if $appWindow.platform !== "Mac"}
|
{#if $appWindow.platform !== "Mac"}
|
||||||
@@ -60,20 +61,20 @@
|
|||||||
: undefined}
|
: undefined}
|
||||||
tooltipShortcut={$tooltip.fullscreenShortcut}
|
tooltipShortcut={$tooltip.fullscreenShortcut}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (isPlatformNative()) editor.handle.appWindowFullscreen();
|
if (isPlatformNative()) editor.appWindowFullscreen();
|
||||||
else ($fullscreen.windowFullscreen ? exitFullscreen : enterFullscreen)();
|
else ($fullscreen.windowFullscreen ? exitFullscreen : enterFullscreen)();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconLabel icon={isFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
<IconLabel icon={isFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{:else}
|
{:else}
|
||||||
<LayoutRow tooltipLabel="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
<LayoutRow tooltipLabel="Minimize" on:click={() => editor.appWindowMinimize()}>
|
||||||
<IconLabel icon="WindowButtonWinMinimize" />
|
<IconLabel icon="WindowButtonWinMinimize" />
|
||||||
</LayoutRow>
|
</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"} />
|
<IconLabel icon={$appWindow.maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow tooltipLabel="Close" on:click={() => editor.handle.appWindowClose()}>
|
<LayoutRow tooltipLabel="Close" on:click={() => editor.appWindowClose()}>
|
||||||
<IconLabel icon="WindowButtonWinClose" />
|
<IconLabel icon="WindowButtonWinClose" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onDestroy } from "svelte";
|
import { getContext, onDestroy } from "svelte";
|
||||||
|
|
||||||
import type { OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
@@ -35,13 +34,13 @@
|
|||||||
$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
|
$: documentTabLabels = $portfolio.documents.map((doc: OpenDocument) => {
|
||||||
const name = doc.details.name;
|
const name = doc.details.name;
|
||||||
const unsaved = !doc.details.isSaved;
|
const unsaved = !doc.details.isSaved;
|
||||||
if (!editor.handle.inDevelopmentMode()) return { name, unsaved };
|
if (!editor.inDevelopmentMode()) return { name, unsaved };
|
||||||
|
|
||||||
const tooltipDescription = `Document ID: ${doc.id}`;
|
const tooltipDescription = `Document ID: ${doc.id}`;
|
||||||
return { name, unsaved, tooltipLabel: name, tooltipDescription };
|
return { name, unsaved, tooltipLabel: name, tooltipDescription };
|
||||||
});
|
});
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<EditorHandle>("editor");
|
||||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||||
|
|
||||||
function resizePanel(e: PointerEvent) {
|
function resizePanel(e: PointerEvent) {
|
||||||
@@ -151,9 +150,9 @@
|
|||||||
tabCloseButtons={true}
|
tabCloseButtons={true}
|
||||||
tabMinWidths={true}
|
tabMinWidths={true}
|
||||||
tabLabels={documentTabLabels}
|
tabLabels={documentTabLabels}
|
||||||
emptySpaceAction={() => editor.handle.newDocumentDialog()}
|
emptySpaceAction={() => editor.newDocumentDialog()}
|
||||||
clickAction={(tabIndex) => editor.handle.selectDocument($portfolio.documents[tabIndex].id)}
|
clickAction={(tabIndex) => editor.selectDocument($portfolio.documents[tabIndex].id)}
|
||||||
closeAction={(tabIndex) => editor.handle.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
|
closeAction={(tabIndex) => editor.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
|
||||||
tabActiveIndex={$portfolio.activeDocumentIndex}
|
tabActiveIndex={$portfolio.activeDocumentIndex}
|
||||||
bind:this={documentPanel}
|
bind:this={documentPanel}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
// import { panicProxy } from "@graphite/utility-functions/panic-proxy";
|
|
||||||
|
|
||||||
import init, { EditorHandle, wasmMemory, receiveNativeMessage } from "@graphite/../wasm/pkg/graphite_wasm";
|
|
||||||
import type { FrontendMessage } from "@graphite/../wasm/pkg/graphite_wasm";
|
|
||||||
import { createSubscriptionRouter } from "@graphite/subscription-router";
|
|
||||||
import type { MessageName, SubscriptionRouter } from "@graphite/subscription-router";
|
|
||||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
|
||||||
|
|
||||||
// TODO: Remove `raw`, split out `subscriptions`, and unwrap the remaining `handle` so `EditorHandle` can replace `Editor` and then it can also be renamed to `Editor` to fully remove `EditorHandle`.
|
|
||||||
export type Editor = {
|
|
||||||
raw: WebAssembly.Memory;
|
|
||||||
handle: EditorHandle;
|
|
||||||
subscriptions: SubscriptionRouter;
|
|
||||||
destroy: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// `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;
|
|
||||||
|
|
||||||
// Should be called asynchronously before `createEditor()`.
|
|
||||||
export async function initWasm() {
|
|
||||||
// Skip if the Wasm module is already initialized
|
|
||||||
if (wasmImport !== undefined) return;
|
|
||||||
|
|
||||||
// Import the Wasm module JS bindings and wrap them in the panic proxy
|
|
||||||
const wasm = await init();
|
|
||||||
for (const [name, f] of Object.entries(wasm)) {
|
|
||||||
if (name.startsWith("__node_registry")) f();
|
|
||||||
}
|
|
||||||
|
|
||||||
wasmImport = await wasmMemory();
|
|
||||||
window.imageCanvases = {};
|
|
||||||
window.receiveNativeMessage = receiveNativeMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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: WebAssembly.Memory = wasmImport;
|
|
||||||
|
|
||||||
// Provide a random starter seed which must occur after initializing the Wasm module, since Wasm can't generate its own random numbers
|
|
||||||
const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
|
|
||||||
const randomSeed = BigInt(randomSeedFloat);
|
|
||||||
|
|
||||||
// Handle: object containing many functions from `editor_api.rs` that are part of the `EditorHandle` struct (generated by wasm-bindgen)
|
|
||||||
const handle = EditorHandle.create(operatingSystem(), randomSeed, (messageType: MessageName, messageData: FrontendMessage) => {
|
|
||||||
// This callback is called by Wasm when a FrontendMessage is received from the Wasm wrapper `EditorHandle`
|
|
||||||
subscriptions.handleFrontendMessage(messageType, messageData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subscriptions: allows subscribing to messages in JS that are sent from the Wasm backend
|
|
||||||
const subscriptions = createSubscriptionRouter();
|
|
||||||
|
|
||||||
// Check if the URL hash fragment has any demo artwork to be loaded
|
|
||||||
const demoArtworkAbortController = new AbortController();
|
|
||||||
(async () => {
|
|
||||||
const demoArtwork = window.location.hash.trim().match(/#demo\/(.*)/)?.[1];
|
|
||||||
if (!demoArtwork) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const url = new URL(`/demo-artwork/${demoArtwork}.${handle.fileExtension()}`, document.location.href);
|
|
||||||
const data = await fetch(url, { signal: demoArtworkAbortController.signal });
|
|
||||||
if (!data.ok) throw new Error();
|
|
||||||
|
|
||||||
const filename = url.pathname.split("/").pop() || "Untitled";
|
|
||||||
const content = await data.bytes();
|
|
||||||
handle.openFile(`${filename}.${handle.fileExtension()}`, content);
|
|
||||||
|
|
||||||
// Remove the hash fragment from the URL
|
|
||||||
history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
|
|
||||||
} catch {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
function destroy() {
|
|
||||||
handle.free();
|
|
||||||
demoArtworkAbortController.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return { raw, handle, subscriptions, destroy };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wasm state can't be hot-replaced, so we tell Vite to do a full page reload when this module changes
|
|
||||||
import.meta.hot?.accept(() => location.reload());
|
|
||||||
@@ -1,37 +1,40 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { insertAtCaret, readAtCaret } from "@graphite/utility-functions/clipboard";
|
import { insertAtCaret, readAtCaret } from "@graphite/utility-functions/clipboard";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editorHandle: EditorHandle | undefined = undefined;
|
||||||
|
|
||||||
export function createClipboardManager(editor: Editor) {
|
export function createClipboardManager(subscriptions: SubscriptionsRouter, editor: EditorHandle) {
|
||||||
destroyClipboardManager();
|
destroyClipboardManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
editorHandle = editor;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerClipboardWrite", (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerClipboardWrite", (data) => {
|
||||||
// If the Clipboard API is supported in the browser, copy text to the clipboard
|
// If the Clipboard API is supported in the browser, copy text to the clipboard
|
||||||
navigator.clipboard?.writeText?.(data.content);
|
navigator.clipboard?.writeText?.(data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSelectionRead", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSelectionRead", async (data) => {
|
||||||
editor.handle.readSelection(readAtCaret(data.cut), data.cut);
|
editor.readSelection(readAtCaret(data.cut), data.cut);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSelectionWrite", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSelectionWrite", async (data) => {
|
||||||
insertAtCaret(data.content);
|
insertAtCaret(data.content);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyClipboardManager() {
|
export function destroyClipboardManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerClipboardWrite");
|
subscriptions.unsubscribeFrontendMessage("TriggerClipboardWrite");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSelectionRead");
|
subscriptions.unsubscribeFrontendMessage("TriggerSelectionRead");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite");
|
subscriptions.unsubscribeFrontendMessage("TriggerSelectionWrite");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef) newModule?.createClipboardManager(editorRef);
|
if (subscriptionsRouter && editorHandle) newModule?.createClipboardManager(subscriptionsRouter, editorHandle);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
type ApiResponse = { family: string; variants: string[]; files: Record<string, string> }[];
|
type ApiResponse = { family: string; variants: string[]; files: Record<string, string> }[];
|
||||||
|
|
||||||
const FONT_LIST_API = "https://api.graphite.art/font-list";
|
const FONT_LIST_API = "https://api.graphite.art/font-list";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editorHandle: EditorHandle | undefined = undefined;
|
||||||
let abortController: AbortController | undefined = undefined;
|
let abortController: AbortController | undefined = undefined;
|
||||||
|
|
||||||
export function createFontsManager(editor: Editor) {
|
export function createFontsManager(subscriptions: SubscriptionsRouter, editor: EditorHandle) {
|
||||||
destroyFontsManager();
|
destroyFontsManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
editorHandle = editor;
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerFontCatalogLoad", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerFontCatalogLoad", async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(FONT_LIST_API, abortController ? { signal: abortController.signal } : undefined);
|
const response = await fetch(FONT_LIST_API, abortController ? { signal: abortController.signal } : undefined);
|
||||||
if (!response.ok) throw new Error(`Font catalog request failed with status ${response.status}`);
|
if (!response.ok) throw new Error(`Font catalog request failed with status ${response.status}`);
|
||||||
@@ -31,14 +34,14 @@ export function createFontsManager(editor: Editor) {
|
|||||||
return { name: font.family, styles };
|
return { name: font.family, styles };
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.handle.onFontCatalogLoad(catalog);
|
editor.onFontCatalogLoad(catalog);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DOMException && error.name === "AbortError") return;
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerFontDataLoad", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerFontDataLoad", async (data) => {
|
||||||
const { fontFamily, fontStyle } = data.font;
|
const { fontFamily, fontStyle } = data.font;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -48,7 +51,7 @@ export function createFontsManager(editor: Editor) {
|
|||||||
const buffer = await response.arrayBuffer();
|
const buffer = await response.arrayBuffer();
|
||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
|
|
||||||
editor.handle.onFontLoad(fontFamily, fontStyle, bytes);
|
editor.onFontLoad(fontFamily, fontStyle, bytes);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DOMException && error.name === "AbortError") return;
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -58,15 +61,15 @@ export function createFontsManager(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyFontsManager() {
|
export function destroyFontsManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
abortController?.abort();
|
abortController?.abort();
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerFontCatalogLoad");
|
subscriptions.unsubscribeFrontendMessage("TriggerFontCatalogLoad");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerFontDataLoad");
|
subscriptions.unsubscribeFrontendMessage("TriggerFontDataLoad");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef) newModule?.createFontsManager(editorRef);
|
if (subscriptionsRouter && editorHandle) newModule?.createFontsManager(subscriptionsRouter, editorHandle);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
export function createHyperlinkManager(editor: Editor) {
|
export function createHyperlinkManager(subscriptions: SubscriptionsRouter) {
|
||||||
destroyHyperlinkManager();
|
destroyHyperlinkManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerVisitLink", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerVisitLink", async (data) => {
|
||||||
window.open(data.url, "_blank", "noopener");
|
window.open(data.url, "_blank", "noopener");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyHyperlinkManager() {
|
export function destroyHyperlinkManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerVisitLink");
|
subscriptions.unsubscribeFrontendMessage("TriggerVisitLink");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef) newModule?.createHyperlinkManager(editorRef);
|
if (subscriptionsRouter) newModule?.createHyperlinkManager(subscriptionsRouter);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { DialogStore } from "@graphite/stores/dialog";
|
import type { DialogStore } from "@graphite/stores/dialog";
|
||||||
import type { DocumentStore } from "@graphite/stores/document";
|
import type { DocumentStore } from "@graphite/stores/document";
|
||||||
import { fullscreenModeChanged } from "@graphite/stores/fullscreen";
|
import { fullscreenModeChanged } from "@graphite/stores/fullscreen";
|
||||||
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { triggerClipboardRead } from "@graphite/utility-functions/clipboard";
|
import { triggerClipboardRead } from "@graphite/utility-functions/clipboard";
|
||||||
import {
|
import {
|
||||||
onBeforeUnload,
|
onBeforeUnload,
|
||||||
@@ -32,42 +33,44 @@ export const PRESS_REPEAT_DELAY_MS = 400;
|
|||||||
export const PRESS_REPEAT_INTERVAL_MS = 72;
|
export const PRESS_REPEAT_INTERVAL_MS = 72;
|
||||||
export const PRESS_REPEAT_INTERVAL_RAPID_MS = 10;
|
export const PRESS_REPEAT_INTERVAL_RAPID_MS = 10;
|
||||||
const listeners: Listener[] = [
|
const listeners: Listener[] = [
|
||||||
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => editorRef && portfolioStore && onBeforeUnload(e, editorRef, portfolioStore) },
|
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => editorHandle && portfolioStore && onBeforeUnload(e, editorHandle, portfolioStore) },
|
||||||
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => editorRef && dialogStore && onKeyUp(e, editorRef, dialogStore) },
|
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => editorHandle && dialogStore && onKeyUp(e, editorHandle, dialogStore) },
|
||||||
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => editorRef && dialogStore && onKeyDown(e, editorRef, dialogStore) },
|
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => editorHandle && dialogStore && onKeyDown(e, editorHandle, dialogStore) },
|
||||||
{ target: window, eventName: "pointermove", action: (e: PointerEvent) => editorRef && documentStore && onPointerMove(e, editorRef, documentStore) },
|
{ target: window, eventName: "pointermove", action: (e: PointerEvent) => editorHandle && documentStore && onPointerMove(e, editorHandle, documentStore) },
|
||||||
{ target: window, eventName: "pointerdown", action: (e: PointerEvent) => editorRef && dialogStore && onPointerDown(e, editorRef, dialogStore) },
|
{ target: window, eventName: "pointerdown", action: (e: PointerEvent) => editorHandle && dialogStore && onPointerDown(e, editorHandle, dialogStore) },
|
||||||
{ target: window, eventName: "pointerup", action: (e: PointerEvent) => editorRef && onPointerUp(e, editorRef) },
|
{ target: window, eventName: "pointerup", action: (e: PointerEvent) => editorHandle && onPointerUp(e, editorHandle) },
|
||||||
{ target: window, eventName: "mousedown", action: (e: MouseEvent) => onMouseDown(e) },
|
{ target: window, eventName: "mousedown", action: (e: MouseEvent) => onMouseDown(e) },
|
||||||
{ target: window, eventName: "mouseup", action: (e: MouseEvent) => editorRef && onPotentialDoubleClick(e, editorRef) },
|
{ target: window, eventName: "mouseup", action: (e: MouseEvent) => editorHandle && onPotentialDoubleClick(e, editorHandle) },
|
||||||
{ target: window, eventName: "wheel", action: (e: WheelEvent) => editorRef && onWheelScroll(e, editorRef), options: { passive: false } },
|
{ target: window, eventName: "wheel", action: (e: WheelEvent) => editorHandle && onWheelScroll(e, editorHandle), options: { passive: false } },
|
||||||
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) },
|
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) },
|
||||||
{ target: window, eventName: "focusout", action: () => onFocusOut() },
|
{ target: window, eventName: "focusout", action: () => onFocusOut() },
|
||||||
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) },
|
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) },
|
||||||
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreenModeChanged() },
|
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreenModeChanged() },
|
||||||
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => editorRef && onPaste(e, editorRef) },
|
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => editorHandle && onPaste(e, editorHandle) },
|
||||||
{ target: window.document, eventName: "pointerlockchange", action: onPointerLockChange },
|
{ target: window.document, eventName: "pointerlockchange", action: onPointerLockChange },
|
||||||
{ target: window.document, eventName: "pointerlockerror", action: onPointerLockChange },
|
{ target: window.document, eventName: "pointerlockerror", action: onPointerLockChange },
|
||||||
];
|
];
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editorHandle: EditorHandle | undefined = undefined;
|
||||||
let dialogStore: DialogStore | undefined = undefined;
|
let dialogStore: DialogStore | undefined = undefined;
|
||||||
let portfolioStore: PortfolioStore | undefined = undefined;
|
let portfolioStore: PortfolioStore | undefined = undefined;
|
||||||
let documentStore: DocumentStore | undefined = undefined;
|
let documentStore: DocumentStore | undefined = undefined;
|
||||||
|
|
||||||
export function createInputManager(editor: Editor, dialog: DialogStore, portfolio: PortfolioStore, doc: DocumentStore) {
|
export function createInputManager(subscriptions: SubscriptionsRouter, editor: EditorHandle, dialog: DialogStore, portfolio: PortfolioStore, doc: DocumentStore) {
|
||||||
destroyInputManager();
|
destroyInputManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
editorHandle = editor;
|
||||||
dialogStore = dialog;
|
dialogStore = dialog;
|
||||||
portfolioStore = portfolio;
|
portfolioStore = portfolio;
|
||||||
documentStore = doc;
|
documentStore = doc;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerClipboardRead", () => {
|
subscriptions.subscribeFrontendMessage("TriggerClipboardRead", () => {
|
||||||
triggerClipboardRead(editor);
|
triggerClipboardRead(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("WindowPointerLockMove", (data) => {
|
subscriptions.subscribeFrontendMessage("WindowPointerLockMove", (data) => {
|
||||||
// Desktop app only: dispatch custom pointer lock movement events
|
// Desktop app only: dispatch custom pointer lock movement events
|
||||||
const event = new CustomEvent("pointerlockmove", { detail: { x: data.position[0], y: data.position[1] } });
|
const event = new CustomEvent("pointerlockmove", { detail: { x: data.position[0], y: data.position[1] } });
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
@@ -83,11 +86,11 @@ export function createInputManager(editor: Editor, dialog: DialogStore, portfoli
|
|||||||
|
|
||||||
// Return the destructor
|
// Return the destructor
|
||||||
export function destroyInputManager() {
|
export function destroyInputManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerClipboardRead");
|
subscriptions.unsubscribeFrontendMessage("TriggerClipboardRead");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("WindowPointerLockMove");
|
subscriptions.unsubscribeFrontendMessage("WindowPointerLockMove");
|
||||||
|
|
||||||
// Remove event bindings after the lifetime of the application (or on hot-module replacement during development)
|
// Remove event bindings after the lifetime of the application (or on hot-module replacement during development)
|
||||||
listeners.forEach(({ target, eventName, action, options }) => target.removeEventListener(eventName, action, options));
|
listeners.forEach(({ target, eventName, action, options }) => target.removeEventListener(eventName, action, options));
|
||||||
@@ -95,5 +98,6 @@ export function destroyInputManager() {
|
|||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef && dialogStore && portfolioStore && documentStore) newModule?.createInputManager(editorRef, dialogStore, portfolioStore, documentStore);
|
if (subscriptionsRouter && editorHandle && dialogStore && portfolioStore && documentStore)
|
||||||
|
newModule?.createInputManager(subscriptionsRouter, editorHandle, dialogStore, portfolioStore, documentStore);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { localizeTimestamp } from "@graphite/utility-functions/time";
|
import { localizeTimestamp } from "@graphite/utility-functions/time";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editorHandle: EditorHandle | undefined = undefined;
|
||||||
|
|
||||||
export function createLocalizationManager(editor: Editor) {
|
export function createLocalizationManager(subscriptions: SubscriptionsRouter, editor: EditorHandle) {
|
||||||
destroyLocalizationManager();
|
destroyLocalizationManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
editorHandle = editor;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerAboutGraphiteLocalizedCommitDate", (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerAboutGraphiteLocalizedCommitDate", (data) => {
|
||||||
const localized = localizeTimestamp(data.commitDate);
|
const localized = localizeTimestamp(data.commitDate);
|
||||||
editor.handle.requestAboutGraphiteDialogWithLocalizedCommitDate(localized.timestamp, localized.year);
|
editor.requestAboutGraphiteDialogWithLocalizedCommitDate(localized.timestamp, localized.year);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyLocalizationManager() {
|
export function destroyLocalizationManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerAboutGraphiteLocalizedCommitDate");
|
subscriptions.unsubscribeFrontendMessage("TriggerAboutGraphiteLocalizedCommitDate");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef) newModule?.createLocalizationManager(editorRef);
|
if (subscriptionsRouter && editorHandle) newModule?.createLocalizationManager(subscriptionsRouter, editorHandle);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import { createCrashDialog } from "@graphite/stores/dialog";
|
import { createCrashDialog } from "@graphite/stores/dialog";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
export function createPanicManager(editor: Editor) {
|
export function createPanicManager(subscriptions: SubscriptionsRouter) {
|
||||||
destroyPanicManager();
|
destroyPanicManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayDialogPanic", (data) => {
|
subscriptions.subscribeFrontendMessage("DisplayDialogPanic", (data) => {
|
||||||
// `Error.stackTraceLimit` is only available in V8/Chromium
|
// `Error.stackTraceLimit` is only available in V8/Chromium
|
||||||
const previousStackTraceLimit = Error.stackTraceLimit;
|
const previousStackTraceLimit = Error.stackTraceLimit;
|
||||||
Error.stackTraceLimit = Infinity;
|
Error.stackTraceLimit = Infinity;
|
||||||
@@ -25,13 +25,13 @@ export function createPanicManager(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyPanicManager() {
|
export function destroyPanicManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayDialogPanic");
|
subscriptions.unsubscribeFrontendMessage("DisplayDialogPanic");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef) newModule?.createPanicManager(editorRef);
|
if (subscriptionsRouter) newModule?.createPanicManager(subscriptionsRouter);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,64 +1,67 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { saveEditorPreferences, loadEditorPreferences, storeDocument, removeDocument, loadFirstDocument, loadRestDocuments, saveActiveDocument } from "@graphite/utility-functions/persistence";
|
import { saveEditorPreferences, loadEditorPreferences, storeDocument, removeDocument, loadFirstDocument, loadRestDocuments, saveActiveDocument } from "@graphite/utility-functions/persistence";
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
let editorHandle: EditorHandle | undefined = undefined;
|
||||||
let portfolioStore: PortfolioStore | undefined = undefined;
|
let portfolioStore: PortfolioStore | undefined = undefined;
|
||||||
|
|
||||||
export function createPersistenceManager(editor: Editor, portfolio: PortfolioStore) {
|
export function createPersistenceManager(subscriptions: SubscriptionsRouter, editor: EditorHandle, portfolio: PortfolioStore) {
|
||||||
destroyPersistenceManager();
|
destroyPersistenceManager();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
editorHandle = editor;
|
||||||
portfolioStore = portfolio;
|
portfolioStore = portfolio;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSavePreferences", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSavePreferences", async (data) => {
|
||||||
await saveEditorPreferences(data.preferences);
|
await saveEditorPreferences(data.preferences);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerLoadPreferences", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerLoadPreferences", async () => {
|
||||||
await loadEditorPreferences(editor);
|
await loadEditorPreferences(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerPersistenceWriteDocument", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerPersistenceWriteDocument", async (data) => {
|
||||||
await storeDocument(data, portfolio);
|
await storeDocument(data, portfolio);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerPersistenceRemoveDocument", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerPersistenceRemoveDocument", async (data) => {
|
||||||
await removeDocument(String(data.documentId), portfolio);
|
await removeDocument(String(data.documentId), portfolio);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument", async () => {
|
||||||
await loadFirstDocument(editor);
|
await loadFirstDocument(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments", async () => {
|
||||||
await loadRestDocuments(editor);
|
await loadRestDocuments(editor);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerOpenLaunchDocuments", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerOpenLaunchDocuments", async () => {
|
||||||
// TODO: Could be used to load documents from URL params or similar on launch
|
// TODO: Could be used to load documents from URL params or similar on launch
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSaveActiveDocument", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSaveActiveDocument", async (data) => {
|
||||||
await saveActiveDocument(data.documentId);
|
await saveActiveDocument(data.documentId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyPersistenceManager() {
|
export function destroyPersistenceManager() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSavePreferences");
|
subscriptions.unsubscribeFrontendMessage("TriggerSavePreferences");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerLoadPreferences");
|
subscriptions.unsubscribeFrontendMessage("TriggerLoadPreferences");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerPersistenceWriteDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerPersistenceWriteDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerPersistenceRemoveDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerPersistenceRemoveDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerLoadFirstAutoSaveDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments");
|
subscriptions.unsubscribeFrontendMessage("TriggerLoadRestAutoSaveDocuments");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerOpenLaunchDocuments");
|
subscriptions.unsubscribeFrontendMessage("TriggerOpenLaunchDocuments");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSaveActiveDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerSaveActiveDocument");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
|
||||||
import.meta.hot?.accept((newModule) => {
|
import.meta.hot?.accept((newModule) => {
|
||||||
if (editorRef && portfolioStore) newModule?.createPersistenceManager(editorRef, portfolioStore);
|
if (subscriptionsRouter && editorHandle && portfolioStore) newModule?.createPersistenceManager(subscriptionsRouter, editorHandle, portfolioStore);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { writable } from "svelte/store";
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { AppWindowPlatform } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { AppWindowPlatform } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
export type AppWindowStore = ReturnType<typeof createAppWindowStore>;
|
export type AppWindowStore = ReturnType<typeof createAppWindowStore>;
|
||||||
|
|
||||||
@@ -21,47 +21,47 @@ const initialState: AppWindowStoreState = {
|
|||||||
uiScale: 1,
|
uiScale: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<AppWindowStoreState> = import.meta.hot?.data?.store || writable<AppWindowStoreState>(initialState);
|
const store: Writable<AppWindowStoreState> = import.meta.hot?.data?.store || writable<AppWindowStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createAppWindowStore(editor: Editor) {
|
export function createAppWindowStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyAppWindowStore();
|
destroyAppWindowStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdatePlatform", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdatePlatform", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.platform = data.platform;
|
state.platform = data.platform;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateMaximized", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateMaximized", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.maximized = data.maximized;
|
state.maximized = data.maximized;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateFullscreen", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateFullscreen", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.fullscreen = data.fullscreen;
|
state.fullscreen = data.fullscreen;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateViewportHolePunch", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateViewportHolePunch", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.viewportHolePunch = data.active;
|
state.viewportHolePunch = data.active;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateUIScale", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateUIScale", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.uiScale = data.scale;
|
state.uiScale = data.scale;
|
||||||
return state;
|
return state;
|
||||||
@@ -72,12 +72,12 @@ export function createAppWindowStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyAppWindowStore() {
|
export function destroyAppWindowStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdatePlatform");
|
subscriptions.unsubscribeFrontendMessage("UpdatePlatform");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateMaximized");
|
subscriptions.unsubscribeFrontendMessage("UpdateMaximized");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateFullscreen");
|
subscriptions.unsubscribeFrontendMessage("UpdateFullscreen");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateViewportHolePunch");
|
subscriptions.unsubscribeFrontendMessage("UpdateViewportHolePunch");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateUIScale");
|
subscriptions.unsubscribeFrontendMessage("UpdateUIScale");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { tick } from "svelte";
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
|
||||||
import type { IconName } from "@graphite/icons";
|
import type { IconName } from "@graphite/icons";
|
||||||
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
export type DialogStore = ReturnType<typeof createDialogStore>;
|
export type DialogStore = ReturnType<typeof createDialogStore>;
|
||||||
@@ -29,19 +29,19 @@ const initialState: DialogStoreState = {
|
|||||||
panicDetails: "",
|
panicDetails: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<DialogStoreState> = import.meta.hot?.data?.store || writable<DialogStoreState>(initialState);
|
const store: Writable<DialogStoreState> = import.meta.hot?.data?.store || writable<DialogStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createDialogStore(editor: Editor) {
|
export function createDialogStore(subscriptions: SubscriptionsRouter, editor: EditorHandle) {
|
||||||
destroyDialogStore();
|
destroyDialogStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("DisplayDialog", (data) => {
|
subscriptions.subscribeFrontendMessage("DisplayDialog", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.visible = true;
|
state.visible = true;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export function createDialogStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("DialogButtons", async (data) => {
|
subscriptions.subscribeLayoutUpdate("DialogButtons", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -62,7 +62,7 @@ export function createDialogStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("DialogColumn1", async (data) => {
|
subscriptions.subscribeLayoutUpdate("DialogColumn1", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -72,7 +72,7 @@ export function createDialogStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("DialogColumn2", async (data) => {
|
subscriptions.subscribeLayoutUpdate("DialogColumn2", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -82,7 +82,7 @@ export function createDialogStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("DialogClose", () => {
|
subscriptions.subscribeFrontendMessage("DialogClose", () => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
// Disallow dismissing the crash dialog since it should remain as the final notification
|
// Disallow dismissing the crash dialog since it should remain as the final notification
|
||||||
if (state.panicDetails === "") state.visible = false;
|
if (state.panicDetails === "") state.visible = false;
|
||||||
@@ -91,7 +91,7 @@ export function createDialogStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerDisplayThirdPartyLicensesDialog", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerDisplayThirdPartyLicensesDialog", async () => {
|
||||||
const BACKUP_URL = "https://editor.graphite.art/third-party-licenses.txt";
|
const BACKUP_URL = "https://editor.graphite.art/third-party-licenses.txt";
|
||||||
let licenseText = `Content was not able to load. Please check your network connection and try again.\n\nOr visit ${BACKUP_URL} for the license notices.`;
|
let licenseText = `Content was not able to load. Please check your network connection and try again.\n\nOr visit ${BACKUP_URL} for the license notices.`;
|
||||||
|
|
||||||
@@ -102,22 +102,22 @@ export function createDialogStore(editor: Editor) {
|
|||||||
// Do nothing on network error
|
// Do nothing on network error
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.handle.requestLicensesThirdPartyDialogWithLicenseText(licenseText);
|
editor.requestLicensesThirdPartyDialogWithLicenseText(licenseText);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { subscribe };
|
return { subscribe };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function destroyDialogStore() {
|
export function destroyDialogStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DisplayDialog");
|
subscriptions.unsubscribeFrontendMessage("DisplayDialog");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("DialogClose");
|
subscriptions.unsubscribeFrontendMessage("DialogClose");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerDisplayThirdPartyLicensesDialog");
|
subscriptions.unsubscribeFrontendMessage("TriggerDisplayThirdPartyLicensesDialog");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("DialogButtons");
|
subscriptions.unsubscribeLayoutUpdate("DialogButtons");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("DialogColumn1");
|
subscriptions.unsubscribeLayoutUpdate("DialogColumn1");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("DialogColumn2");
|
subscriptions.unsubscribeLayoutUpdate("DialogColumn2");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a crash dialog from JS once the editor has panicked.
|
// Creates a crash dialog from JS once the editor has panicked.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { writable } from "svelte/store";
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
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 { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
export type DocumentStore = ReturnType<typeof createDocumentStore>;
|
export type DocumentStore = ReturnType<typeof createDocumentStore>;
|
||||||
@@ -27,26 +27,26 @@ const initialState: DocumentStoreState = {
|
|||||||
fadeArtwork: 100,
|
fadeArtwork: 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<DocumentStoreState> = import.meta.hot?.data?.store || writable<DocumentStoreState>(initialState);
|
const store: Writable<DocumentStoreState> = import.meta.hot?.data?.store || writable<DocumentStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createDocumentStore(editor: Editor) {
|
export function createDocumentStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyDocumentStore();
|
destroyDocumentStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateGraphFadeArtwork", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateGraphFadeArtwork", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.fadeArtwork = data.percentage;
|
state.fadeArtwork = data.percentage;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("ToolOptions", async (data) => {
|
subscriptions.subscribeLayoutUpdate("ToolOptions", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -55,7 +55,7 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("DocumentBar", async (data) => {
|
subscriptions.subscribeLayoutUpdate("DocumentBar", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -64,7 +64,7 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("ToolShelf", async (data) => {
|
subscriptions.subscribeLayoutUpdate("ToolShelf", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -73,7 +73,7 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("WorkingColors", async (data) => {
|
subscriptions.subscribeLayoutUpdate("WorkingColors", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -82,7 +82,7 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeLayoutUpdate("NodeGraphControlBar", async (data) => {
|
subscriptions.subscribeLayoutUpdate("NodeGraphControlBar", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
update((state) => {
|
update((state) => {
|
||||||
@@ -91,7 +91,7 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateGraphViewOverlay", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateGraphViewOverlay", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.graphViewOverlayOpen = data.open;
|
state.graphViewOverlayOpen = data.open;
|
||||||
return state;
|
return state;
|
||||||
@@ -102,14 +102,14 @@ export function createDocumentStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyDocumentStore() {
|
export function destroyDocumentStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateGraphFadeArtwork");
|
subscriptions.unsubscribeFrontendMessage("UpdateGraphFadeArtwork");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateGraphViewOverlay");
|
subscriptions.unsubscribeFrontendMessage("UpdateGraphViewOverlay");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("ToolOptions");
|
subscriptions.unsubscribeLayoutUpdate("ToolOptions");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("DocumentBar");
|
subscriptions.unsubscribeLayoutUpdate("DocumentBar");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("ToolShelf");
|
subscriptions.unsubscribeLayoutUpdate("ToolShelf");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("WorkingColors");
|
subscriptions.unsubscribeLayoutUpdate("WorkingColors");
|
||||||
editor.subscriptions.unsubscribeLayoutUpdate("NodeGraphControlBar");
|
subscriptions.unsubscribeLayoutUpdate("NodeGraphControlBar");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
|
|
||||||
export type FullscreenStore = ReturnType<typeof createFullscreenStore>;
|
export type FullscreenStore = ReturnType<typeof createFullscreenStore>;
|
||||||
|
|
||||||
@@ -14,19 +14,19 @@ const initialState: FullscreenStoreState = {
|
|||||||
keyboardLocked: false,
|
keyboardLocked: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<FullscreenStoreState> = import.meta.hot?.data?.store || writable<FullscreenStoreState>(initialState);
|
const store: Writable<FullscreenStoreState> = import.meta.hot?.data?.store || writable<FullscreenStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createFullscreenStore(editor: Editor) {
|
export function createFullscreenStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyFullscreenStore();
|
destroyFullscreenStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("WindowFullscreen", () => {
|
subscriptions.subscribeFrontendMessage("WindowFullscreen", () => {
|
||||||
toggleFullscreen();
|
toggleFullscreen();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@ export function createFullscreenStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyFullscreenStore() {
|
export function destroyFullscreenStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("WindowFullscreen");
|
subscriptions.unsubscribeFrontendMessage("WindowFullscreen");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fullscreenModeChanged() {
|
export function fullscreenModeChanged() {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { writable } from "svelte/store";
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { NodeGraphErrorDiagnostic, BoxSelection, FrontendClickTargets, ContextMenuInformation, FrontendNode, FrontendNodeType, WirePath } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { NodeGraphErrorDiagnostic, BoxSelection, FrontendClickTargets, ContextMenuInformation, FrontendNode, FrontendNodeType, WirePath } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import type { MessageBody } from "@graphite/subscription-router";
|
import type { MessageBody } from "/src/subscriptions-router";
|
||||||
|
|
||||||
export type NodeGraphStore = ReturnType<typeof createNodeGraphStore>;
|
export type NodeGraphStore = ReturnType<typeof createNodeGraphStore>;
|
||||||
|
|
||||||
@@ -53,19 +53,19 @@ const initialState: NodeGraphStoreState = {
|
|||||||
reorderExportIndex: undefined,
|
reorderExportIndex: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<NodeGraphStoreState> = import.meta.hot?.data?.store || writable<NodeGraphStoreState>(initialState);
|
const store: Writable<NodeGraphStoreState> = import.meta.hot?.data?.store || writable<NodeGraphStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createNodeGraphStore(editor: Editor) {
|
export function createNodeGraphStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyNodeGraphStore();
|
destroyNodeGraphStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("SendUIMetadata", (data) => {
|
subscriptions.subscribeFrontendMessage("SendUIMetadata", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.nodeDescriptions = new Map(data.nodeDescriptions);
|
state.nodeDescriptions = new Map(data.nodeDescriptions);
|
||||||
state.nodeTypes = data.nodeTypes;
|
state.nodeTypes = data.nodeTypes;
|
||||||
@@ -73,56 +73,56 @@ export function createNodeGraphStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateBox", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateBox", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.box = data.box;
|
state.box = data.box;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateClickTargets", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateClickTargets", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.clickTargets = data.clickTargets;
|
state.clickTargets = data.clickTargets;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateContextMenuInformation", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateContextMenuInformation", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.contextMenuInformation = data.contextMenuInformation;
|
state.contextMenuInformation = data.contextMenuInformation;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateImportReorderIndex", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateImportReorderIndex", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.reorderImportIndex = data.importIndex;
|
state.reorderImportIndex = data.importIndex;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateExportReorderIndex", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateExportReorderIndex", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.reorderExportIndex = data.exportIndex;
|
state.reorderExportIndex = data.exportIndex;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateImportsExports", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateImportsExports", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.updateImportsExports = data;
|
state.updateImportsExports = data;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateInSelectedNetwork", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateInSelectedNetwork", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.inSelectedNetwork = data.inSelectedNetwork;
|
state.inSelectedNetwork = data.inSelectedNetwork;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateLayerWidths", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateLayerWidths", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.layerWidths = data.layerWidths;
|
state.layerWidths = data.layerWidths;
|
||||||
state.chainWidths = data.chainWidths;
|
state.chainWidths = data.chainWidths;
|
||||||
@@ -131,7 +131,7 @@ export function createNodeGraphStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphNodes", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphNodes", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.nodes.clear();
|
state.nodes.clear();
|
||||||
data.nodes.forEach((node) => {
|
data.nodes.forEach((node) => {
|
||||||
@@ -141,21 +141,21 @@ export function createNodeGraphStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphErrorDiagnostic", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphErrorDiagnostic", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.error = data.error;
|
state.error = data.error;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateVisibleNodes", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateVisibleNodes", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.visibleNodes = new Set<bigint>(data.nodes);
|
state.visibleNodes = new Set<bigint>(data.nodes);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphWires", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphWires", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
data.wires.forEach((wireUpdate) => {
|
data.wires.forEach((wireUpdate) => {
|
||||||
let inputMap = state.wires.get(wireUpdate.id);
|
let inputMap = state.wires.get(wireUpdate.id);
|
||||||
@@ -174,35 +174,35 @@ export function createNodeGraphStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("ClearAllNodeGraphWires", () => {
|
subscriptions.subscribeFrontendMessage("ClearAllNodeGraphWires", () => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.wires.clear();
|
state.wires.clear();
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphSelection", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphSelection", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.selected = data.selected;
|
state.selected = data.selected;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphTransform", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeGraphTransform", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.transform = { scale: data.scale, x: data.translation[0], y: data.translation[1] };
|
state.transform = { scale: data.scale, x: data.translation[0], y: data.translation[1] };
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateNodeThumbnail", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateNodeThumbnail", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.thumbnails.set(data.id, data.value);
|
state.thumbnails.set(data.id, data.value);
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateWirePathInProgress", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateWirePathInProgress", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.wirePathInProgress = data.wirePath;
|
state.wirePathInProgress = data.wirePath;
|
||||||
return state;
|
return state;
|
||||||
@@ -213,27 +213,27 @@ export function createNodeGraphStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyNodeGraphStore() {
|
export function destroyNodeGraphStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("SendUIMetadata");
|
subscriptions.unsubscribeFrontendMessage("SendUIMetadata");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateBox");
|
subscriptions.unsubscribeFrontendMessage("UpdateBox");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateClickTargets");
|
subscriptions.unsubscribeFrontendMessage("UpdateClickTargets");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateContextMenuInformation");
|
subscriptions.unsubscribeFrontendMessage("UpdateContextMenuInformation");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateImportReorderIndex");
|
subscriptions.unsubscribeFrontendMessage("UpdateImportReorderIndex");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateExportReorderIndex");
|
subscriptions.unsubscribeFrontendMessage("UpdateExportReorderIndex");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateImportsExports");
|
subscriptions.unsubscribeFrontendMessage("UpdateImportsExports");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateInSelectedNetwork");
|
subscriptions.unsubscribeFrontendMessage("UpdateInSelectedNetwork");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateLayerWidths");
|
subscriptions.unsubscribeFrontendMessage("UpdateLayerWidths");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphNodes");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphNodes");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphErrorDiagnostic");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphErrorDiagnostic");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateVisibleNodes");
|
subscriptions.unsubscribeFrontendMessage("UpdateVisibleNodes");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphWires");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphWires");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("ClearAllNodeGraphWires");
|
subscriptions.unsubscribeFrontendMessage("ClearAllNodeGraphWires");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphSelection");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphSelection");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphTransform");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeGraphTransform");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateNodeThumbnail");
|
subscriptions.unsubscribeFrontendMessage("UpdateNodeThumbnail");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateWirePathInProgress");
|
subscriptions.unsubscribeFrontendMessage("UpdateWirePathInProgress");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeContextMenu() {
|
export function closeContextMenu() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { EditorHandle, OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
||||||
import { rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
import { rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
||||||
|
|
||||||
@@ -25,26 +25,26 @@ const initialState: PortfolioStoreState = {
|
|||||||
layersPanelOpen: true,
|
layersPanelOpen: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
const store: Writable<PortfolioStoreState> = import.meta.hot?.data?.store || writable<PortfolioStoreState>(initialState);
|
const store: Writable<PortfolioStoreState> = import.meta.hot?.data?.store || writable<PortfolioStoreState>(initialState);
|
||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createPortfolioStore(editor: Editor) {
|
export function createPortfolioStore(subscriptions: SubscriptionsRouter, editor: EditorHandle) {
|
||||||
destroyPortfolioStore();
|
destroyPortfolioStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateOpenDocumentsList", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateOpenDocumentsList", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.documents = data.openDocuments;
|
state.documents = data.openDocuments;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateActiveDocument", (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateActiveDocument", (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
// Assume we receive a correct document id
|
// Assume we receive a correct document id
|
||||||
const activeId = state.documents.findIndex((doc) => doc.id === data.documentId);
|
const activeId = state.documents.findIndex((doc) => doc.id === data.documentId);
|
||||||
@@ -53,39 +53,39 @@ export function createPortfolioStore(editor: Editor) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerFetchAndOpenDocument", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerFetchAndOpenDocument", async (data) => {
|
||||||
try {
|
try {
|
||||||
const url = new URL(`demo-artwork/${data.filename}`, document.location.href);
|
const url = new URL(`demo-artwork/${data.filename}`, document.location.href);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
editor.handle.openFile(data.filename, await response.bytes());
|
editor.openFile(data.filename, await response.bytes());
|
||||||
} catch {
|
} 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
|
// 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(() => {
|
setTimeout(() => {
|
||||||
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.");
|
editor.errorDialog("Failed to open document", "The file could not be reached over the internet. You may be offline, or it may be missing.");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerOpen", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerOpen", async () => {
|
||||||
const data = await upload(`image/*,.${editor.handle.fileExtension()}`, "data");
|
const data = await upload(`image/*,.${editor.fileExtension()}`, "data");
|
||||||
editor.handle.openFile(data.filename, data.content);
|
editor.openFile(data.filename, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerImport", async () => {
|
subscriptions.subscribeFrontendMessage("TriggerImport", async () => {
|
||||||
// TODO: Use the same `accept` string as in the `TriggerOpen` handler once importing Graphite documents as nodes is supported
|
// TODO: Use the same `accept` string as in the `TriggerOpen` handler once importing Graphite documents as nodes is supported
|
||||||
const data = await upload("image/*", "data");
|
const data = await upload("image/*", "data");
|
||||||
editor.handle.importFile(data.filename, data.content);
|
editor.importFile(data.filename, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSaveDocument", (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSaveDocument", (data) => {
|
||||||
downloadFile(data.name, data.content);
|
downloadFile(data.name, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerSaveFile", (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerSaveFile", (data) => {
|
||||||
downloadFile(data.name, data.content);
|
downloadFile(data.name, data.content);
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("TriggerExportImage", async (data) => {
|
subscriptions.subscribeFrontendMessage("TriggerExportImage", async (data) => {
|
||||||
const { svg, name, mime, size } = data;
|
const { svg, name, mime, size } = data;
|
||||||
|
|
||||||
// Fill the canvas with white if it'll be a JPEG (which does not support transparency and defaults to black)
|
// Fill the canvas with white if it'll be a JPEG (which does not support transparency and defaults to black)
|
||||||
@@ -102,21 +102,21 @@ export function createPortfolioStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateDataPanelState", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateDataPanelState", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.dataPanelOpen = data.open;
|
state.dataPanelOpen = data.open;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdatePropertiesPanelState", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdatePropertiesPanelState", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.propertiesPanelOpen = data.open;
|
state.propertiesPanelOpen = data.open;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateLayersPanelState", async (data) => {
|
subscriptions.subscribeFrontendMessage("UpdateLayersPanelState", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.layersPanelOpen = data.open;
|
state.layersPanelOpen = data.open;
|
||||||
return state;
|
return state;
|
||||||
@@ -127,18 +127,18 @@ export function createPortfolioStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyPortfolioStore() {
|
export function destroyPortfolioStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateOpenDocumentsList");
|
subscriptions.unsubscribeFrontendMessage("UpdateOpenDocumentsList");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateActiveDocument");
|
subscriptions.unsubscribeFrontendMessage("UpdateActiveDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerFetchAndOpenDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerFetchAndOpenDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerOpen");
|
subscriptions.unsubscribeFrontendMessage("TriggerOpen");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerImport");
|
subscriptions.unsubscribeFrontendMessage("TriggerImport");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSaveDocument");
|
subscriptions.unsubscribeFrontendMessage("TriggerSaveDocument");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerSaveFile");
|
subscriptions.unsubscribeFrontendMessage("TriggerSaveFile");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("TriggerExportImage");
|
subscriptions.unsubscribeFrontendMessage("TriggerExportImage");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateDataPanelState");
|
subscriptions.unsubscribeFrontendMessage("UpdateDataPanelState");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdatePropertiesPanelState");
|
subscriptions.unsubscribeFrontendMessage("UpdatePropertiesPanelState");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("UpdateLayersPanelState");
|
subscriptions.unsubscribeFrontendMessage("UpdateLayersPanelState");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { writable } from "svelte/store";
|
|||||||
import type { Writable } from "svelte/store";
|
import type { Writable } from "svelte/store";
|
||||||
|
|
||||||
import type { ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
import type { ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||||
|
|
||||||
export type TooltipStore = ReturnType<typeof createTooltipStore>;
|
export type TooltipStore = ReturnType<typeof createTooltipStore>;
|
||||||
@@ -36,7 +36,7 @@ const tooltipEventListeners: Listener[] = [
|
|||||||
{ eventName: "wheel", action: closeTooltip },
|
{ eventName: "wheel", action: closeTooltip },
|
||||||
];
|
];
|
||||||
|
|
||||||
let editorRef: Editor | undefined = undefined;
|
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||||
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
let tooltipTimeout: ReturnType<typeof setTimeout> | undefined = undefined;
|
||||||
|
|
||||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||||
@@ -44,26 +44,26 @@ const store: Writable<TooltipStoreState> = import.meta.hot?.data?.store || writa
|
|||||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||||
const { subscribe, update } = store;
|
const { subscribe, update } = store;
|
||||||
|
|
||||||
export function createTooltipStore(editor: Editor) {
|
export function createTooltipStore(subscriptions: SubscriptionsRouter) {
|
||||||
destroyTooltipStore();
|
destroyTooltipStore();
|
||||||
|
|
||||||
editorRef = editor;
|
subscriptionsRouter = subscriptions;
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("SendShortcutShiftClick", async (data) => {
|
subscriptions.subscribeFrontendMessage("SendShortcutShiftClick", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.shiftClickShortcut = data.shortcut;
|
state.shiftClickShortcut = data.shortcut;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("SendShortcutAltClick", async (data) => {
|
subscriptions.subscribeFrontendMessage("SendShortcutAltClick", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.altClickShortcut = data.shortcut;
|
state.altClickShortcut = data.shortcut;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.subscriptions.subscribeFrontendMessage("SendShortcutFullscreen", async (data) => {
|
subscriptions.subscribeFrontendMessage("SendShortcutFullscreen", async (data) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.fullscreenShortcut = operatingSystem() === "Mac" ? data.shortcutMac : data.shortcut;
|
state.fullscreenShortcut = operatingSystem() === "Mac" ? data.shortcutMac : data.shortcut;
|
||||||
return state;
|
return state;
|
||||||
@@ -76,14 +76,14 @@ export function createTooltipStore(editor: Editor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function destroyTooltipStore() {
|
export function destroyTooltipStore() {
|
||||||
const editor = editorRef;
|
const subscriptions = subscriptionsRouter;
|
||||||
if (!editor) return;
|
if (!subscriptions) return;
|
||||||
|
|
||||||
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
if (tooltipTimeout) clearTimeout(tooltipTimeout);
|
||||||
|
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("SendShortcutShiftClick");
|
subscriptions.unsubscribeFrontendMessage("SendShortcutShiftClick");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("SendShortcutAltClick");
|
subscriptions.unsubscribeFrontendMessage("SendShortcutAltClick");
|
||||||
editor.subscriptions.unsubscribeFrontendMessage("SendShortcutFullscreen");
|
subscriptions.unsubscribeFrontendMessage("SendShortcutFullscreen");
|
||||||
|
|
||||||
tooltipEventListeners.forEach(({ eventName, action }) => document.removeEventListener(eventName, action));
|
tooltipEventListeners.forEach(({ eventName, action }) => document.removeEventListener(eventName, action));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export type MessageMap = ToMessageMap<FrontendMessage>;
|
|||||||
export type MessageName = keyof MessageMap;
|
export type MessageName = keyof MessageMap;
|
||||||
export type MessageBody<T extends MessageName> = Extract<FrontendMessage, Record<T, unknown>>[T];
|
export type MessageBody<T extends MessageName> = Extract<FrontendMessage, Record<T, unknown>>[T];
|
||||||
|
|
||||||
export function createSubscriptionRouter() {
|
export function createSubscriptionsRouter() {
|
||||||
// Callbacks are wrapped at subscription time to capture their type-specific data extraction in a closure,
|
// Callbacks are wrapped at subscription time to capture their type-specific data extraction in a closure,
|
||||||
// so the stored function has a uniform signature and the map doesn't need per-key generic value types.
|
// so the stored function has a uniform signature and the map doesn't need per-key generic value types.
|
||||||
const subscriptions = new Map<MessageName, (taggedMessage: MessageMap) => void>();
|
const subscriptions = new Map<MessageName, (taggedMessage: MessageMap) => void>();
|
||||||
@@ -99,4 +99,4 @@ export function createSubscriptionRouter() {
|
|||||||
handleFrontendMessage,
|
handleFrontendMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export type SubscriptionRouter = ReturnType<typeof createSubscriptionRouter>;
|
export type SubscriptionsRouter = ReturnType<typeof createSubscriptionsRouter>;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||||
import { stripIndents } from "@graphite/utility-functions/strip-indents";
|
import { stripIndents } from "@graphite/utility-functions/strip-indents";
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export function insertAtCaret(text: string) {
|
|||||||
element.dispatchEvent(new Event("input", { bubbles: true }));
|
element.dispatchEvent(new Event("input", { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function triggerClipboardRead(editor: Editor) {
|
export async function triggerClipboardRead(editor: EditorHandle) {
|
||||||
// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers
|
// In the try block, attempt to read from the Clipboard API, which may not have permission and may not be supported in all browsers
|
||||||
// In the catch block, explain to the user why the paste failed and how to fix or work around the problem
|
// In the catch block, explain to the user why the paste failed and how to fix or work around the problem
|
||||||
try {
|
try {
|
||||||
@@ -105,7 +105,7 @@ export async function triggerClipboardRead(editor: Editor) {
|
|||||||
const blob = await item.getType("text/plain");
|
const blob = await item.getType("text/plain");
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
if (typeof reader.result === "string") editor.handle.pasteText(reader.result);
|
if (typeof reader.result === "string") editor.pasteText(reader.result);
|
||||||
};
|
};
|
||||||
reader.readAsText(blob);
|
reader.readAsText(blob);
|
||||||
return true;
|
return true;
|
||||||
@@ -119,7 +119,7 @@ export async function triggerClipboardRead(editor: Editor) {
|
|||||||
const blob = await item.getType("text/plain");
|
const blob = await item.getType("text/plain");
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
if (typeof reader.result === "string") editor.handle.pasteSvg(undefined, reader.result);
|
if (typeof reader.result === "string") editor.pasteSvg(undefined, reader.result);
|
||||||
};
|
};
|
||||||
reader.readAsText(blob);
|
reader.readAsText(blob);
|
||||||
return true;
|
return true;
|
||||||
@@ -132,7 +132,7 @@ export async function triggerClipboardRead(editor: Editor) {
|
|||||||
reader.onload = async () => {
|
reader.onload = async () => {
|
||||||
if (reader.result instanceof ArrayBuffer) {
|
if (reader.result instanceof ArrayBuffer) {
|
||||||
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
|
const imageData = await extractPixelData(new Blob([reader.result], { type: imageType }));
|
||||||
editor.handle.pasteImage(undefined, new Uint8Array(imageData.data), imageData.width, imageData.height);
|
editor.pasteImage(undefined, new Uint8Array(imageData.data), imageData.width, imageData.height);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(blob);
|
reader.readAsArrayBuffer(blob);
|
||||||
@@ -170,6 +170,6 @@ export async function triggerClipboardRead(editor: Editor) {
|
|||||||
};
|
};
|
||||||
const message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);
|
const message = Object.entries(matchMessage).find(([key]) => String(err).includes(key))?.[1] || String(err);
|
||||||
|
|
||||||
editor.handle.errorDialog("Cannot access clipboard", message);
|
editor.errorDialog("Cannot access clipboard", message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||||
|
|
||||||
export function downloadFileURL(filename: string, url: string) {
|
export function downloadFileURL(filename: string, url: string) {
|
||||||
@@ -66,18 +66,18 @@ export async function upload(accept: string, textOrData: "text" | "data" | "both
|
|||||||
}
|
}
|
||||||
export type UploadResult<T> = { filename: string; type: string; content: T };
|
export type UploadResult<T> = { filename: string; type: string; content: T };
|
||||||
|
|
||||||
export async function pasteFile(item: DataTransferItem, editor: Editor, mouse?: [number, number], insertParentId?: bigint, insertIndex?: number) {
|
export async function pasteFile(item: DataTransferItem, editor: EditorHandle, mouse?: [number, number], insertParentId?: bigint, insertIndex?: number) {
|
||||||
const file = item.getAsFile();
|
const file = item.getAsFile();
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
if (file.type.startsWith("image/svg")) {
|
if (file.type.startsWith("image/svg")) {
|
||||||
const svg = await file.text();
|
const svg = await file.text();
|
||||||
editor.handle.pasteSvg(file.name, svg, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
|
editor.pasteSvg(file.name, svg, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
|
||||||
} else if (file.type.startsWith("image/")) {
|
} else if (file.type.startsWith("image/")) {
|
||||||
const imageData = await extractPixelData(file);
|
const imageData = await extractPixelData(file);
|
||||||
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
|
editor.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height, mouse?.[0], mouse?.[1], insertParentId, insertIndex);
|
||||||
} else if (file.name.endsWith("." + editor.handle.fileExtension())) {
|
} else if (file.name.endsWith("." + editor.fileExtension())) {
|
||||||
// TODO: When we eventually have sub-documents, this should be changed to import the document as a node instead of opening it in a separate tab
|
// TODO: When we eventually have sub-documents, this should be changed to import the document as a node instead of opening it in a separate tab
|
||||||
editor.handle.openFile(file.name, await file.bytes());
|
editor.openFile(file.name, await file.bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { DialogStore } from "@graphite/stores/dialog";
|
import type { DialogStore } from "@graphite/stores/dialog";
|
||||||
import type { DocumentStore } from "@graphite/stores/document";
|
import type { DocumentStore } from "@graphite/stores/document";
|
||||||
import { toggleFullscreen } from "@graphite/stores/fullscreen";
|
import { toggleFullscreen } from "@graphite/stores/fullscreen";
|
||||||
@@ -79,7 +79,7 @@ export async function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent, dia
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onKeyDown(e: KeyboardEvent, editor: Editor, dialogStore: DialogStore) {
|
export async function onKeyDown(e: KeyboardEvent, editor: EditorHandle, dialogStore: DialogStore) {
|
||||||
const key = await getLocalizedScanCode(e);
|
const key = await getLocalizedScanCode(e);
|
||||||
|
|
||||||
const NO_KEY_REPEAT_MODIFIER_KEYS = ["ControlLeft", "ControlRight", "ShiftLeft", "ShiftRight", "MetaLeft", "MetaRight", "AltLeft", "AltRight", "AltGraph", "CapsLock", "Fn", "FnLock"];
|
const NO_KEY_REPEAT_MODIFIER_KEYS = ["ControlLeft", "ControlRight", "ShiftLeft", "ShiftRight", "MetaLeft", "MetaRight", "AltLeft", "AltRight", "AltGraph", "CapsLock", "Fn", "FnLock"];
|
||||||
@@ -88,29 +88,29 @@ export async function onKeyDown(e: KeyboardEvent, editor: Editor, dialogStore: D
|
|||||||
if (await shouldRedirectKeyboardEventToBackend(e, dialogStore)) {
|
if (await shouldRedirectKeyboardEventToBackend(e, dialogStore)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onKeyDown(key, modifiers, e.repeat);
|
editor.onKeyDown(key, modifiers, e.repeat);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get(dialogStore).visible && key === "Escape") {
|
if (get(dialogStore).visible && key === "Escape") {
|
||||||
editor.handle.onDialogDismiss();
|
editor.onDialogDismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function onKeyUp(e: KeyboardEvent, editor: Editor, dialogStore: DialogStore) {
|
export async function onKeyUp(e: KeyboardEvent, editor: EditorHandle, dialogStore: DialogStore) {
|
||||||
const key = await getLocalizedScanCode(e);
|
const key = await getLocalizedScanCode(e);
|
||||||
|
|
||||||
if (await shouldRedirectKeyboardEventToBackend(e, dialogStore)) {
|
if (await shouldRedirectKeyboardEventToBackend(e, dialogStore)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onKeyUp(key, modifiers, e.repeat);
|
editor.onKeyUp(key, modifiers, e.repeat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pointer events
|
// Pointer events
|
||||||
|
|
||||||
// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend
|
// While any pointer button is already down, additional button down events are not reported, but they are sent as `pointermove` events and these are handled in the backend
|
||||||
export function onPointerMove(e: PointerEvent, editor: Editor, documentStore: DocumentStore) {
|
export function onPointerMove(e: PointerEvent, editor: EditorHandle, documentStore: DocumentStore) {
|
||||||
potentiallyRestoreCanvasFocus(e);
|
potentiallyRestoreCanvasFocus(e);
|
||||||
|
|
||||||
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
if (!e.buttons) viewportPointerInteractionOngoing = false;
|
||||||
@@ -124,11 +124,11 @@ export function onPointerMove(e: PointerEvent, editor: Editor, documentStore: Do
|
|||||||
if (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return;
|
if (!viewportPointerInteractionOngoing && (inFloatingMenu || inGraphOverlay)) return;
|
||||||
|
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
if (detectShake(e)) editor.handle.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers);
|
if (detectShake(e)) editor.onMouseShake(e.clientX, e.clientY, e.buttons, modifiers);
|
||||||
editor.handle.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
|
editor.onMouseMove(e.clientX, e.clientY, e.buttons, modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onPointerDown(e: PointerEvent, editor: Editor, dialogStore: DialogStore) {
|
export function onPointerDown(e: PointerEvent, editor: EditorHandle, dialogStore: DialogStore) {
|
||||||
potentiallyRestoreCanvasFocus(e);
|
potentiallyRestoreCanvasFocus(e);
|
||||||
|
|
||||||
const inFloatingMenu = e.target instanceof Element && e.target.closest("[data-floating-menu-content]");
|
const inFloatingMenu = e.target instanceof Element && e.target.closest("[data-floating-menu-content]");
|
||||||
@@ -138,7 +138,7 @@ export function onPointerDown(e: PointerEvent, editor: Editor, dialogStore: Dial
|
|||||||
const inTextInput = e.target === textToolInteractiveInputElement;
|
const inTextInput = e.target === textToolInteractiveInputElement;
|
||||||
|
|
||||||
if (get(dialogStore).visible && !inDialog) {
|
if (get(dialogStore).visible && !inDialog) {
|
||||||
editor.handle.onDialogDismiss();
|
editor.onDialogDismiss();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ export function onPointerDown(e: PointerEvent, editor: Editor, dialogStore: Dial
|
|||||||
if (!inTextInput && !inContextMenu) {
|
if (!inTextInput && !inContextMenu) {
|
||||||
if (textToolInteractiveInputElement) {
|
if (textToolInteractiveInputElement) {
|
||||||
const isLeftOrRightClick = e.button === BUTTON_RIGHT || e.button === BUTTON_LEFT;
|
const isLeftOrRightClick = e.button === BUTTON_RIGHT || e.button === BUTTON_LEFT;
|
||||||
editor.handle.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText), isLeftOrRightClick);
|
editor.onChangeText(textInputCleanup(textToolInteractiveInputElement.innerText), isLeftOrRightClick);
|
||||||
} else {
|
} else {
|
||||||
viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
|
viewportPointerInteractionOngoing = isTargetingCanvas instanceof Element;
|
||||||
}
|
}
|
||||||
@@ -154,11 +154,11 @@ export function onPointerDown(e: PointerEvent, editor: Editor, dialogStore: Dial
|
|||||||
|
|
||||||
if (viewportPointerInteractionOngoing && isTargetingCanvas instanceof Element) {
|
if (viewportPointerInteractionOngoing && isTargetingCanvas instanceof Element) {
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
|
editor.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onPointerUp(e: PointerEvent, editor: Editor) {
|
export function onPointerUp(e: PointerEvent, editor: EditorHandle) {
|
||||||
potentiallyRestoreCanvasFocus(e);
|
potentiallyRestoreCanvasFocus(e);
|
||||||
|
|
||||||
// Don't let the browser navigate back or forward when using the buttons on some mice
|
// Don't let the browser navigate back or forward when using the buttons on some mice
|
||||||
@@ -172,12 +172,12 @@ export function onPointerUp(e: PointerEvent, editor: Editor) {
|
|||||||
if (textToolInteractiveInputElement) return;
|
if (textToolInteractiveInputElement) return;
|
||||||
|
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
|
editor.onMouseUp(e.clientX, e.clientY, e.buttons, modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mouse events
|
// Mouse events
|
||||||
|
|
||||||
export function onPotentialDoubleClick(e: MouseEvent, editor: Editor) {
|
export function onPotentialDoubleClick(e: MouseEvent, editor: EditorHandle) {
|
||||||
if (textToolInteractiveInputElement || inPointerLock) return;
|
if (textToolInteractiveInputElement || inPointerLock) return;
|
||||||
|
|
||||||
// Allow only events within the viewport or node graph boundaries
|
// Allow only events within the viewport or node graph boundaries
|
||||||
@@ -196,7 +196,7 @@ export function onPotentialDoubleClick(e: MouseEvent, editor: Editor) {
|
|||||||
if (e.button === BUTTON_FORWARD) buttons = 16; // Forward
|
if (e.button === BUTTON_FORWARD) buttons = 16; // Forward
|
||||||
|
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
editor.onDoubleClick(e.clientX, e.clientY, buttons, modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onMouseDown(e: MouseEvent) {
|
export function onMouseDown(e: MouseEvent) {
|
||||||
@@ -216,7 +216,7 @@ export function onPointerLockChange() {
|
|||||||
|
|
||||||
// Wheel events
|
// Wheel events
|
||||||
|
|
||||||
export function onWheelScroll(e: WheelEvent, editor: Editor) {
|
export function onWheelScroll(e: WheelEvent, editor: EditorHandle) {
|
||||||
const isTargetingCanvas = e.target instanceof Element && e.target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
const isTargetingCanvas = e.target instanceof Element && e.target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||||
|
|
||||||
// Prevent zooming the entire page when using Ctrl + scroll wheel outside of the viewport
|
// Prevent zooming the entire page when using Ctrl + scroll wheel outside of the viewport
|
||||||
@@ -235,7 +235,7 @@ export function onWheelScroll(e: WheelEvent, editor: Editor) {
|
|||||||
if (isTargetingCanvas) {
|
if (isTargetingCanvas) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const modifiers = makeKeyboardModifiersBitfield(e);
|
const modifiers = makeKeyboardModifiersBitfield(e);
|
||||||
editor.handle.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
editor.onWheelScroll(e.clientX, e.clientY, e.buttons, e.deltaX, e.deltaY, e.deltaZ, modifiers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,15 +247,15 @@ export function onModifyInputField(e: CustomEvent) {
|
|||||||
|
|
||||||
// Window events
|
// Window events
|
||||||
|
|
||||||
export async function onBeforeUnload(e: BeforeUnloadEvent, editor: Editor, portfolioStore: PortfolioStore) {
|
export async function onBeforeUnload(e: BeforeUnloadEvent, editor: EditorHandle, portfolioStore: PortfolioStore) {
|
||||||
const activeDocument = get(portfolioStore).documents[get(portfolioStore).activeDocumentIndex];
|
const activeDocument = get(portfolioStore).documents[get(portfolioStore).activeDocumentIndex];
|
||||||
if (activeDocument && !activeDocument.details.isAutoSaved) editor.handle.triggerAutoSave(activeDocument.id);
|
if (activeDocument && !activeDocument.details.isAutoSaved) editor.triggerAutoSave(activeDocument.id);
|
||||||
|
|
||||||
// Skip the message if the editor crashed, since work is already lost
|
// Skip the message if the editor crashed, since work is already lost
|
||||||
if (await editor.handle.hasCrashed()) return;
|
if (await editor.hasCrashed()) return;
|
||||||
|
|
||||||
// Skip the message during development, since it's annoying when testing
|
// Skip the message during development, since it's annoying when testing
|
||||||
if (await editor.handle.inDevelopmentMode()) return;
|
if (await editor.inDevelopmentMode()) return;
|
||||||
|
|
||||||
const allDocumentsSaved = get(portfolioStore).documents.reduce((acc, doc) => acc && doc.details.isSaved, true);
|
const allDocumentsSaved = get(portfolioStore).documents.reduce((acc, doc) => acc && doc.details.isSaved, true);
|
||||||
if (!allDocumentsSaved) {
|
if (!allDocumentsSaved) {
|
||||||
@@ -264,13 +264,13 @@ export async function onBeforeUnload(e: BeforeUnloadEvent, editor: Editor, portf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onPaste(e: ClipboardEvent, editor: Editor) {
|
export function onPaste(e: ClipboardEvent, editor: EditorHandle) {
|
||||||
const dataTransfer = e.clipboardData;
|
const dataTransfer = e.clipboardData;
|
||||||
if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
|
if (!dataTransfer || targetIsTextField(e.target || undefined)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
Array.from(dataTransfer.items).forEach(async (item) => {
|
Array.from(dataTransfer.items).forEach(async (item) => {
|
||||||
if (item.type === "text/plain") item.getAsString((text) => editor.handle.pasteText(text));
|
if (item.type === "text/plain") item.getAsString((text) => editor.pasteText(text));
|
||||||
await pasteFile(item, editor);
|
await pasteFile(item, editor);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
|
||||||
export type RequestResult = { body: string; status: number };
|
export type RequestResult = { body: string; status: number };
|
||||||
|
|
||||||
// Special implementation using the legacy XMLHttpRequest API that provides callbacks to get:
|
// Special implementation using the legacy XMLHttpRequest API that provides callbacks to get:
|
||||||
@@ -31,3 +33,23 @@ export function requestWithUploadDownloadProgress(
|
|||||||
|
|
||||||
return [promise, xhrValue];
|
return [promise, xhrValue];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the URL hash fragment contains a demo artwork path (e.g. #demo/isometric-light), fetch and open it
|
||||||
|
export async function loadDemoArtwork(editor: EditorHandle) {
|
||||||
|
const demoArtwork = window.location.hash.trim().match(/#demo\/(.*)/)?.[1];
|
||||||
|
if (!demoArtwork) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(`/demo-artwork/${demoArtwork}.${editor.fileExtension()}`, document.location.href);
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) throw new Error();
|
||||||
|
|
||||||
|
const filename = url.pathname.split("/").pop() || `Untitled.${editor.fileExtension()}`;
|
||||||
|
const content = await response.bytes();
|
||||||
|
editor.openFile(filename, content);
|
||||||
|
|
||||||
|
history.replaceState("", "", `${window.location.pathname}${window.location.search}`);
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import * as idb from "idb-keyval";
|
import * as idb from "idb-keyval";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
import type { PortfolioStore } from "@graphite/stores/portfolio";
|
||||||
import type { MessageBody } from "@graphite/subscription-router";
|
import type { MessageBody } from "/src/subscriptions-router";
|
||||||
|
|
||||||
export async function storeCurrentDocumentId(documentId: string) {
|
export async function storeCurrentDocumentId(documentId: string) {
|
||||||
const indexedDbStorage = idb.createStore("graphite", "store");
|
const indexedDbStorage = idb.createStore("graphite", "store");
|
||||||
@@ -65,7 +65,7 @@ export async function removeDocument(id: string, portfolio: PortfolioStore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadFirstDocument(editor: Editor) {
|
export async function loadFirstDocument(editor: EditorHandle) {
|
||||||
const indexedDbStorage = idb.createStore("graphite", "store");
|
const indexedDbStorage = idb.createStore("graphite", "store");
|
||||||
|
|
||||||
const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage);
|
const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage);
|
||||||
@@ -87,19 +87,19 @@ export async function loadFirstDocument(editor: Editor) {
|
|||||||
|
|
||||||
if (currentDocumentId !== undefined && String(currentDocumentId) in previouslySavedDocuments) {
|
if (currentDocumentId !== undefined && String(currentDocumentId) in previouslySavedDocuments) {
|
||||||
const doc = previouslySavedDocuments[String(currentDocumentId)];
|
const doc = previouslySavedDocuments[String(currentDocumentId)];
|
||||||
editor.handle.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);
|
editor.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);
|
||||||
editor.handle.selectDocument(currentDocumentId);
|
editor.selectDocument(currentDocumentId);
|
||||||
} else {
|
} else {
|
||||||
const len = orderedSavedDocuments.length;
|
const len = orderedSavedDocuments.length;
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
const doc = orderedSavedDocuments[len - 1];
|
const doc = orderedSavedDocuments[len - 1];
|
||||||
editor.handle.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);
|
editor.openAutoSavedDocument(doc.documentId, doc.details.name, doc.details.isSaved, doc.document, false);
|
||||||
editor.handle.selectDocument(doc.documentId);
|
editor.selectDocument(doc.documentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadRestDocuments(editor: Editor) {
|
export async function loadRestDocuments(editor: EditorHandle) {
|
||||||
const indexedDbStorage = idb.createStore("graphite", "store");
|
const indexedDbStorage = idb.createStore("graphite", "store");
|
||||||
|
|
||||||
const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage);
|
const previouslySavedDocuments = await idb.get<Record<string, MessageBody<"TriggerPersistenceWriteDocument">>>("documents", indexedDbStorage);
|
||||||
@@ -126,15 +126,15 @@ export async function loadRestDocuments(editor: Editor) {
|
|||||||
for (let i = currentIndex - 1; i >= 0; i--) {
|
for (let i = currentIndex - 1; i >= 0; i--) {
|
||||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||||
const { name, isSaved } = details;
|
const { name, isSaved } = details;
|
||||||
editor.handle.openAutoSavedDocument(documentId, name, isSaved, document, true);
|
editor.openAutoSavedDocument(documentId, name, isSaved, document, true);
|
||||||
}
|
}
|
||||||
for (let i = currentIndex + 1; i < orderedSavedDocuments.length; i++) {
|
for (let i = currentIndex + 1; i < orderedSavedDocuments.length; i++) {
|
||||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||||
const { name, isSaved } = details;
|
const { name, isSaved } = details;
|
||||||
editor.handle.openAutoSavedDocument(documentId, name, isSaved, document, false);
|
editor.openAutoSavedDocument(documentId, name, isSaved, document, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.handle.selectDocument(currentDocumentId);
|
editor.selectDocument(currentDocumentId);
|
||||||
}
|
}
|
||||||
// No valid current document: open all remaining documents and select the last one
|
// No valid current document: open all remaining documents and select the last one
|
||||||
else {
|
else {
|
||||||
@@ -143,10 +143,10 @@ export async function loadRestDocuments(editor: Editor) {
|
|||||||
for (let i = length - 2; i >= 0; i--) {
|
for (let i = length - 2; i >= 0; i--) {
|
||||||
const { documentId, document, details } = orderedSavedDocuments[i];
|
const { documentId, document, details } = orderedSavedDocuments[i];
|
||||||
const { name, isSaved } = details;
|
const { name, isSaved } = details;
|
||||||
editor.handle.openAutoSavedDocument(documentId, name, isSaved, document, true);
|
editor.openAutoSavedDocument(documentId, name, isSaved, document, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > 0) editor.handle.selectDocument(orderedSavedDocuments[length - 1].documentId);
|
if (length > 0) editor.selectDocument(orderedSavedDocuments[length - 1].documentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +177,11 @@ export async function saveEditorPreferences(preferences: unknown) {
|
|||||||
await idb.set("preferences", preferences, indexedDbStorage);
|
await idb.set("preferences", preferences, indexedDbStorage);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadEditorPreferences(editor: Editor) {
|
export async function loadEditorPreferences(editor: EditorHandle) {
|
||||||
const indexedDbStorage = idb.createStore("graphite", "store");
|
const indexedDbStorage = idb.createStore("graphite", "store");
|
||||||
|
|
||||||
const preferences = await idb.get<Record<string, unknown>>("preferences", indexedDbStorage);
|
const preferences = await idb.get<Record<string, unknown>>("preferences", indexedDbStorage);
|
||||||
editor.handle.loadPreferences(preferences ? JSON.stringify(preferences) : undefined);
|
editor.loadPreferences(preferences ? JSON.stringify(preferences) : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function wipeDocuments() {
|
export async function wipeDocuments() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Editor } from "@graphite/editor";
|
import type { EditorHandle } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
|
||||||
export function setupViewportResizeObserver(editor: Editor): () => void {
|
export function setupViewportResizeObserver(editor: EditorHandle): () => void {
|
||||||
const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]"));
|
const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]"));
|
||||||
if (viewports.length <= 0) return () => {};
|
if (viewports.length <= 0) return () => {};
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export function setupViewportResizeObserver(editor: Editor): () => void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.handle.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale);
|
editor.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user