mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Diff simple layout changes to avoid excessive DOM replacements (#910)
* Add UI diffs to rust * Clean up some js * Fix lints * Fix test * Remove one unnecessary keyword * Rename to widget path * Rename new_val to new_value * Rename newVal to layoutGroup in createLayoutGroup * Extract get_widget_path to a function * Base skipping on the layout rather than the target * Rename to ResendActiveWidget * Switch info to trace * Add a link to the documentation about Object.assign * knitpick js changes * Add more comments to diff functions Co-authored-by: mfish33 <maxmfishernj@gmail.com>
This commit is contained in:
committed by
Keavon Chambers
parent
01931939ff
commit
694890642e
@@ -231,6 +231,7 @@ import { textInputCleanup } from "@/utility-functions/keyboard-entry";
|
||||
import { rasterizeSVGCanvas } from "@/utility-functions/rasterization";
|
||||
import {
|
||||
defaultWidgetLayout,
|
||||
patchWidgetLayout,
|
||||
type DisplayEditableTextbox,
|
||||
type MouseCursorIcon,
|
||||
type UpdateDocumentBarLayout,
|
||||
@@ -505,19 +506,19 @@ export default defineComponent({
|
||||
},
|
||||
// Update layouts
|
||||
updateDocumentModeLayout(updateDocumentModeLayout: UpdateDocumentModeLayout) {
|
||||
this.documentModeLayout = updateDocumentModeLayout;
|
||||
patchWidgetLayout(this.documentModeLayout, updateDocumentModeLayout);
|
||||
},
|
||||
updateToolOptionsLayout(updateToolOptionsLayout: UpdateToolOptionsLayout) {
|
||||
this.toolOptionsLayout = updateToolOptionsLayout;
|
||||
patchWidgetLayout(this.toolOptionsLayout, updateToolOptionsLayout);
|
||||
},
|
||||
updateDocumentBarLayout(updateDocumentBarLayout: UpdateDocumentBarLayout) {
|
||||
this.documentBarLayout = updateDocumentBarLayout;
|
||||
patchWidgetLayout(this.documentBarLayout, updateDocumentBarLayout);
|
||||
},
|
||||
updateToolShelfLayout(updateToolShelfLayout: UpdateToolShelfLayout) {
|
||||
this.toolShelfLayout = updateToolShelfLayout;
|
||||
patchWidgetLayout(this.toolShelfLayout, updateToolShelfLayout);
|
||||
},
|
||||
updateWorkingColorsLayout(updateWorkingColorsLayout: UpdateWorkingColorsLayout) {
|
||||
this.workingColorsLayout = updateWorkingColorsLayout;
|
||||
patchWidgetLayout(this.workingColorsLayout, updateWorkingColorsLayout);
|
||||
},
|
||||
// Resize elements to render the new viewport size
|
||||
viewportResize() {
|
||||
|
||||
@@ -277,6 +277,7 @@ import {
|
||||
type LayerTypeData,
|
||||
type LayerPanelEntry,
|
||||
defaultWidgetLayout,
|
||||
patchWidgetLayout,
|
||||
UpdateDocumentLayerDetails,
|
||||
UpdateDocumentLayerTreeStructureJs,
|
||||
UpdateLayerTreeOptionsLayout,
|
||||
@@ -523,7 +524,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateLayerTreeOptionsLayout, (updateLayerTreeOptionsLayout) => {
|
||||
this.layerTreeOptionsLayout = updateLayerTreeOptionsLayout;
|
||||
patchWidgetLayout(this.layerTreeOptionsLayout, updateLayerTreeOptionsLayout);
|
||||
});
|
||||
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { defaultWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@/wasm-communication/messages";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelOptionsLayout, UpdatePropertyPanelSectionsLayout } from "@/wasm-communication/messages";
|
||||
|
||||
import LayoutCol from "@/components/layout/LayoutCol.vue";
|
||||
import LayoutRow from "@/components/layout/LayoutRow.vue";
|
||||
@@ -48,11 +48,11 @@ export default defineComponent({
|
||||
},
|
||||
mounted() {
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelOptionsLayout, (updatePropertyPanelOptionsLayout) => {
|
||||
this.propertiesOptionsLayout = updatePropertyPanelOptionsLayout;
|
||||
patchWidgetLayout(this.propertiesOptionsLayout, updatePropertyPanelOptionsLayout);
|
||||
});
|
||||
|
||||
this.editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
|
||||
this.propertiesSectionsLayout = updatePropertyPanelSectionsLayout;
|
||||
patchWidgetLayout(this.propertiesSectionsLayout, updatePropertyPanelSectionsLayout);
|
||||
});
|
||||
},
|
||||
components: {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { reactive, readonly } from "vue";
|
||||
|
||||
import { type IconName } from "@/utility-functions/icons";
|
||||
import { type Editor } from "@/wasm-communication/editor";
|
||||
import { type TextButtonWidget, type WidgetLayout, defaultWidgetLayout, DisplayDialog, DisplayDialogDismiss, UpdateDialogDetails } from "@/wasm-communication/messages";
|
||||
import { type TextButtonWidget, type WidgetLayout, defaultWidgetLayout, DisplayDialog, DisplayDialogDismiss, UpdateDialogDetails, patchWidgetLayout } from "@/wasm-communication/messages";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function createDialogState(editor: Editor) {
|
||||
@@ -37,7 +37,7 @@ export function createDialogState(editor: Editor) {
|
||||
state.icon = displayDialog.icon;
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDialogDetails, (updateDialogDetails) => {
|
||||
state.widgets = updateDialogDetails;
|
||||
patchWidgetLayout(state.widgets, updateDialogDetails);
|
||||
state.jsCallbackBasedButtons = undefined;
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(DisplayDialogDismiss, dismissDialog);
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { reactive, readonly } from "vue";
|
||||
|
||||
import { type Editor } from "@/wasm-communication/editor";
|
||||
import { type FrontendNode, type FrontendNodeLink, type FrontendNodeType, UpdateNodeGraph, UpdateNodeTypes, UpdateNodeGraphBarLayout, defaultWidgetLayout } from "@/wasm-communication/messages";
|
||||
import {
|
||||
type FrontendNode,
|
||||
type FrontendNodeLink,
|
||||
type FrontendNodeType,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeTypes,
|
||||
UpdateNodeGraphBarLayout,
|
||||
defaultWidgetLayout,
|
||||
patchWidgetLayout,
|
||||
} from "@/wasm-communication/messages";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function createNodeGraphState(editor: Editor) {
|
||||
@@ -21,7 +30,7 @@ export function createNodeGraphState(editor: Editor) {
|
||||
state.nodeTypes = updateNodeTypes.nodeTypes;
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
|
||||
state.nodeGraphBarLayout = updateNodeGraphBarLayout;
|
||||
patchWidgetLayout(state.nodeGraphBarLayout, updateNodeGraphBarLayout);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -1171,18 +1171,20 @@ export class Widget {
|
||||
widgetId!: bigint;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function hoistWidgetHolder(widgetHolder: any): Widget {
|
||||
const kind = Object.keys(widgetHolder.widget)[0];
|
||||
const props = widgetHolder.widget[kind];
|
||||
props.kind = kind;
|
||||
|
||||
const { widgetId } = widgetHolder;
|
||||
|
||||
return plainToClass(Widget, { props, widgetId });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function hoistWidgetHolders(widgetHolders: any[]): Widget[] {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return widgetHolders.map((widgetHolder: any) => {
|
||||
const kind = Object.keys(widgetHolder.widget)[0];
|
||||
const props = widgetHolder.widget[kind];
|
||||
props.kind = kind;
|
||||
|
||||
const { widgetId } = widgetHolder;
|
||||
|
||||
return plainToClass(Widget, { props, widgetId });
|
||||
});
|
||||
return widgetHolders.map(hoistWidgetHolder);
|
||||
}
|
||||
|
||||
// WIDGET LAYOUT
|
||||
@@ -1192,6 +1194,18 @@ export type WidgetLayout = {
|
||||
layout: LayoutGroup[];
|
||||
};
|
||||
|
||||
export class WidgetDiffUpdate extends JsMessage {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetDiff(value))
|
||||
diff!: WidgetDiff[];
|
||||
}
|
||||
|
||||
type UIItem = LayoutGroup[] | LayoutGroup | Widget | MenuBarEntry[] | MenuBarEntry;
|
||||
type WidgetDiff = { widgetPath: number[]; newValue: UIItem };
|
||||
|
||||
export function defaultWidgetLayout(): WidgetLayout {
|
||||
return {
|
||||
layoutTarget: undefined,
|
||||
@@ -1199,6 +1213,42 @@ export function defaultWidgetLayout(): WidgetLayout {
|
||||
};
|
||||
}
|
||||
|
||||
// Updates a widget layout based on a list of updates, returning the new layout
|
||||
export function patchWidgetLayout(layout: WidgetLayout, updates: WidgetDiffUpdate): void {
|
||||
layout.layoutTarget = updates.layoutTarget;
|
||||
|
||||
updates.diff.forEach((update) => {
|
||||
// Find the object where the diff applies to
|
||||
const diffObject = update.widgetPath.reduce((targetLayout, index) => {
|
||||
if ("columnWidgets" in targetLayout) return targetLayout.columnWidgets[index];
|
||||
if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
|
||||
if ("layout" in targetLayout) return targetLayout.layout[index];
|
||||
if (targetLayout instanceof Widget) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Tried to index widget");
|
||||
return targetLayout;
|
||||
}
|
||||
// This is a path traversal so we can assume from the backend that it exists
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if ("action" in targetLayout) return targetLayout.children![index];
|
||||
return targetLayout[index];
|
||||
}, layout.layout as UIItem);
|
||||
|
||||
// If this is a list with a length, then set the length to 0 to clear the list
|
||||
if ("length" in diffObject) {
|
||||
diffObject.length = 0;
|
||||
}
|
||||
// Remove all of the keys from the old object
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
Object.keys(diffObject).forEach((key) => delete (diffObject as any)[key]);
|
||||
|
||||
// Assign keys to the new object
|
||||
// `Object.assign` works but `diffObject = update.newValue;` doesn't.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
||||
Object.assign(diffObject, update.newValue);
|
||||
});
|
||||
}
|
||||
|
||||
export type LayoutGroup = WidgetRow | WidgetColumn | WidgetSection;
|
||||
|
||||
export type WidgetColumn = { columnWidgets: Widget[] };
|
||||
@@ -1218,116 +1268,66 @@ export function isWidgetSection(layoutRow: LayoutGroup): layoutRow is WidgetSect
|
||||
|
||||
// Unpacking rust types to more usable type in the frontend
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createWidgetLayout(widgetLayout: any[]): LayoutGroup[] {
|
||||
return widgetLayout.map((layoutType): LayoutGroup => {
|
||||
if (layoutType.column) {
|
||||
const columnWidgets = hoistWidgetHolders(layoutType.column.columnWidgets);
|
||||
|
||||
const result: WidgetColumn = { columnWidgets };
|
||||
return result;
|
||||
function createWidgetDiff(diffs: any[]): WidgetDiff[] {
|
||||
return diffs.map((diff) => {
|
||||
const { widgetPath, newValue } = diff;
|
||||
if (newValue.subLayout) {
|
||||
return { widgetPath, newValue: newValue.subLayout.map(createLayoutGroup) };
|
||||
}
|
||||
|
||||
if (layoutType.row) {
|
||||
const rowWidgets = hoistWidgetHolders(layoutType.row.rowWidgets);
|
||||
|
||||
const result: WidgetRow = { rowWidgets };
|
||||
return result;
|
||||
if (newValue.layoutGroup) {
|
||||
return { widgetPath, newValue: createLayoutGroup(newValue.layoutGroup) };
|
||||
}
|
||||
|
||||
if (layoutType.section) {
|
||||
const { name } = layoutType.section;
|
||||
const layout = createWidgetLayout(layoutType.section.layout);
|
||||
|
||||
const result: WidgetSection = { name, layout };
|
||||
return result;
|
||||
if (newValue.widget) {
|
||||
return { widgetPath, newValue: hoistWidgetHolder(newValue.widget) };
|
||||
}
|
||||
|
||||
throw new Error("Layout row type does not exist");
|
||||
// This code should be unreachable
|
||||
throw new Error("DiffUpdate invalid");
|
||||
});
|
||||
}
|
||||
|
||||
// Unpacking a layout group
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createLayoutGroup(layoutGroup: any): LayoutGroup {
|
||||
if (layoutGroup.column) {
|
||||
const columnWidgets = hoistWidgetHolders(layoutGroup.column.columnWidgets);
|
||||
|
||||
const result: WidgetColumn = { columnWidgets };
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layoutGroup.row) {
|
||||
const result: WidgetRow = { rowWidgets: hoistWidgetHolders(layoutGroup.row.rowWidgets) };
|
||||
return result;
|
||||
}
|
||||
|
||||
if (layoutGroup.section) {
|
||||
const result: WidgetSection = { name: layoutGroup.section.name, layout: layoutGroup.section.layout.map(createLayoutGroup) };
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error("Layout row type does not exist");
|
||||
}
|
||||
|
||||
// WIDGET LAYOUTS
|
||||
export class UpdateDialogDetails extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateDialogDetails extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
export class UpdateDocumentModeLayout extends WidgetDiffUpdate {}
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
export class UpdateToolOptionsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateDocumentModeLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
export class UpdateDocumentBarLayout extends WidgetDiffUpdate {}
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
export class UpdateToolShelfLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateToolOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
export class UpdateWorkingColorsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateDocumentBarLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateToolShelfLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateWorkingColorsLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdatePropertyPanelOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdatePropertyPanelSectionsLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
export class UpdateLayerTreeOptionsLayout extends JsMessage implements WidgetLayout {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateMenuBarLayout extends JsMessage {
|
||||
layoutTarget!: unknown;
|
||||
@@ -1338,15 +1338,6 @@ export class UpdateMenuBarLayout extends JsMessage {
|
||||
layout!: MenuBarEntry[];
|
||||
}
|
||||
|
||||
export class UpdateNodeGraphBarLayout extends JsMessage {
|
||||
layoutTarget!: unknown;
|
||||
|
||||
// TODO: Replace `any` with correct typing
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||
layout!: LayoutGroup[];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
|
||||
return menuBarEntry.map((entry) => ({
|
||||
|
||||
5
frontend/wasm/.cargo/Config.toml
Normal file
5
frontend/wasm/.cargo/Config.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
[target.wasm32-unknown-unknown]
|
||||
rustflags = ["-C", "target-feature=+simd128,+atomics,+bulk-memory,+mutable-globals"]
|
||||
|
||||
[unstable]
|
||||
build-std = ["panic_abort", "std"]
|
||||
Reference in New Issue
Block a user