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

* Remove the unused Editor.raw/wasmMemory/wasmImport

* Split out Editor.subscriptions

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

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

* Replace Editor typedef with EditorHandle import

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

* Remove editor.ts

* Update the readme

* Fix demo art loading bug
This commit is contained in:
Keavon Chambers
2026-03-20 23:34:13 -07:00
committed by GitHub
parent 64fd12a1a0
commit ed7987c881
40 changed files with 549 additions and 584 deletions
+24 -20
View File
@@ -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 { DocumentStore } from "@graphite/stores/document";
import { fullscreenModeChanged } from "@graphite/stores/fullscreen";
import type { PortfolioStore } from "@graphite/stores/portfolio";
import type { SubscriptionsRouter } from "/src/subscriptions-router";
import { triggerClipboardRead } from "@graphite/utility-functions/clipboard";
import {
onBeforeUnload,
@@ -32,42 +33,44 @@ export const PRESS_REPEAT_DELAY_MS = 400;
export const PRESS_REPEAT_INTERVAL_MS = 72;
export const PRESS_REPEAT_INTERVAL_RAPID_MS = 10;
const listeners: Listener[] = [
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => editorRef && portfolioStore && onBeforeUnload(e, editorRef, portfolioStore) },
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => editorRef && dialogStore && onKeyUp(e, editorRef, dialogStore) },
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => editorRef && dialogStore && onKeyDown(e, editorRef, dialogStore) },
{ target: window, eventName: "pointermove", action: (e: PointerEvent) => editorRef && documentStore && onPointerMove(e, editorRef, documentStore) },
{ target: window, eventName: "pointerdown", action: (e: PointerEvent) => editorRef && dialogStore && onPointerDown(e, editorRef, dialogStore) },
{ target: window, eventName: "pointerup", action: (e: PointerEvent) => editorRef && onPointerUp(e, editorRef) },
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => editorHandle && portfolioStore && onBeforeUnload(e, editorHandle, portfolioStore) },
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => editorHandle && dialogStore && onKeyUp(e, editorHandle, dialogStore) },
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => editorHandle && dialogStore && onKeyDown(e, editorHandle, dialogStore) },
{ target: window, eventName: "pointermove", action: (e: PointerEvent) => editorHandle && documentStore && onPointerMove(e, editorHandle, documentStore) },
{ target: window, eventName: "pointerdown", action: (e: PointerEvent) => editorHandle && dialogStore && onPointerDown(e, editorHandle, dialogStore) },
{ target: window, eventName: "pointerup", action: (e: PointerEvent) => editorHandle && onPointerUp(e, editorHandle) },
{ target: window, eventName: "mousedown", action: (e: MouseEvent) => onMouseDown(e) },
{ target: window, eventName: "mouseup", action: (e: MouseEvent) => editorRef && onPotentialDoubleClick(e, editorRef) },
{ target: window, eventName: "wheel", action: (e: WheelEvent) => editorRef && onWheelScroll(e, editorRef), options: { passive: false } },
{ target: window, eventName: "mouseup", action: (e: MouseEvent) => editorHandle && onPotentialDoubleClick(e, editorHandle) },
{ 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: "focusout", action: () => onFocusOut() },
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => onContextMenu(e) },
{ 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: "pointerlockerror", action: onPointerLockChange },
];
let editorRef: Editor | undefined = undefined;
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
let editorHandle: EditorHandle | undefined = undefined;
let dialogStore: DialogStore | undefined = undefined;
let portfolioStore: PortfolioStore | 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();
editorRef = editor;
subscriptionsRouter = subscriptions;
editorHandle = editor;
dialogStore = dialog;
portfolioStore = portfolio;
documentStore = doc;
editor.subscriptions.subscribeFrontendMessage("TriggerClipboardRead", () => {
subscriptions.subscribeFrontendMessage("TriggerClipboardRead", () => {
triggerClipboardRead(editor);
});
editor.subscriptions.subscribeFrontendMessage("WindowPointerLockMove", (data) => {
subscriptions.subscribeFrontendMessage("WindowPointerLockMove", (data) => {
// Desktop app only: dispatch custom pointer lock movement events
const event = new CustomEvent("pointerlockmove", { detail: { x: data.position[0], y: data.position[1] } });
window.dispatchEvent(event);
@@ -83,11 +86,11 @@ export function createInputManager(editor: Editor, dialog: DialogStore, portfoli
// Return the destructor
export function destroyInputManager() {
const editor = editorRef;
if (!editor) return;
const subscriptions = subscriptionsRouter;
if (!subscriptions) return;
editor.subscriptions.unsubscribeFrontendMessage("TriggerClipboardRead");
editor.subscriptions.unsubscribeFrontendMessage("WindowPointerLockMove");
subscriptions.unsubscribeFrontendMessage("TriggerClipboardRead");
subscriptions.unsubscribeFrontendMessage("WindowPointerLockMove");
// 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));
@@ -95,5 +98,6 @@ export function destroyInputManager() {
// Self-accepting HMR: tear down the old instance and re-create with the new module's code
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);
});