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
+27 -29
View File
@@ -1,3 +1,5 @@
import { get } from "svelte/store";
import { type DialogState } from "@/state-providers/dialog";
import { type FullscreenState } from "@/state-providers/fullscreen";
import { type PortfolioState } from "@/state-providers/portfolio";
@@ -14,8 +16,8 @@ type EventListenerTarget = {
removeEventListener: typeof window.removeEventListener;
};
export function createInputManager(editor: Editor, container: HTMLElement, dialog: DialogState, document: PortfolioState, fullscreen: FullscreenState): () => void {
const app = window.document.querySelector("[data-app]") as HTMLElement | undefined;
export function createInputManager(editor: Editor, dialog: DialogState, document: PortfolioState, fullscreen: FullscreenState): () => void {
const app = window.document.querySelector("[data-app-container]") as HTMLElement | undefined;
app?.focus();
let viewportPointerInteractionOngoing = false;
@@ -30,21 +32,20 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: boolean | AddEventListenerOptions }[] = [
{ target: window, eventName: "resize", action: (): void => onWindowResize(container) },
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent): Promise<void> => onBeforeUnload(e) },
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent): void => e.preventDefault() },
{ target: window.document, eventName: "fullscreenchange", action: (): void => fullscreen.fullscreenModeChanged() },
{ target: window, eventName: "keyup", action: (e: KeyboardEvent): Promise<void> => onKeyUp(e) },
{ target: window, eventName: "keydown", action: (e: KeyboardEvent): Promise<void> => onKeyDown(e) },
{ target: window, eventName: "pointermove", action: (e: PointerEvent): void => onPointerMove(e) },
{ target: window, eventName: "pointerdown", action: (e: PointerEvent): void => onPointerDown(e) },
{ target: window, eventName: "pointerup", action: (e: PointerEvent): void => onPointerUp(e) },
{ target: window, eventName: "dblclick", action: (e: PointerEvent): void => onDoubleClick(e) },
{ target: window, eventName: "mousedown", action: (e: MouseEvent): void => onMouseDown(e) },
{ target: window, eventName: "wheel", action: (e: WheelEvent): void => onWheelScroll(e), options: { passive: false } },
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent): void => onModifyInputField(e) },
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent): void => onPaste(e) },
{ target: app as EventListenerTarget, eventName: "blur", action: (): void => blurApp() },
{ target: window, eventName: "resize", action: () => onWindowResize(window.document.body) },
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) },
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => onKeyUp(e) },
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => onKeyDown(e) },
{ target: window, eventName: "pointermove", action: (e: PointerEvent) => onPointerMove(e) },
{ target: window, eventName: "pointerdown", action: (e: PointerEvent) => onPointerDown(e) },
{ target: window, eventName: "pointerup", action: (e: PointerEvent) => onPointerUp(e) },
{ target: window, eventName: "dblclick", action: (e: PointerEvent) => onDoubleClick(e) },
{ target: window, eventName: "wheel", action: (e: WheelEvent) => onWheelScroll(e), options: { passive: false } },
{ target: window, eventName: "modifyinputfield", action: (e: CustomEvent) => onModifyInputField(e) },
{ target: window, eventName: "focusout", action: () => (canvasFocused = false) },
{ target: window.document, eventName: "contextmenu", action: (e: MouseEvent) => e.preventDefault() },
{ target: window.document, eventName: "fullscreenchange", action: () => fullscreen.fullscreenModeChanged() },
{ target: window.document.body, eventName: "paste", action: (e: ClipboardEvent) => onPaste(e) },
];
// Event bindings
@@ -62,7 +63,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
async function shouldRedirectKeyboardEventToBackend(e: KeyboardEvent): Promise<boolean> {
// Don't redirect when a modal is covering the workspace
if (dialog.dialogIsVisible()) return false;
if (get(dialog).visible) return false;
const key = await getLocalizedScanCode(e);
@@ -110,7 +111,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
return;
}
if (dialog.dialogIsVisible() && key === "Escape") {
if (get(dialog).visible && key === "Escape") {
dialog.dismissDialog();
}
}
@@ -155,7 +156,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
const inDialog = target instanceof Element && target.closest("[data-dialog-modal] [data-floating-menu-content]");
const inTextInput = target === textInput;
if (dialog.dialogIsVisible() && !inDialog) {
if (get(dialog).visible && !inDialog) {
dialog.dismissDialog();
e.preventDefault();
e.stopPropagation();
@@ -170,6 +171,9 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
const modifiers = makeKeyboardModifiersBitfield(e);
editor.instance.onMouseDown(e.clientX, e.clientY, e.buttons, modifiers);
}
// Block middle mouse button auto-scroll mode (the circlar widget that appears and allows quick scrolling by moving the cursor above or below it)
if (e.button === 1) e.preventDefault();
}
function onPointerUp(e: PointerEvent): void {
@@ -192,12 +196,6 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
// Mouse events
function onMouseDown(e: MouseEvent): void {
// Block middle mouse button auto-scroll mode (the circlar widget that appears and allows quick scrolling by moving the cursor above or below it)
// This has to be in `mousedown`, not `pointerdown`, to avoid blocking Vue's middle click detection on HTML elements
if (e.button === 1) e.preventDefault();
}
function onWheelScroll(e: WheelEvent): void {
const { target } = e;
const isTargetingCanvas = target instanceof Element && target.closest("[data-canvas]");
@@ -237,7 +235,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
}
async function onBeforeUnload(e: BeforeUnloadEvent): Promise<void> {
const activeDocument = document.state.documents[document.state.activeDocumentIndex];
const activeDocument = get(document).documents[get(document).activeDocumentIndex];
if (activeDocument && !activeDocument.isAutoSaved) editor.instance.triggerAutoSave(activeDocument.id);
// Skip the message if the editor crashed, since work is already lost
@@ -246,7 +244,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
// Skip the message during development, since it's annoying when testing
if (await editor.instance.inDevelopmentMode()) return;
const allDocumentsSaved = document.state.documents.reduce((acc, doc) => acc && doc.isSaved, true);
const allDocumentsSaved = get(document).documents.reduce((acc, doc) => acc && doc.isSaved, true);
if (!allDocumentsSaved) {
e.returnValue = "Unsaved work will be lost if the web browser tab is closed. Close anyway?";
e.preventDefault();
@@ -353,7 +351,7 @@ export function createInputManager(editor: Editor, container: HTMLElement, dialo
// Bind the event listeners
bindListeners();
// Resize on creation
onWindowResize(container);
onWindowResize(window.document.body);
// Return the destructor
return unbindListeners;
+2 -1
View File
@@ -4,7 +4,8 @@ import { type IconName } from "@/utility-functions/icons";
import { browserVersion, operatingSystem } from "@/utility-functions/platform";
import { stripIndents } from "@/utility-functions/strip-indents";
import { type Editor } from "@/wasm-communication/editor";
import { type TextButtonWidget, type TextLabel, type WidgetLayout, Widget, DisplayDialogPanic } from "@/wasm-communication/messages";
import type { TextLabel } from "@/wasm-communication/messages";
import { type TextButtonWidget, type WidgetLayout, Widget, DisplayDialogPanic } from "@/wasm-communication/messages";
export function createPanicManager(editor: Editor, dialogState: DialogState): void {
// Code panic dialog and console error
+2 -1
View File
@@ -1,4 +1,5 @@
import { createStore, del, get, set, update } from "idb-keyval";
import { get as getFromStore } from "svelte/store";
import { type PortfolioState } from "@/state-providers/portfolio";
import { type Editor } from "@/wasm-communication/editor";
@@ -10,7 +11,7 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta
// DOCUMENTS
async function storeDocumentOrder(): Promise<void> {
const documentOrder = portfolio.state.documents.map((doc) => String(doc.id));
const documentOrder = getFromStore(portfolio).documents.map((doc) => String(doc.id));
await set("documents_tab_order", documentOrder, graphiteStore);
}