Unwrap the Layout enum to replace it with the WidgetLayout struct now called Layout (#3448)

This commit is contained in:
Keavon Chambers
2025-12-04 15:24:40 -08:00
committed by GitHub
parent 3c4ad8b720
commit 783ea0b437
49 changed files with 205 additions and 254 deletions
+3 -3
View File
@@ -2,18 +2,18 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Editor } from "@graphite/editor";
import { patchWidgetLayout, UpdateDataPanelLayout, type LayoutGroup } from "@graphite/messages";
import { patchLayout, UpdateDataPanelLayout, type Layout } from "@graphite/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
let dataPanelLayout: LayoutGroup[] = [];
let dataPanelLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateDataPanelLayout, (updateDataPanelLayout) => {
patchWidgetLayout(dataPanelLayout, updateDataPanelLayout);
patchLayout(dataPanelLayout, updateDataPanelLayout);
dataPanelLayout = dataPanelLayout;
});
});
+8 -8
View File
@@ -4,14 +4,14 @@
import { shortcutAltClick } from "@graphite/../wasm/pkg/graphite_wasm";
import type { Editor } from "@graphite/editor";
import {
patchWidgetLayout,
patchLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerStructureJs,
UpdateLayersPanelControlBarLeftLayout,
UpdateLayersPanelControlBarRightLayout,
UpdateLayersPanelBottomBarLayout,
} from "@graphite/messages";
import type { ActionShortcut, DataBuffer, LayerPanelEntry, LayoutGroup } from "@graphite/messages";
import type { ActionShortcut, DataBuffer, LayerPanelEntry, Layout } from "@graphite/messages";
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import { operatingSystem } from "@graphite/utility-functions/platform";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
@@ -69,25 +69,25 @@
let layerToClipAltKeyPressed = false;
// Layouts
let layersPanelControlBarLeftLayout: LayoutGroup[] = [];
let layersPanelControlBarRightLayout: LayoutGroup[] = [];
let layersPanelBottomBarLayout: LayoutGroup[] = [];
let layersPanelControlBarLeftLayout: Layout = [];
let layersPanelControlBarRightLayout: Layout = [];
let layersPanelBottomBarLayout: Layout = [];
const altClickKeys: ActionShortcut = shortcutAltClick();
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarLeftLayout, (updateLayersPanelControlBarLeftLayout) => {
patchWidgetLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
patchLayout(layersPanelControlBarLeftLayout, updateLayersPanelControlBarLeftLayout);
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
});
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelControlBarRightLayout, (updateLayersPanelControlBarRightLayout) => {
patchWidgetLayout(layersPanelControlBarRightLayout, updateLayersPanelControlBarRightLayout);
patchLayout(layersPanelControlBarRightLayout, updateLayersPanelControlBarRightLayout);
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
});
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelBottomBarLayout, (updateLayersPanelBottomBarLayout) => {
patchWidgetLayout(layersPanelBottomBarLayout, updateLayersPanelBottomBarLayout);
patchLayout(layersPanelBottomBarLayout, updateLayersPanelBottomBarLayout);
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
});
@@ -2,18 +2,18 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Editor } from "@graphite/editor";
import { patchWidgetLayout, UpdatePropertiesPanelLayout, type LayoutGroup } from "@graphite/messages";
import { patchLayout, UpdatePropertiesPanelLayout, type Layout } from "@graphite/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
let propertiesPanelLayout: LayoutGroup[] = [];
let propertiesPanelLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelLayout, (updatePropertiesPanelLayout) => {
patchWidgetLayout(propertiesPanelLayout, updatePropertiesPanelLayout);
patchLayout(propertiesPanelLayout, updatePropertiesPanelLayout);
propertiesPanelLayout = propertiesPanelLayout;
});
});
@@ -2,8 +2,8 @@
import { getContext, onMount, onDestroy } from "svelte";
import type { Editor } from "@graphite/editor";
import type { LayoutGroup } from "@graphite/messages";
import { patchWidgetLayout, UpdateWelcomeScreenButtonsLayout } from "@graphite/messages";
import type { Layout } from "@graphite/messages";
import { patchLayout, UpdateWelcomeScreenButtonsLayout } from "@graphite/messages";
import { extractPixelData } from "@graphite/utility-functions/rasterization";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@@ -14,11 +14,11 @@
const editor = getContext<Editor>("editor");
let welcomePanelButtonsLayout: LayoutGroup[] = [];
let welcomePanelButtonsLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateWelcomeScreenButtonsLayout, (updateWelcomeScreenButtonsLayout) => {
patchWidgetLayout(welcomePanelButtonsLayout, updateWelcomeScreenButtonsLayout);
patchLayout(welcomePanelButtonsLayout, updateWelcomeScreenButtonsLayout);
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
});
@@ -1,11 +1,11 @@
<script lang="ts">
import { isWidgetSpanColumn, isWidgetSpanRow, isWidgetSection, type LayoutGroup, isWidgetTable, type LayoutTarget } from "@graphite/messages";
import { isWidgetSpanColumn, isWidgetSpanRow, isWidgetSection, type Layout, isWidgetTable, type LayoutTarget } from "@graphite/messages";
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
import WidgetTable from "@graphite/components/widgets/WidgetTable.svelte";
export let layout: LayoutGroup[];
export let layout: Layout;
export let layoutTarget: LayoutTarget;
let className = "";
export { className as class };
@@ -2,7 +2,7 @@
import { getContext } from "svelte";
import type { Editor } from "@graphite/editor";
import type { LayoutTarget, Widget, WidgetSpanColumn, WidgetSpanRow } from "@graphite/messages";
import type { LayoutTarget, WidgetInstance, WidgetSpanColumn, WidgetSpanRow } from "@graphite/messages";
import { narrowWidgetProps, isWidgetSpanColumn, isWidgetSpanRow } from "@graphite/messages";
import { debouncer } from "@graphite/utility-functions/debounce";
@@ -54,8 +54,8 @@
if (isWidgetSpanColumn(widgetData)) return "column";
}
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): Widget[] {
let widgets: Widget[] = [];
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): WidgetInstance[] {
let widgets: WidgetInstance[] = [];
if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets;
else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets;
return widgets;
@@ -2,19 +2,19 @@
import { getContext, onMount } from "svelte";
import type { Editor } from "@graphite/editor";
import type { LayoutGroup } from "@graphite/messages";
import { patchWidgetLayout, UpdateStatusBarHintsLayout } from "@graphite/messages";
import type { Layout } from "@graphite/messages";
import { patchLayout, UpdateStatusBarHintsLayout } from "@graphite/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
const editor = getContext<Editor>("editor");
let statusBarHintsLayout: LayoutGroup[] = [];
let statusBarHintsLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateStatusBarHintsLayout, (updateStatusBarHintsLayout) => {
patchWidgetLayout(statusBarHintsLayout, updateStatusBarHintsLayout);
patchLayout(statusBarHintsLayout, updateStatusBarHintsLayout);
statusBarHintsLayout = statusBarHintsLayout;
});
});
@@ -2,8 +2,8 @@
import { getContext, onMount } from "svelte";
import type { Editor } from "@graphite/editor";
import type { LayoutGroup } from "@graphite/messages";
import { patchWidgetLayout, UpdateMenuBarLayout } from "@graphite/messages";
import type { Layout } from "@graphite/messages";
import { patchLayout, UpdateMenuBarLayout } from "@graphite/messages";
import type { AppWindowState } from "@graphite/state-providers/app-window";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
@@ -15,11 +15,11 @@
const appWindow = getContext<AppWindowState>("appWindow");
const editor = getContext<Editor>("editor");
let menuBarLayout: LayoutGroup[] = [];
let menuBarLayout: Layout = [];
onMount(() => {
editor.subscriptions.subscribeJsMessage(UpdateMenuBarLayout, (updateMenuBarLayout) => {
patchWidgetLayout(menuBarLayout, updateMenuBarLayout);
patchLayout(menuBarLayout, updateMenuBarLayout);
menuBarLayout = menuBarLayout;
});
});
+30 -38
View File
@@ -1218,7 +1218,7 @@ export class PopoverButton extends WidgetProps {
tooltipShortcut!: ActionShortcut | undefined;
// Body
popoverLayout!: LayoutGroup[];
popoverLayout!: Layout;
popoverMinWidth: number | undefined;
}
@@ -1453,19 +1453,14 @@ export function narrowWidgetProps<K extends WidgetPropsNames>(props: WidgetProps
else return undefined;
}
export class Widget {
constructor(props: WidgetPropsSet, widgetId: bigint) {
this.props = props;
this.widgetId = widgetId;
}
export class WidgetInstance {
@Type(() => WidgetProps, { discriminator: { property: "kind", subTypes: [...widgetSubTypes] }, keepDiscriminatorProperty: true })
props!: WidgetPropsSet;
widgetId!: bigint;
}
function hoistWidgetInstance(widgetInstance: any): Widget {
function hoistWidgetInstance(widgetInstance: any): WidgetInstance {
const kind = Object.keys(widgetInstance.widget)[0];
const props = widgetInstance.widget[kind];
props.kind = kind;
@@ -1476,10 +1471,10 @@ function hoistWidgetInstance(widgetInstance: any): Widget {
const { widgetId } = widgetInstance;
return plainToClass(Widget, { props, widgetId });
return plainToClass(WidgetInstance, { props, widgetId });
}
function hoistWidgetInstances(widgetInstance: any[]): Widget[] {
function hoistWidgetInstances(widgetInstance: any[]): WidgetInstance[] {
return widgetInstance.map(hoistWidgetInstance);
}
@@ -1506,15 +1501,29 @@ export type LayoutTarget =
export class WidgetDiffUpdate extends JsMessage {
// TODO: Replace `any` with correct typing
@Transform(({ value }: { value: any }) => createWidgetDiff(value))
@Transform(({ value }: { value: WidgetDiff[] }) => {
// Unpacking rust types to more usable type in the frontend
return value.map((diff) => {
const { widgetPath, newValue } = diff;
if ("layout" in newValue) return { widgetPath, newValue: newValue.layout.map(createLayoutGroup) };
if ("layoutGroup" in newValue) return { widgetPath, newValue: createLayoutGroup(newValue.layoutGroup) };
if ("widget" in newValue) return { widgetPath, newValue: hoistWidgetInstance(newValue.widget) };
// This code should be unreachable
throw new Error("DiffUpdate invalid");
});
})
diff!: WidgetDiff[];
}
type UIItem = LayoutGroup[] | LayoutGroup | Widget[] | Widget;
type WidgetDiff = { widgetPath: number[]; newValue: UIItem };
type DiffUpdate = { layout: Layout } | { layoutGroup: LayoutGroup } | { widget: WidgetInstance };
type WidgetDiff = { widgetPath: number[]; newValue: DiffUpdate };
type UIItem = Layout | LayoutGroup | WidgetInstance[] | WidgetInstance;
// Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument
export function patchWidgetLayout(layout: /* &mut */ LayoutGroup[], updates: WidgetDiffUpdate) {
export function patchLayout(layout: /* &mut */ Layout, updates: WidgetDiffUpdate) {
updates.diff.forEach((update) => {
// Find the object where the diff applies to
const diffObject = update.widgetPath.reduce((targetLayout: UIItem | undefined, index: number): UIItem | undefined => {
@@ -1522,7 +1531,7 @@ export function patchWidgetLayout(layout: /* &mut */ LayoutGroup[], updates: Wid
if (targetLayout && "rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
if (targetLayout && "tableWidgets" in targetLayout) return targetLayout.tableWidgets[index];
if (targetLayout && "layout" in targetLayout) return targetLayout.layout[index];
if (targetLayout instanceof Widget) {
if (targetLayout instanceof WidgetInstance) {
if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.popoverLayout) {
return targetLayout.props.popoverLayout[index];
}
@@ -1540,7 +1549,7 @@ export function patchWidgetLayout(layout: /* &mut */ LayoutGroup[], updates: Wid
// tries to update the layout, it attempts to insert only the changes against the old layout that no longer exists.
if (diffObject === undefined) {
// eslint-disable-next-line no-console
console.error("In `patchWidgetLayout`, the `diffObject` is undefined. The layout has not been updated. See the source code comment above this error for hints.");
console.error("In `patchLayout`, the `diffObject` is undefined. The layout has not been updated. See the source code comment above this error for hints.");
return;
}
@@ -1559,45 +1568,28 @@ export function patchWidgetLayout(layout: /* &mut */ LayoutGroup[], updates: Wid
}
export type LayoutGroup = WidgetSpanRow | WidgetSpanColumn | WidgetTable | WidgetSection;
export type Layout = LayoutGroup[];
export type WidgetSpanColumn = { columnWidgets: Widget[] };
export type WidgetSpanColumn = { columnWidgets: WidgetInstance[] };
export function isWidgetSpanColumn(layoutColumn: LayoutGroup): layoutColumn is WidgetSpanColumn {
return Boolean((layoutColumn as WidgetSpanColumn)?.columnWidgets);
}
export type WidgetSpanRow = { rowWidgets: Widget[] };
export type WidgetSpanRow = { rowWidgets: WidgetInstance[] };
export function isWidgetSpanRow(layoutRow: LayoutGroup): layoutRow is WidgetSpanRow {
return Boolean((layoutRow as WidgetSpanRow)?.rowWidgets);
}
export type WidgetTable = { tableWidgets: Widget[][]; unstyled: boolean };
export type WidgetTable = { tableWidgets: WidgetInstance[][]; unstyled: boolean };
export function isWidgetTable(layoutTable: LayoutGroup): layoutTable is WidgetTable {
return Boolean((layoutTable as WidgetTable)?.tableWidgets);
}
export type WidgetSection = { name: string; description: string; visible: boolean; pinned: boolean; id: bigint; layout: LayoutGroup[] };
export type WidgetSection = { name: string; description: string; visible: boolean; pinned: boolean; id: bigint; layout: Layout };
export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSection {
return Boolean((layoutRow as WidgetSection)?.layout);
}
// Unpacking rust types to more usable type in the frontend
function createWidgetDiff(diffs: any[]): WidgetDiff[] {
return diffs.map((diff) => {
const { widgetPath, newValue } = diff;
if (newValue.widgetLayout) {
return { widgetPath, newValue: newValue.widgetLayout.map(createLayoutGroup) };
}
if (newValue.layoutGroup) {
return { widgetPath, newValue: createLayoutGroup(newValue.layoutGroup) };
}
if (newValue.widget) {
return { widgetPath, newValue: hoistWidgetInstance(newValue.widget) };
}
// This code should be unreachable
throw new Error("DiffUpdate invalid");
});
}
// Unpacking a layout group
function createLayoutGroup(layoutGroup: any): LayoutGroup {
if (layoutGroup.column) {
+8 -16
View File
@@ -2,25 +2,17 @@ import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import { type IconName } from "@graphite/icons";
import {
DisplayDialog,
DisplayDialogDismiss,
UpdateDialogButtons,
UpdateDialogColumn1,
UpdateDialogColumn2,
patchWidgetLayout,
TriggerDisplayThirdPartyLicensesDialog,
type LayoutGroup,
} from "@graphite/messages";
import { DisplayDialog, DisplayDialogDismiss, UpdateDialogButtons, UpdateDialogColumn1, UpdateDialogColumn2, patchLayout, TriggerDisplayThirdPartyLicensesDialog } from "@graphite/messages";
import type { Layout } from "@graphite/messages";
export function createDialogState(editor: Editor) {
const { subscribe, update } = writable({
visible: false,
title: "",
icon: "" as IconName,
buttons: [] as LayoutGroup[],
column1: [] as LayoutGroup[],
column2: [] as LayoutGroup[],
buttons: [] as Layout,
column1: [] as Layout,
column2: [] as Layout,
// Special case for the crash dialog because we cannot handle button widget callbacks from Rust once the editor has panicked
panicDetails: "",
});
@@ -65,21 +57,21 @@ export function createDialogState(editor: Editor) {
});
editor.subscriptions.subscribeJsMessage(UpdateDialogButtons, (updateDialogButtons) => {
update((state) => {
patchWidgetLayout(state.buttons, updateDialogButtons);
patchLayout(state.buttons, updateDialogButtons);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogColumn1, (updateDialogColumn1) => {
update((state) => {
patchWidgetLayout(state.column1, updateDialogColumn1);
patchLayout(state.column1, updateDialogColumn1);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateDialogColumn2, (updateDialogColumn2) => {
update((state) => {
patchWidgetLayout(state.column2, updateDialogColumn2);
patchLayout(state.column2, updateDialogColumn2);
return state;
});
+14 -14
View File
@@ -4,7 +4,7 @@ import { writable } from "svelte/store";
import { type Editor } from "@graphite/editor";
import {
patchWidgetLayout,
patchLayout,
UpdateDocumentBarLayout,
UpdateDocumentModeLayout,
UpdateToolOptionsLayout,
@@ -13,18 +13,18 @@ import {
UpdateNodeGraphControlBarLayout,
UpdateGraphViewOverlay,
UpdateGraphFadeArtwork,
type LayoutGroup,
} from "@graphite/messages";
import type { Layout } from "@graphite/messages";
export function createDocumentState(editor: Editor) {
const state = writable({
// Layouts
documentModeLayout: [] as LayoutGroup[],
toolOptionsLayout: [] as LayoutGroup[],
documentBarLayout: [] as LayoutGroup[],
toolShelfLayout: [] as LayoutGroup[],
workingColorsLayout: [] as LayoutGroup[],
nodeGraphControlBarLayout: [] as LayoutGroup[],
documentModeLayout: [] as Layout,
toolOptionsLayout: [] as Layout,
documentBarLayout: [] as Layout,
toolShelfLayout: [] as Layout,
workingColorsLayout: [] as Layout,
nodeGraphControlBarLayout: [] as Layout,
// Graph view overlay
graphViewOverlayOpen: false,
fadeArtwork: 100,
@@ -42,7 +42,7 @@ export function createDocumentState(editor: Editor) {
await tick();
update((state) => {
patchWidgetLayout(state.documentModeLayout, updateDocumentModeLayout);
patchLayout(state.documentModeLayout, updateDocumentModeLayout);
return state;
});
});
@@ -50,7 +50,7 @@ export function createDocumentState(editor: Editor) {
await tick();
update((state) => {
patchWidgetLayout(state.toolOptionsLayout, updateToolOptionsLayout);
patchLayout(state.toolOptionsLayout, updateToolOptionsLayout);
return state;
});
});
@@ -58,7 +58,7 @@ export function createDocumentState(editor: Editor) {
await tick();
update((state) => {
patchWidgetLayout(state.documentBarLayout, updateDocumentBarLayout);
patchLayout(state.documentBarLayout, updateDocumentBarLayout);
return state;
});
});
@@ -66,7 +66,7 @@ export function createDocumentState(editor: Editor) {
await tick();
update((state) => {
patchWidgetLayout(state.toolShelfLayout, updateToolShelfLayout);
patchLayout(state.toolShelfLayout, updateToolShelfLayout);
return state;
});
});
@@ -74,13 +74,13 @@ export function createDocumentState(editor: Editor) {
await tick();
update((state) => {
patchWidgetLayout(state.workingColorsLayout, updateWorkingColorsLayout);
patchLayout(state.workingColorsLayout, updateWorkingColorsLayout);
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphControlBarLayout, (updateNodeGraphControlBarLayout) => {
update((state) => {
patchWidgetLayout(state.nodeGraphControlBarLayout, updateNodeGraphControlBarLayout);
patchLayout(state.nodeGraphControlBarLayout, updateNodeGraphControlBarLayout);
return state;
});
});