Fix frontend HMR so the page doesn't break upon saving TS files and restructure frontend architecture (#3871)

* Clean up component setup/tear-down side effects

* Clean up more component setup/tear-down side effects

* Remove nonfunctional debouncer

* Clean up even more component setup/tear-down side effects

* Reuse backend state

* Fix HMR for IO Managers and for State Providers

* Rename IO Managers -> Managers and State Providers -> Stores

* Restructure and partially flatten managers/stores

* Code review fixes

* Review fixes
This commit is contained in:
Keavon Chambers
2026-03-19 13:38:41 -07:00
committed by GitHub
parent eb562146ce
commit 124b17f609
54 changed files with 1385 additions and 931 deletions
@@ -2,9 +2,9 @@
import { getContext } from "svelte";
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import type { DialogState } from "@graphite/state-providers/dialog";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import type { AppWindowStore } from "@graphite/stores/app-window";
import type { DialogStore } from "@graphite/stores/dialog";
import type { TooltipStore } from "@graphite/stores/tooltip";
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
import Tooltip from "@graphite/components/floating-menus/Tooltip.svelte";
@@ -14,9 +14,9 @@
import TitleBar from "@graphite/components/window/TitleBar.svelte";
import Workspace from "@graphite/components/window/Workspace.svelte";
const dialog = getContext<DialogState>("dialog");
const tooltip = getContext<TooltipState>("tooltip");
const appWindow = getContext<AppWindowState>("appWindow");
const dialog = getContext<DialogStore>("dialog");
const tooltip = getContext<TooltipStore>("tooltip");
const appWindow = getContext<AppWindowStore>("appWindow");
</script>
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": $appWindow.viewportHolePunch }}>
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { getContext, onMount, onDestroy } from "svelte";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
@@ -24,6 +24,11 @@
statusBarInfoLayout = statusBarInfoLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarHints");
editor.subscriptions.unsubscribeLayoutUpdate("StatusBarInfo");
});
</script>
<LayoutRow class="status-bar">
+16 -9
View File
@@ -1,22 +1,25 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { getContext, onMount, onDestroy } from "svelte";
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
import type { TooltipState } from "@graphite/state-providers/tooltip";
import type { AppWindowStore } from "@graphite/stores/app-window";
import { enterFullscreen, exitFullscreen } from "@graphite/stores/fullscreen";
import type { FullscreenStore } from "@graphite/stores/fullscreen";
import type { TooltipStore } from "@graphite/stores/tooltip";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const appWindow = getContext<AppWindowState>("appWindow");
const keyboardLockApiSupported = navigator.keyboard !== undefined && "lock" in navigator.keyboard;
const appWindow = getContext<AppWindowStore>("appWindow");
const editor = getContext<Editor>("editor");
const fullscreen = getContext<FullscreenState>("fullscreen");
const tooltip = getContext<TooltipState>("tooltip");
const fullscreen = getContext<FullscreenStore>("fullscreen");
const tooltip = getContext<TooltipStore>("tooltip");
let menuBarLayout: Layout = [];
@@ -31,6 +34,10 @@
menuBarLayout = menuBarLayout;
});
});
onDestroy(() => {
editor.subscriptions.unsubscribeLayoutUpdate("MenuBar");
});
</script>
<LayoutRow class="title-bar" styles={{ height: height + "px" }}>
@@ -48,13 +55,13 @@
{#if showFullscreenButton}
<LayoutRow
tooltipLabel={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
tooltipDescription={$appWindow.platform === "Web" && $fullscreen.keyboardLockApiSupported
tooltipDescription={$appWindow.platform === "Web" && keyboardLockApiSupported
? "While fullscreen, keyboard shortcuts normally reserved by the browser become available."
: undefined}
tooltipShortcut={$tooltip.fullscreenShortcut}
on:click={() => {
if (isPlatformNative()) editor.handle.appWindowFullscreen();
else ($fullscreen.windowFullscreen ? fullscreen.exitFullscreen : fullscreen.enterFullscreen)();
else ($fullscreen.windowFullscreen ? exitFullscreen : enterFullscreen)();
}}
>
<IconLabel icon={isFullscreen ? "FullscreenExit" : "FullscreenEnter"} />
@@ -1,9 +1,9 @@
<script lang="ts">
import { getContext } from "svelte";
import { getContext, onDestroy } from "svelte";
import type { OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import type { PortfolioStore } from "@graphite/stores/portfolio";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -24,6 +24,11 @@
let documentPanel: Panel | undefined;
let gutterResizeRestore: [number, number] | undefined = undefined;
let pointerCaptureId: number | undefined = undefined;
let activeResizeCleanup: (() => void) | undefined = undefined;
onDestroy(() => {
activeResizeCleanup?.();
});
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
@@ -37,7 +42,7 @@
});
const editor = getContext<Editor>("editor");
const portfolio = getContext<PortfolioState>("portfolio");
const portfolio = getContext<PortfolioStore>("portfolio");
function resizePanel(e: PointerEvent) {
const gutter = e.target;
@@ -76,6 +81,7 @@
const abortResize = () => {
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);
removeListeners();
activeResizeCleanup = undefined;
pointerCaptureId = e.pointerId;
gutter.setPointerCapture(pointerCaptureId);
@@ -104,6 +110,7 @@
gutterResizeRestore = undefined;
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId);
removeListeners();
activeResizeCleanup = undefined;
};
const onMouseDown = (e: MouseEvent) => {
@@ -130,6 +137,7 @@
};
addListeners();
activeResizeCleanup = removeListeners;
}
</script>