Replace the Vue frontend with Svelte

This commit is contained in:
Keavon Chambers
2023-03-10 03:54:39 -08:00
parent e539e43483
commit 6e20ea538b
83 changed files with 10013 additions and 19687 deletions
+25 -17
View File
@@ -1,4 +1,4 @@
import { reactive, readonly } from "vue";
import {writable} from "svelte/store";
import { type IconName } from "@/utility-functions/icons";
import { type Editor } from "@/wasm-communication/editor";
@@ -6,7 +6,7 @@ import { type TextButtonWidget, type WidgetLayout, defaultWidgetLayout, DisplayD
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createDialogState(editor: Editor) {
const state = reactive({
const { subscribe, update } = writable({
visible: false,
icon: "" as IconName,
widgets: defaultWidgetLayout(),
@@ -15,37 +15,45 @@ export function createDialogState(editor: Editor) {
});
function dismissDialog(): void {
state.visible = false;
}
function dialogIsVisible(): boolean {
return state.visible;
update((state) => {
state.visible = false;
return state;
});
}
// Creates a panic dialog from JS.
// Normal dialogs are created in the Rust backend, but for the crash dialog, the editor instance has panicked so it cannot respond to widget callbacks.
function createPanicDialog(icon: IconName, widgets: WidgetLayout, jsCallbackBasedButtons: TextButtonWidget[]): void {
state.visible = true;
state.icon = icon;
state.widgets = widgets;
state.jsCallbackBasedButtons = jsCallbackBasedButtons;
update((state) => {
state.visible = true;
state.icon = icon;
state.widgets = widgets;
state.jsCallbackBasedButtons = jsCallbackBasedButtons;
return state;
});
}
// Subscribe to process backend events
editor.subscriptions.subscribeJsMessage(DisplayDialog, (displayDialog) => {
state.visible = true;
state.icon = displayDialog.icon;
update((state) => {
state.visible = true;
state.icon = displayDialog.icon;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogDetails, (updateDialogDetails) => {
patchWidgetLayout(state.widgets, updateDialogDetails);
state.jsCallbackBasedButtons = undefined;
update((state) => {
patchWidgetLayout(state.widgets, updateDialogDetails);
state.jsCallbackBasedButtons = undefined;
return state;
});
});
editor.subscriptions.subscribeJsMessage(DisplayDialogDismiss, dismissDialog);
return {
state: readonly(state) as typeof state,
subscribe,
dismissDialog,
dialogIsVisible,
createPanicDialog,
};
}
+50 -13
View File
@@ -1,9 +1,11 @@
import { nextTick, reactive, readonly } from "vue";
import {tick} from "svelte";
import {writable} from "svelte/store";
import { type Editor } from "@/wasm-communication/editor";
import {
defaultWidgetLayout,
patchWidgetLayout,
TriggerRefreshBoundsOfViewports,
UpdateDocumentBarLayout,
UpdateDocumentModeLayout,
UpdateToolOptionsLayout,
@@ -13,7 +15,7 @@ import {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createDocumentState(editor: Editor) {
const state = reactive({
const state = writable({
// Layouts
documentModeLayout: defaultWidgetLayout(),
toolOptionsLayout: defaultWidgetLayout(),
@@ -21,31 +23,66 @@ export function createDocumentState(editor: Editor) {
toolShelfLayout: defaultWidgetLayout(),
workingColorsLayout: defaultWidgetLayout(),
});
const { subscribe, update } = state;
// Update layouts
editor.subscriptions.subscribeJsMessage(UpdateDocumentModeLayout, async (updateDocumentModeLayout) => {
await nextTick();
patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout);
await tick();
update((state) => {
// `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateToolOptionsLayout, async (updateToolOptionsLayout) => {
await nextTick();
patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout);
await tick();
update((state) => {
// `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentBarLayout, async (updateDocumentBarLayout) => {
await nextTick();
patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout);
await tick();
update((state) => {
// `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateToolShelfLayout, async (updateToolShelfLayout) => {
await nextTick();
patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout);
await tick();
update((state) => {
// `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateWorkingColorsLayout, async (updateWorkingColorsLayout) => {
await nextTick();
patchWidgetLayout(state.workingColorsLayout, updateWorkingColorsLayout);
await tick();
update((state) => {
// `state.documentModeLayout` is mutated in the function
patchWidgetLayout(state.workingColorsLayout, updateWorkingColorsLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(TriggerRefreshBoundsOfViewports, async () => {
// Wait to display the unpopulated document panel (missing: tools, options bar content, scrollbar positioning, and canvas)
await tick();
// Wait to display the populated document panel
await tick();
// Request a resize event so the viewport gets measured now that the canvas is populated and positioned correctly
window.dispatchEvent(new CustomEvent("resize"));
});
return {
state: readonly(state) as typeof state,
subscribe,
};
}
export type DocumentState = ReturnType<typeof createDocumentState>;
+9 -3
View File
@@ -1,11 +1,12 @@
import { reactive } from "vue";
import { writable } from "svelte/store";
import { type Editor } from "@/wasm-communication/editor";
import { TriggerFontLoad } from "@/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createFontsState(editor: Editor) {
const state = reactive({});
// TODO: Do some code cleanup to remove the need for this empty store
const { subscribe } = writable({});
function createURL(font: string): URL {
const url = new URL("https://fonts.googleapis.com/css2");
@@ -73,7 +74,12 @@ export function createFontsState(editor: Editor) {
});
});
return { state, fontNames, getFontStyles, getFontFileUrl };
return {
subscribe,
fontNames,
getFontStyles,
getFontFileUrl,
};
}
export type FontsState = ReturnType<typeof createFontsState>;
+21 -8
View File
@@ -1,17 +1,20 @@
import { reactive, readonly } from "vue";
import {writable} from "svelte/store";
import { type Editor } from "@/wasm-communication/editor";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createFullscreenState(_: Editor) {
const state = reactive({
const { subscribe, update } = writable({
windowFullscreen: false,
keyboardLocked: false,
});
function fullscreenModeChanged(): void {
state.windowFullscreen = Boolean(document.fullscreenElement);
if (!state.windowFullscreen) state.keyboardLocked = false;
update((state) => {
state.windowFullscreen = Boolean(document.fullscreenElement);
if (!state.windowFullscreen) state.keyboardLocked = false;
return state;
});
}
async function enterFullscreen(): Promise<void> {
@@ -20,7 +23,11 @@ export function createFullscreenState(_: Editor) {
if (keyboardLockApiSupported) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await (navigator as any).keyboard.lock(["ControlLeft", "ControlRight"]);
state.keyboardLocked = true;
update((state) => {
state.keyboardLocked = true;
return state;
});
}
}
@@ -29,8 +36,14 @@ export function createFullscreenState(_: Editor) {
}
async function toggleFullscreen(): Promise<void> {
if (state.windowFullscreen) await exitFullscreen();
else await enterFullscreen();
return new Promise((resolve, reject) => {
update((state) => {
if (state.windowFullscreen) exitFullscreen().then(resolve).catch(reject);
else enterFullscreen().then(resolve).catch(reject);
return state;
});
});
}
// Experimental Keyboard API: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/keyboard
@@ -38,7 +51,7 @@ export function createFullscreenState(_: Editor) {
const keyboardLockApiSupported: Readonly<boolean> = "keyboard" in navigator && (navigator as any).keyboard && "lock" in (navigator as any).keyboard;
return {
state: readonly(state) as typeof state,
subscribe,
fullscreenModeChanged,
enterFullscreen,
exitFullscreen,
+21 -8
View File
@@ -1,4 +1,5 @@
import { reactive, readonly } from "vue";
import {tick} from "svelte";
import {writable} from "svelte/store";
import { type Editor } from "@/wasm-communication/editor";
import {
@@ -15,7 +16,7 @@ import {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createNodeGraphState(editor: Editor) {
const state = reactive({
const { subscribe, update } = writable({
nodes: [] as FrontendNode[],
links: [] as FrontendNodeLink[],
nodeTypes: [] as FrontendNodeType[],
@@ -25,21 +26,33 @@ export function createNodeGraphState(editor: Editor) {
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
state.nodes = updateNodeGraph.nodes;
state.links = updateNodeGraph.links;
update((state) => {
state.nodes = updateNodeGraph.nodes;
state.links = updateNodeGraph.links;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeTypes, (updateNodeTypes) => {
state.nodeTypes = updateNodeTypes.nodeTypes;
update((state) => {
state.nodeTypes = updateNodeTypes.nodeTypes;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
update((state) => {
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
update((state) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
return state;
});
});
return {
state: readonly(state) as typeof state,
subscribe,
};
}
export type NodeGraphState = ReturnType<typeof createNodeGraphState>;
-114
View File
@@ -1,114 +0,0 @@
import { nextTick, reactive, readonly } from "vue";
import { type Editor } from "@/wasm-communication/editor";
import {
DisplayEditableTextbox,
DisplayRemoveEditableTextbox,
TriggerRefreshBoundsOfViewports,
TriggerTextCommit,
TriggerViewportResize,
UpdateDocumentArtboards,
UpdateDocumentArtwork,
UpdateDocumentOverlays,
UpdateDocumentRulers,
UpdateDocumentScrollbars,
UpdateEyedropperSamplingState,
UpdateMouseCursor,
} from "@/wasm-communication/messages";
import DocumentComponent from "@/components/panels/Document.vue";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createPanelsState(editor: Editor) {
const state = reactive({
documentPanel: DocumentComponent,
});
// We use `any` instead of `typeof DocumentComponent` as a workaround for the fact that calling this function with the `this` argument from within `Document.vue` isn't a compatible type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function registerPanel(type: string, panelComponent: any): void {
state.documentPanel = panelComponent;
}
function subscribeDocumentPanel(): void {
// Update rendered SVGs
editor.subscriptions.subscribeJsMessage(UpdateDocumentArtwork, async (updateDocumentArtwork) => {
await nextTick();
state.documentPanel.updateDocumentArtwork(updateDocumentArtwork.svg);
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentOverlays, async (updateDocumentOverlays) => {
await nextTick();
state.documentPanel.updateDocumentOverlays(updateDocumentOverlays.svg);
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentArtboards, async (updateDocumentArtboards) => {
await nextTick();
state.documentPanel.updateDocumentArtboards(updateDocumentArtboards.svg);
});
editor.subscriptions.subscribeJsMessage(UpdateEyedropperSamplingState, async (updateEyedropperSamplingState) => {
await nextTick();
const { mousePosition, primaryColor, secondaryColor, setColorChoice } = updateEyedropperSamplingState;
const rgb = (await state.documentPanel.updateEyedropperSamplingState(mousePosition, primaryColor, secondaryColor)) as [number, number, number] | undefined;
if (setColorChoice && rgb) {
if (setColorChoice === "Primary") editor.instance.updatePrimaryColor(...rgb, 1);
if (setColorChoice === "Secondary") editor.instance.updateSecondaryColor(...rgb, 1);
}
});
// Update scrollbars and rulers
editor.subscriptions.subscribeJsMessage(UpdateDocumentScrollbars, async (updateDocumentScrollbars) => {
await nextTick();
const { position, size, multiplier } = updateDocumentScrollbars;
state.documentPanel.updateDocumentScrollbars(position, size, multiplier);
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentRulers, async (updateDocumentRulers) => {
await nextTick();
const { origin, spacing, interval } = updateDocumentRulers;
state.documentPanel.updateDocumentRulers(origin, spacing, interval);
});
// Update mouse cursor icon
editor.subscriptions.subscribeJsMessage(UpdateMouseCursor, async (updateMouseCursor) => {
await nextTick();
const { cursor } = updateMouseCursor;
state.documentPanel.updateMouseCursor(cursor);
});
// Text entry
editor.subscriptions.subscribeJsMessage(TriggerTextCommit, async () => {
await nextTick();
state.documentPanel.triggerTextCommit();
});
editor.subscriptions.subscribeJsMessage(DisplayEditableTextbox, async (displayEditableTextbox) => {
await nextTick();
state.documentPanel.displayEditableTextbox(displayEditableTextbox);
});
editor.subscriptions.subscribeJsMessage(DisplayRemoveEditableTextbox, async () => {
await nextTick();
state.documentPanel.displayRemoveEditableTextbox();
});
// Resize elements to render the new viewport size
editor.subscriptions.subscribeJsMessage(TriggerViewportResize, async () => {
await nextTick();
state.documentPanel.viewportResize();
});
editor.subscriptions.subscribeJsMessage(TriggerRefreshBoundsOfViewports, async () => {
// Wait to display the unpopulated document panel (missing: tools, options bar content, scrollbar positioning, and canvas)
await nextTick();
// Wait to display the populated document panel
await nextTick();
// Request a resize event so the viewport gets measured now that the canvas is populated and positioned correctly
window.dispatchEvent(new CustomEvent("resize"));
});
}
subscribeDocumentPanel();
return {
state: readonly(state) as typeof state,
registerPanel,
};
}
export type PanelsState = ReturnType<typeof createPanelsState>;
+14 -7
View File
@@ -1,5 +1,6 @@
/* eslint-disable max-classes-per-file */
import { reactive, readonly } from "vue";
import {writable} from "svelte/store";
import { downloadFileText, downloadFileBlob, upload } from "@/utility-functions/files";
import { imaginateGenerate, imaginateCheckConnection, imaginateTerminate, updateBackendImage } from "@/utility-functions/imaginate";
@@ -23,7 +24,7 @@ import {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createPortfolioState(editor: Editor) {
const state = reactive({
const { subscribe, update } = writable({
unsaved: false,
documents: [] as FrontendDocumentDetails[],
activeDocumentIndex: 0,
@@ -31,12 +32,18 @@ export function createPortfolioState(editor: Editor) {
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateOpenDocumentsList, (updateOpenDocumentList) => {
state.documents = updateOpenDocumentList.openDocuments;
update((state) => {
state.documents = updateOpenDocumentList.openDocuments;
return state;
})
});
editor.subscriptions.subscribeJsMessage(UpdateActiveDocument, (updateActiveDocument) => {
// Assume we receive a correct document id
const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId);
state.activeDocumentIndex = activeId;
update((state) => {
// Assume we receive a correct document id
const activeId = state.documents.findIndex((doc) => doc.id === updateActiveDocument.documentId);
state.activeDocumentIndex = activeId;
return state;
})
});
editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => {
const extension = editor.instance.fileSaveSuffix();
@@ -122,7 +129,7 @@ export function createPortfolioState(editor: Editor) {
});
return {
state: readonly(state) as typeof state,
subscribe,
};
}
export type PortfolioState = ReturnType<typeof createPortfolioState>;
+11 -5
View File
@@ -1,25 +1,31 @@
/* eslint-disable max-classes-per-file */
import { nextTick, reactive, readonly } from "vue";
import {tick} from "svelte";
import {writable} from "svelte/store";
import { type Editor } from "@/wasm-communication/editor";
import { UpdateNodeGraphVisibility } from "@/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createWorkspaceState(editor: Editor) {
const state = reactive({
const { subscribe, update } = writable({
nodeGraphVisible: false,
});
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphVisibility, async (updateNodeGraphVisibility) => {
state.nodeGraphVisible = updateNodeGraphVisibility.visible;
update((state) => {
state.nodeGraphVisible = updateNodeGraphVisibility.visible;
return state;
});
// Update the viewport bounds
await nextTick();
await tick();
window.dispatchEvent(new Event("resize"));
});
return {
state: readonly(state) as typeof state,
subscribe,
};
}
export type WorkspaceState = ReturnType<typeof createWorkspaceState>;