Refactor the TypeScript data flow for full type safety and auto-generation of Rust types (#3865)

* Migrate Specta to Tsify to auto-generate messages.ts, working except colors and widgets

* Adopt the generated FillColor/Color/GradientStops

* Fix widget typing

* Separate WidgetGroup enum variants into wrapper structs

* Small rename

* Simplify widgets further

* Clean up message type references

* Switch type imports to the auto-generated file

* Remove lowercase serde rename

* Fix FillChoice deserialization

* Fix small regression from #3837

* Improve type safety

* Make WidgetSpan type-safe

* More cleanup and type safety

* More type safety

* More type safety

* Get the rest to type-check without errors; improve widget builder macro to have optional icons; improve Svelte 5 configs

* Cargo fmt

* Fix imports

* Update outdated readme info

* Fix lint command rename references

* Fix typos

* One more typos fix

* Remove unnecessary dep: prefix from the edited Cargo.toml files

* Remove excess parts from Cargo.toml

* Fix compiling on desktop

* Revert "Remove excess parts from Cargo.toml"

This reverts commit 6b711117b3a5d5d8a3ee20f36a43bc74930b7c82.

* Update dev docs with simpler, more accurate instructions
This commit is contained in:
Keavon Chambers
2026-03-09 16:35:04 -07:00
parent fbd2658148
commit 52d2b38a82
199 changed files with 2265 additions and 2811 deletions
@@ -1,10 +1,10 @@
<script lang="ts">
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 { isDesktop } from "@graphite/utility-functions/platform";
import Dialog from "@graphite/components/floating-menus/Dialog.svelte";
import Tooltip from "@graphite/components/floating-menus/Tooltip.svelte";
@@ -31,7 +31,7 @@
{#if $tooltip.visible}
<Tooltip />
{/if}
{#if isDesktop() && new Date() > new Date("2026-03-15")}
{#if isPlatformNative() && new Date() > new Date("2026-03-15")}
<LayoutCol class="release-candidate-expiry">
<TextLabel>
<p>
+11 -25
View File
@@ -1,9 +1,19 @@
<script lang="ts" context="module">
<script lang="ts">
import { getContext, tick } from "svelte";
import type { Editor } from "@graphite/editor";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import Data from "@graphite/components/panels/Data.svelte";
import Document from "@graphite/components/panels/Document.svelte";
import Layers from "@graphite/components/panels/Layers.svelte";
import Properties from "@graphite/components/panels/Properties.svelte";
import Welcome from "@graphite/components/panels/Welcome.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
type PanelType = keyof typeof PANEL_COMPONENTS;
const PANEL_COMPONENTS = {
Welcome,
@@ -12,20 +22,6 @@
Properties,
Data,
};
type PanelType = keyof typeof PANEL_COMPONENTS;
</script>
<script lang="ts">
import { getContext, tick } from "svelte";
import type { Editor } from "@graphite/editor";
import { isEventSupported } from "@graphite/utility-functions/platform";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const BUTTON_LEFT = 0;
const BUTTON_MIDDLE = 1;
@@ -80,16 +76,6 @@
closeAction?.(tabIndex);
}
}}
on:mouseup={(e) => {
// Middle mouse button click fallback for Safari:
// https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event#browser_compatibility
// The downside of using mouseup is that the mousedown didn't have to originate in the same element.
// A possible future improvement could save the target element during mousedown and check if it's the same here.
if (!isEventSupported("auxclick") && e.button === BUTTON_MIDDLE) {
e.stopPropagation();
closeAction?.(tabIndex);
}
}}
bind:this={tabElements[tabIndex]}
>
<LayoutRow class="name">
@@ -1,8 +1,8 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { Layout } from "@graphite/messages";
import { patchLayout } from "@graphite/utility-functions/widgets";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -1,8 +1,9 @@
<script lang="ts">
import { getContext, onMount } 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 { Layout } from "@graphite/messages";
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";
@@ -11,7 +12,6 @@
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";
import { isDesktop } from "/src/utility-functions/platform";
const appWindow = getContext<AppWindowState>("appWindow");
const editor = getContext<Editor>("editor");
@@ -20,8 +20,8 @@
let menuBarLayout: Layout = [];
$: showFullscreenButton = $appWindow.platform === "Web" || $fullscreen.windowFullscreen || (isDesktop() && $appWindow.fullscreen);
$: isFullscreen = isDesktop() ? $appWindow.fullscreen : $fullscreen.windowFullscreen;
$: showFullscreenButton = $appWindow.platform === "Web" || $fullscreen.windowFullscreen || (isPlatformNative() && $appWindow.fullscreen);
$: isFullscreen = isPlatformNative() ? $appWindow.fullscreen : $fullscreen.windowFullscreen;
// On Mac, the menu bar height needs to be scaled by the inverse of the UI scale to fit its native window buttons
$: height = $appWindow.platform === "Mac" ? 28 * (1 / $appWindow.uiScale) : 28;
@@ -53,7 +53,7 @@
: undefined}
tooltipShortcut={$tooltip.fullscreenShortcut}
on:click={() => {
if (isDesktop()) editor.handle.appWindowFullscreen();
if (isPlatformNative()) editor.handle.appWindowFullscreen();
else ($fullscreen.windowFullscreen ? fullscreen.exitFullscreen : fullscreen.enterFullscreen)();
}}
>
+14 -10
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { getContext } from "svelte";
import type { OpenDocument } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import type { OpenDocument } from "@graphite/messages";
import type { PortfolioState } from "@graphite/state-providers/portfolio";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@@ -18,9 +18,9 @@
/* └─ */ details: 20,
/* ├─ */ properties: 45,
/* └─ */ layers: 55,
};
} as const;
let panelSizes = PANEL_SIZES;
let panelSizes: Record<string, number> = PANEL_SIZES;
let documentPanel: Panel | undefined;
let gutterResizeRestore: [number, number] | undefined = undefined;
let pointerCaptureId: number | undefined = undefined;
@@ -40,15 +40,19 @@
const portfolio = getContext<PortfolioState>("portfolio");
function resizePanel(e: PointerEvent) {
const gutter = (e.target || undefined) as HTMLDivElement | undefined;
const nextSibling = (gutter?.nextElementSibling || undefined) as HTMLDivElement | undefined;
const prevSibling = (gutter?.previousElementSibling || undefined) as HTMLDivElement | undefined;
const parentElement = (gutter?.parentElement || undefined) as HTMLDivElement | undefined;
const gutter = e.target;
if (!(gutter instanceof HTMLDivElement)) return;
const nextSiblingName = (nextSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES;
const prevSiblingName = (prevSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES;
const nextSibling = gutter.nextElementSibling;
const prevSibling = gutter.previousElementSibling;
if (!gutter || !nextSibling || !prevSibling || !parentElement || !nextSiblingName || !prevSiblingName) return;
const parentElement = gutter.parentElement;
if (!(nextSibling instanceof HTMLDivElement) || !(prevSibling instanceof HTMLDivElement) || !(parentElement instanceof HTMLDivElement)) return;
const nextSiblingName = nextSibling.getAttribute("data-subdivision-name") || undefined;
const prevSiblingName = prevSibling.getAttribute("data-subdivision-name") || undefined;
if (!nextSiblingName || !prevSiblingName || !(nextSiblingName in PANEL_SIZES) || !(prevSiblingName in PANEL_SIZES)) return;
// Are we resizing horizontally?
const isHorizontal = gutter.getAttribute("data-gutter-horizontal") !== null;