Refactor messages.ts by removing class-transformer and JS classes (#3858)

* Fix gamma correction with HTML-based editable Text tool text

* Migrate simple, undecorated classes to types

* Remove TupleToVec2 transformation

* Remove @Transform from tooltips

* Cleanup: replace value.toString() with String(value) everywhere

* Convert documentId from string to bigint

* Migrate the rest of the easy @Transform/@Type decorations

* Migrate FillChoice

* Migrate WidgetDiffUpdate

* Migrate WidgetInstance

* Migrate away from classes that extend WidgetProps

* Remove class-transformer and all classes in messages.ts

* Migrate UI layout passing

* Remove dead code

* Remove unnecessary export and readonly prefixes

* Remove HSVA type

* Break out Color, Gradient, and FillChoice functions into a utility-functions file

* Move widget helper functions from messages.ts into a new utility-functions file; restructure type imports

* Reduce internal type defs

* Rename JsMessage to FrontendMessage

* Code review fixes

* Fix other usages

* Tidying up
This commit is contained in:
Keavon Chambers
2026-03-05 01:43:21 -08:00
committed by GitHub
parent f00a15a4c9
commit 8a1dfb9d8f
64 changed files with 1436 additions and 2093 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { type AppWindowPlatform, UpdatePlatform, UpdateViewportHolePunch, UpdateMaximized, UpdateFullscreen, UpdateUIScale } from "@graphite/messages";
import type { Editor } from "@graphite/editor";
import type { AppWindowPlatform } from "@graphite/messages";
export function createAppWindowState(editor: Editor) {
const { subscribe, update } = writable({
@@ -13,31 +13,31 @@ export function createAppWindowState(editor: Editor) {
});
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdatePlatform, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdatePlatform", (data) => {
update((state) => {
state.platform = data.platform;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateMaximized, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateMaximized", (data) => {
update((state) => {
state.maximized = data.maximized;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateFullscreen, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateFullscreen", (data) => {
update((state) => {
state.fullscreen = data.fullscreen;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateViewportHolePunch, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateViewportHolePunch", (data) => {
update((state) => {
state.viewportHolePunch = data.active;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateUIScale, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateUIScale", (data) => {
update((state) => {
state.uiScale = data.scale;
return state;
+9 -9
View File
@@ -1,9 +1,9 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { type IconName } from "@graphite/icons";
import { DisplayDialog, DialogClose, UpdateDialogButtons, UpdateDialogColumn1, UpdateDialogColumn2, patchLayout, TriggerDisplayThirdPartyLicensesDialog } from "@graphite/messages";
import type { Editor } from "@graphite/editor";
import type { IconName } from "@graphite/icons";
import type { Layout } from "@graphite/messages";
import { patchLayout } from "@graphite/utility-functions/widgets";
export function createDialogState(editor: Editor) {
const { subscribe, update } = writable({
@@ -45,7 +45,7 @@ export function createDialogState(editor: Editor) {
}
// Subscribe to process backend events
editor.subscriptions.subscribeJsMessage(DisplayDialog, (data) => {
editor.subscriptions.subscribeFrontendMessage("DisplayDialog", (data) => {
update((state) => {
state.visible = true;
@@ -55,30 +55,30 @@ export function createDialogState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogButtons, (data) => {
editor.subscriptions.subscribeLayoutUpdate("DialogButtons", (data) => {
update((state) => {
patchLayout(state.buttons, data);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogColumn1, (data) => {
editor.subscriptions.subscribeLayoutUpdate("DialogColumn1", (data) => {
update((state) => {
patchLayout(state.column1, data);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogColumn2, (data) => {
editor.subscriptions.subscribeLayoutUpdate("DialogColumn2", (data) => {
update((state) => {
patchLayout(state.column2, data);
return state;
});
});
editor.subscriptions.subscribeJsMessage(DialogClose, dismissDialog);
editor.subscriptions.subscribeFrontendMessage("DialogClose", dismissDialog);
editor.subscriptions.subscribeJsMessage(TriggerDisplayThirdPartyLicensesDialog, async () => {
editor.subscriptions.subscribeFrontendMessage("TriggerDisplayThirdPartyLicensesDialog", async () => {
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.`;
+9 -19
View File
@@ -1,19 +1,9 @@
import { tick } from "svelte";
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import {
patchLayout,
UpdateDocumentBarLayout,
UpdateToolOptionsLayout,
UpdateToolShelfLayout,
UpdateWorkingColorsLayout,
UpdateNodeGraphControlBarLayout,
UpdateGraphViewOverlay,
UpdateGraphFadeArtwork,
} from "@graphite/messages";
import type { Editor } from "@graphite/editor";
import type { Layout } from "@graphite/messages";
import { patchLayout } from "@graphite/utility-functions/widgets";
export function createDocumentState(editor: Editor) {
const state = writable({
@@ -30,13 +20,13 @@ export function createDocumentState(editor: Editor) {
const { subscribe, update } = state;
// Update layouts
editor.subscriptions.subscribeJsMessage(UpdateGraphFadeArtwork, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateGraphFadeArtwork", (data) => {
update((state) => {
state.fadeArtwork = data.percentage;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateToolOptionsLayout, async (data) => {
editor.subscriptions.subscribeLayoutUpdate("ToolOptions", async (data) => {
await tick();
update((state) => {
@@ -44,7 +34,7 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentBarLayout, async (data) => {
editor.subscriptions.subscribeLayoutUpdate("DocumentBar", async (data) => {
await tick();
update((state) => {
@@ -52,7 +42,7 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateToolShelfLayout, async (data) => {
editor.subscriptions.subscribeLayoutUpdate("ToolShelf", async (data) => {
await tick();
update((state) => {
@@ -60,7 +50,7 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (data) => {
editor.subscriptions.subscribeLayoutUpdate("WorkingColors", async (data) => {
await tick();
update((state) => {
@@ -68,7 +58,7 @@ export function createDocumentState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphControlBarLayout, (data) => {
editor.subscriptions.subscribeLayoutUpdate("NodeGraphControlBar", (data) => {
update((state) => {
patchLayout(state.nodeGraphControlBarLayout, data);
return state;
@@ -76,7 +66,7 @@ export function createDocumentState(editor: Editor) {
});
// Show or hide the graph view overlay
editor.subscriptions.subscribeJsMessage(UpdateGraphViewOverlay, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateGraphViewOverlay", (data) => {
update((state) => {
state.graphViewOverlayOpen = data.open;
return state;
+2 -3
View File
@@ -1,7 +1,6 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { WindowFullscreen } from "@graphite/messages";
import type { Editor } from "@graphite/editor";
export function createFullscreenState(editor: Editor) {
// Experimental Keyboard API: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/keyboard
@@ -51,7 +50,7 @@ export function createFullscreenState(editor: Editor) {
});
}
editor.subscriptions.subscribeJsMessage(WindowFullscreen, () => {
editor.subscriptions.subscribeFrontendMessage("WindowFullscreen", () => {
toggleFullscreen();
});
+23 -47
View File
@@ -1,33 +1,9 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import type { NodeGraphError } from "@graphite/messages";
import {
type Box,
type FrontendClickTargets,
type ContextMenuInformation,
type FrontendNode,
type FrontendNodeType,
type WirePath,
ClearAllNodeGraphWires,
SendUIMetadata,
UpdateBox,
UpdateClickTargets,
UpdateContextMenuInformation,
UpdateInSelectedNetwork,
UpdateImportReorderIndex,
UpdateExportReorderIndex,
UpdateImportsExports,
UpdateLayerWidths,
UpdateNodeGraphNodes,
UpdateVisibleNodes,
UpdateNodeGraphWires,
UpdateNodeGraphSelection,
UpdateNodeGraphTransform,
UpdateNodeThumbnail,
UpdateWirePathInProgress,
UpdateNodeGraphErrorDiagnostic,
} from "@graphite/messages";
import type { Editor } from "@graphite/editor";
import type { NodeGraphError, Box, FrontendClickTargets, ContextMenuInformation, FrontendNode, FrontendNodeType, WirePath, FrontendMessages } from "@graphite/messages";
type UpdateImportsExports = FrontendMessages["UpdateImportsExports"];
export function createNodeGraphState(editor: Editor) {
const { subscribe, update } = writable({
@@ -62,56 +38,56 @@ export function createNodeGraphState(editor: Editor) {
}
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(SendUIMetadata, (data) => {
editor.subscriptions.subscribeFrontendMessage("SendUIMetadata", (data) => {
update((state) => {
state.nodeDescriptions = data.nodeDescriptions;
state.nodeDescriptions = new Map(data.nodeDescriptions);
state.nodeTypes = data.nodeTypes;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateBox, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateBox", (data) => {
update((state) => {
state.box = data.box;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateClickTargets, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateClickTargets", (data) => {
update((state) => {
state.clickTargets = data.clickTargets;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateContextMenuInformation, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateContextMenuInformation", (data) => {
update((state) => {
state.contextMenuInformation = data.contextMenuInformation;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateImportReorderIndex, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateImportReorderIndex", (data) => {
update((state) => {
state.reorderImportIndex = data.importIndex;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateExportReorderIndex, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateExportReorderIndex", (data) => {
update((state) => {
state.reorderExportIndex = data.exportIndex;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateImportsExports", (data) => {
update((state) => {
state.updateImportsExports = data;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateInSelectedNetwork, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateInSelectedNetwork", (data) => {
update((state) => {
state.inSelectedNetwork = data.inSelectedNetwork;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateLayerWidths, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateLayerWidths", (data) => {
update((state) => {
state.layerWidths = data.layerWidths;
state.chainWidths = data.chainWidths;
@@ -119,7 +95,7 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphNodes", (data) => {
update((state) => {
state.nodes.clear();
data.nodes.forEach((node) => {
@@ -128,19 +104,19 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphErrorDiagnostic, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphErrorDiagnostic", (data) => {
update((state) => {
state.error = data.error;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateVisibleNodes", (data) => {
update((state) => {
state.visibleNodes = new Set<bigint>(data.nodes);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphWires", (data) => {
update((state) => {
data.wires.forEach((wireUpdate) => {
let inputMap = state.wires.get(wireUpdate.id);
@@ -158,31 +134,31 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, () => {
editor.subscriptions.subscribeFrontendMessage("ClearAllNodeGraphWires", () => {
update((state) => {
state.wires.clear();
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphSelection", (data) => {
update((state) => {
state.selected = data.selected;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphTransform, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeGraphTransform", (data) => {
update((state) => {
state.transform = data.transform;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeThumbnail, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateNodeThumbnail", (data) => {
update((state) => {
state.thumbnails.set(data.id, data.value);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateWirePathInProgress, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateWirePathInProgress", (data) => {
update((state) => {
state.wirePathInProgress = data.wirePath;
return state;
+13 -26
View File
@@ -1,20 +1,7 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import type { Editor } from "@graphite/editor";
import type { OpenDocument } from "@graphite/messages";
import {
TriggerFetchAndOpenDocument,
TriggerSaveDocument,
TriggerExportImage,
TriggerSaveFile,
TriggerImport,
TriggerOpen,
UpdateActiveDocument,
UpdateOpenDocumentsList,
UpdateDataPanelState,
UpdatePropertiesPanelState,
UpdateLayersPanelState,
} from "@graphite/messages";
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
import { rasterizeSVG } from "@graphite/utility-functions/rasterization";
@@ -29,13 +16,13 @@ export function createPortfolioState(editor: Editor) {
});
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateOpenDocumentsList, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateOpenDocumentsList", (data) => {
update((state) => {
state.documents = data.openDocuments;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateActiveDocument", (data) => {
update((state) => {
// Assume we receive a correct document id
const activeId = state.documents.findIndex((doc) => doc.id === data.documentId);
@@ -43,7 +30,7 @@ export function createPortfolioState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(TriggerFetchAndOpenDocument, async (data) => {
editor.subscriptions.subscribeFrontendMessage("TriggerFetchAndOpenDocument", async (data) => {
try {
const url = new URL(`demo-artwork/${data.filename}`, document.location.href);
const response = await fetch(url);
@@ -55,22 +42,22 @@ export function createPortfolioState(editor: Editor) {
}, 0);
}
});
editor.subscriptions.subscribeJsMessage(TriggerOpen, async () => {
editor.subscriptions.subscribeFrontendMessage("TriggerOpen", async () => {
const data = await upload(`image/*,.${editor.handle.fileExtension()}`, "data");
editor.handle.openFile(data.filename, data.content);
});
editor.subscriptions.subscribeJsMessage(TriggerImport, async () => {
editor.subscriptions.subscribeFrontendMessage("TriggerImport", async () => {
// 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");
editor.handle.importFile(data.filename, data.content);
});
editor.subscriptions.subscribeJsMessage(TriggerSaveDocument, (data) => {
editor.subscriptions.subscribeFrontendMessage("TriggerSaveDocument", (data) => {
downloadFile(data.name, data.content);
});
editor.subscriptions.subscribeJsMessage(TriggerSaveFile, (data) => {
editor.subscriptions.subscribeFrontendMessage("TriggerSaveFile", (data) => {
downloadFile(data.name, data.content);
});
editor.subscriptions.subscribeJsMessage(TriggerExportImage, async (data) => {
editor.subscriptions.subscribeFrontendMessage("TriggerExportImage", async (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)
@@ -78,7 +65,7 @@ export function createPortfolioState(editor: Editor) {
// Rasterize the SVG to an image file
try {
const blob = await rasterizeSVG(svg, size.x, size.y, mime, backgroundColor);
const blob = await rasterizeSVG(svg, size[0], size[1], mime, backgroundColor);
// Have the browser download the file to the user's disk
downloadFileBlob(name, blob);
@@ -86,19 +73,19 @@ export function createPortfolioState(editor: Editor) {
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
}
});
editor.subscriptions.subscribeJsMessage(UpdateDataPanelState, async (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateDataPanelState", async (data) => {
update((state) => {
state.dataPanelOpen = data.open;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelState, async (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdatePropertiesPanelState", async (data) => {
update((state) => {
state.propertiesPanelOpen = data.open;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelState, async (data) => {
editor.subscriptions.subscribeFrontendMessage("UpdateLayersPanelState", async (data) => {
update((state) => {
state.layersPanelOpen = data.open;
return state;
+5 -5
View File
@@ -1,7 +1,7 @@
import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { SendShortcutAltClick, SendShortcutFullscreen, SendShortcutShiftClick, type ActionShortcut } from "@graphite/messages";
import type { Editor } from "@graphite/editor";
import type { ActionShortcut } from "@graphite/messages";
import { operatingSystem } from "@graphite/utility-functions/platform";
const SHOW_TOOLTIP_DELAY_MS = 500;
@@ -65,19 +65,19 @@ export function createTooltipState(editor: Editor) {
document.addEventListener("keydown", closeTooltip);
document.addEventListener("wheel", closeTooltip);
editor.subscriptions.subscribeJsMessage(SendShortcutShiftClick, async (data) => {
editor.subscriptions.subscribeFrontendMessage("SendShortcutShiftClick", async (data) => {
update((state) => {
state.shiftClickShortcut = data.shortcut;
return state;
});
});
editor.subscriptions.subscribeJsMessage(SendShortcutAltClick, async (data) => {
editor.subscriptions.subscribeFrontendMessage("SendShortcutAltClick", async (data) => {
update((state) => {
state.altClickShortcut = data.shortcut;
return state;
});
});
editor.subscriptions.subscribeJsMessage(SendShortcutFullscreen, async (data) => {
editor.subscriptions.subscribeFrontendMessage("SendShortcutFullscreen", async (data) => {
update((state) => {
state.fullscreenShortcut = operatingSystem() === "Mac" ? data.shortcutMac : data.shortcut;
return state;