From 173398ad55d953d00fd9b48ae9d4d20e5853eeac Mon Sep 17 00:00:00 2001 From: Samyat Gautam <107800884+FadedBronze@users.noreply.github.com> Date: Fri, 23 Jun 2023 14:01:39 -0400 Subject: [PATCH] Improve 'add node' menu (#1312) * improved node adding list with dropdowns and scrolling * changed arrow icon from default details arrow * made 'add node' menu appear above mouse when clicking at bottom of nodegraph. Searching automatically opens the dropdowns you need. Set fixed 'add node' menu size * updated code style to be more clear * undo mistake changes --------- Co-authored-by: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> --- .../src/components/panels/NodeGraph.svelte | 115 +++++++++++++++--- frontend/src/wasm-communication/messages.ts | 49 ++++---- 2 files changed, 122 insertions(+), 42 deletions(-) diff --git a/frontend/src/components/panels/NodeGraph.svelte b/frontend/src/components/panels/NodeGraph.svelte index d330bfd6..1f3ac41c 100644 --- a/frontend/src/components/panels/NodeGraph.svelte +++ b/frontend/src/components/panels/NodeGraph.svelte @@ -18,6 +18,8 @@ const WHEEL_RATE = (1 / 600) * 3; const GRID_COLLAPSE_SPACING = 10; const GRID_SIZE = 24; + const ADD_NODE_MENU_WIDTH = 180; + const ADD_NODE_MENU_HEIGHT = 200; const editor = getContext("editor"); const nodeGraph = getContext("nodeGraph"); @@ -46,6 +48,19 @@ $: nodeCategories = buildNodeCategories($nodeGraph.nodeTypes, searchTerm); $: nodeListX = ((nodeListLocation?.x || 0) * GRID_SIZE + transform.x) * transform.scale; $: nodeListY = ((nodeListLocation?.y || 0) * GRID_SIZE + transform.y) * transform.scale; + + let appearAboveMouse = false; + let appearRightOfMouse = false; + + $: (() => { + const bounds = graph?.div()?.getBoundingClientRect(); + if (!bounds) return; + const { width, height } = bounds; + + appearRightOfMouse = nodeListX > width - ADD_NODE_MENU_WIDTH / 2; + appearAboveMouse = nodeListY > height - ADD_NODE_MENU_HEIGHT / 2; + })(); + $: linkPathInProgress = createLinkPathInProgress(linkInProgressFromConnector, linkInProgressToConnector); $: linkPaths = createLinkPaths(linkPathInProgress, nodeLinkPaths); @@ -60,16 +75,35 @@ return sparse; } - function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, FrontendNodeType[]][] { - const categories = new Map(); + type NodeCategoryDetails = { + nodes: FrontendNodeType[]; + open: boolean; + }; + + function buildNodeCategories(nodeTypes: FrontendNodeType[], searchTerm: string): [string, NodeCategoryDetails][] { + const categories = new Map(); + nodeTypes.forEach((node) => { - if (searchTerm.length > 0 && !node.name.toLowerCase().includes(searchTerm.toLowerCase()) && !node.category.toLowerCase().includes(searchTerm.toLowerCase())) { + const nameIncludesSearchTerm = node.name.toLowerCase().includes(searchTerm.toLowerCase()); + + if (searchTerm.length > 0 && !nameIncludesSearchTerm && !node.category.toLowerCase().includes(searchTerm.toLowerCase())) { return; } const category = categories.get(node.category); - if (category) category.push(node); - else categories.set(node.category, [node]); + let open = nameIncludesSearchTerm; + if (searchTerm.length === 0) { + open = false; + } + + if (category) { + category.open = open; + category.nodes.push(node); + } else + categories.set(node.category, { + open: open, + nodes: [node], + }); }); return Array.from(categories); @@ -497,18 +531,32 @@ }} > {#if nodeListLocation} - + (searchTerm = detail)} bind:this={nodeSearchInput} /> - {#each nodeCategories as nodeCategory} - - {nodeCategory[0]} - {#each nodeCategory[1] as nodeType} - createNode(nodeType.name)} /> - {/each} - - {:else} - No search results - {/each} +
+ {#each nodeCategories as nodeCategory} +
+ + + {nodeCategory[0]} + + {#each nodeCategory[1].nodes as nodeType} + createNode(nodeType.name)} /> + {/each} +
+ {:else} +
No search results
+ {/each} +
{/if}
@@ -607,12 +655,45 @@ .node-list { width: max-content; - position: fixed; + position: absolute; padding: 5px; z-index: 3; background-color: var(--color-3-darkgray); + .text-button { + width: 100%; + } + + .list-nodes { + overflow-y: scroll; + } + + details { + margin-right: 4px; + cursor: pointer; + } + + summary { + list-style-type: none; + display: flex; + align-items: center; + gap: 2px; + + span { + white-space: break-spaces; + } + } + + details summary svg { + transform: rotate(-90deg); + } + + details[open] summary svg { + transform: rotate(0deg); + } + .text-button + .text-button { + display: block; margin-left: 0; margin-top: 4px; } diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 90a0dda5..81addb4c 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -42,7 +42,6 @@ export class UpdateNodeGraphSelection extends JsMessage { readonly selected!: bigint[]; } - export class UpdateOpenDocumentsList extends JsMessage { @Type(() => FrontendDocumentDetails) readonly openDocuments!: FrontendDocumentDetails[]; @@ -493,22 +492,22 @@ const mouseCursorIconCSSNames = { Rotate: "custom-rotate", } as const; export type MouseCursor = keyof typeof mouseCursorIconCSSNames; -export type MouseCursorIcon = typeof mouseCursorIconCSSNames[MouseCursor]; +export type MouseCursorIcon = (typeof mouseCursorIconCSSNames)[MouseCursor]; export class UpdateMouseCursor extends JsMessage { @Transform(({ value }: { value: MouseCursor }) => mouseCursorIconCSSNames[value] || "alias") readonly cursor!: MouseCursorIcon; } -export class TriggerLoadAutoSaveDocuments extends JsMessage { } +export class TriggerLoadAutoSaveDocuments extends JsMessage {} -export class TriggerLoadPreferences extends JsMessage { } +export class TriggerLoadPreferences extends JsMessage {} -export class TriggerOpenDocument extends JsMessage { } +export class TriggerOpenDocument extends JsMessage {} -export class TriggerImport extends JsMessage { } +export class TriggerImport extends JsMessage {} -export class TriggerPaste extends JsMessage { } +export class TriggerPaste extends JsMessage {} export class TriggerCopyToClipboardBlobUrl extends JsMessage { readonly blobUrl!: string; @@ -547,7 +546,7 @@ export class TriggerRasterizeRegionBelowLayer extends JsMessage { readonly size!: [number, number]; } -export class TriggerRefreshBoundsOfViewports extends JsMessage { } +export class TriggerRefreshBoundsOfViewports extends JsMessage {} export class TriggerRevokeBlobUrl extends JsMessage { readonly url!: string; @@ -557,7 +556,7 @@ export class TriggerSavePreferences extends JsMessage { readonly preferences!: Record; } -export class DocumentChanged extends JsMessage { } +export class DocumentChanged extends JsMessage {} export class UpdateDocumentLayerTreeStructureJs extends JsMessage { constructor(readonly layerId: bigint, readonly children: UpdateDocumentLayerTreeStructureJs[]) { @@ -650,7 +649,7 @@ export class UpdateImageData extends JsMessage { readonly imageData!: ImaginateImageData[]; } -export class DisplayRemoveEditableTextbox extends JsMessage { } +export class DisplayRemoveEditableTextbox extends JsMessage {} export class UpdateDocumentLayerDetails extends JsMessage { @Type(() => LayerPanelEntry) @@ -701,7 +700,7 @@ export class ImaginateImageData { readonly transform!: Float64Array; } -export class DisplayDialogDismiss extends JsMessage { } +export class DisplayDialogDismiss extends JsMessage {} export class Font { fontFamily!: string; @@ -720,7 +719,7 @@ export class TriggerVisitLink extends JsMessage { url!: string; } -export class TriggerTextCommit extends JsMessage { } +export class TriggerTextCommit extends JsMessage {} export class TriggerTextCopy extends JsMessage { readonly copyText!: string; @@ -730,7 +729,7 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage { readonly commitDate!: string; } -export class TriggerViewportResize extends JsMessage { } +export class TriggerViewportResize extends JsMessage {} // WIDGET PROPS @@ -1098,13 +1097,13 @@ const widgetSubTypes = [ { value: TextLabel, name: "TextLabel" }, ] as const; -type WidgetSubTypes = typeof widgetSubTypes[number]; +type WidgetSubTypes = (typeof widgetSubTypes)[number]; type WidgetKindMap = { [T in WidgetSubTypes as T["name"]]: InstanceType }; export type WidgetPropsNames = keyof WidgetKindMap; export type WidgetPropsSet = WidgetKindMap[WidgetPropsNames]; export function narrowWidgetProps(props: WidgetPropsSet, kind: K) { - if (props.kind === kind) return props as WidgetKindMap[K] + if (props.kind === kind) return props as WidgetKindMap[K]; else return undefined; } @@ -1258,25 +1257,25 @@ function createLayoutGroup(layoutGroup: any): LayoutGroup { } // WIDGET LAYOUTS -export class UpdateDialogDetails extends WidgetDiffUpdate { } +export class UpdateDialogDetails extends WidgetDiffUpdate {} -export class UpdateDocumentModeLayout extends WidgetDiffUpdate { } +export class UpdateDocumentModeLayout extends WidgetDiffUpdate {} -export class UpdateToolOptionsLayout extends WidgetDiffUpdate { } +export class UpdateToolOptionsLayout extends WidgetDiffUpdate {} -export class UpdateDocumentBarLayout extends WidgetDiffUpdate { } +export class UpdateDocumentBarLayout extends WidgetDiffUpdate {} -export class UpdateToolShelfLayout extends WidgetDiffUpdate { } +export class UpdateToolShelfLayout extends WidgetDiffUpdate {} -export class UpdateWorkingColorsLayout extends WidgetDiffUpdate { } +export class UpdateWorkingColorsLayout extends WidgetDiffUpdate {} -export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate { } +export class UpdatePropertyPanelOptionsLayout extends WidgetDiffUpdate {} -export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate { } +export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {} -export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate { } +export class UpdateLayerTreeOptionsLayout extends WidgetDiffUpdate {} -export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate { } +export class UpdateNodeGraphBarLayout extends WidgetDiffUpdate {} export class UpdateMenuBarLayout extends JsMessage { layoutTarget!: unknown;