From 59cbff1648ea0391c0f92d4c5640e67d2787ad79 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 29 Apr 2023 16:13:35 -0700 Subject: [PATCH] Refactor & handle node lifecycle --- src/lib/components/BlockContainer.svelte | 269 ++++++++++++++++++++++ src/lib/components/ComfyApp.svelte | 5 + src/lib/components/ComfyUIPane.svelte | 11 +- src/lib/components/WidgetContainer.svelte | 234 +------------------ src/lib/stores/layoutState.ts | 98 +++++++- src/lib/utils.ts | 31 +++ 6 files changed, 405 insertions(+), 243 deletions(-) create mode 100644 src/lib/components/BlockContainer.svelte diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte new file mode 100644 index 0000000..82f00c8 --- /dev/null +++ b/src/lib/components/BlockContainer.svelte @@ -0,0 +1,269 @@ + + +{#if container && children} +
1}> + + {#if container.attrs.showTitle} + + {/if} +
1} + use:dndzone="{{ + items: children, + flipDurationMs, + centreDraggedOnCursor: true, + morphDisabled: true, + dropFromOthersDisabled: zIndex === 0, + dragDisabled: zIndex === 0 || $layoutState.currentSelection.length > 2 || $uiState.uiEditMode === "disabled" + }}" + on:consider="{handleConsider}" + on:finalize="{handleFinalize}" + > + {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} +
+ + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} +
+ {/if} +
+ {/each} +
+ {#if showHandles} +
+ {/if} + +
+{/if} + + diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index a8df899..3ed254a 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -110,6 +110,11 @@ app.eventBus.on("configured", nodeState.configureFinished); app.eventBus.on("cleared", nodeState.clear); + app.eventBus.on("nodeAdded", layoutState.nodeAdded); + app.eventBus.on("nodeRemoved", layoutState.nodeRemoved); + app.eventBus.on("configured", layoutState.configureFinished); + app.eventBus.on("cleared", layoutState.clear); + app.eventBus.on("autosave", doAutosave); app.eventBus.on("restored", doRestore); diff --git a/src/lib/components/ComfyUIPane.svelte b/src/lib/components/ComfyUIPane.svelte index 9593381..6f6ce4c 100644 --- a/src/lib/components/ComfyUIPane.svelte +++ b/src/lib/components/ComfyUIPane.svelte @@ -33,18 +33,11 @@ * Serialize UI panel order so it can be restored when workflow is loaded */ export function serialize(): any { + // TODO } export function restore(panels: SerializedPanes) { - const id = 0; - $layoutState.root = layoutState.addContainer(null, { direction: "horizontal", showTitle: false }); - const left = layoutState.addContainer($layoutState.root.id, { direction: "vertical", showTitle: false }); - const right = layoutState.addContainer($layoutState.root.id, { direction: "vertical", showTitle: false }); - - for (const node of app.lGraph.computeExecutionOrder(false, null)) { - layoutState.nodeAdded(node) - } - console.warn($layoutState) + // TODO } function groupWidgets() { diff --git a/src/lib/components/WidgetContainer.svelte b/src/lib/components/WidgetContainer.svelte index 7c09386..fd2f319 100644 --- a/src/lib/components/WidgetContainer.svelte +++ b/src/lib/components/WidgetContainer.svelte @@ -1,47 +1,34 @@ -{#if container && children} - {@const id = container.id} -
1}> - - {#if container.attrs.showTitle} - - {/if} -
1} - use:dndzone="{{ - items: children, - flipDurationMs, - centreDraggedOnCursor: true, - morphDisabled: true, - dropFromOthersDisabled: zIndex === 0, - dragDisabled: zIndex === 0 || $layoutState.currentSelection.length > 2 || $uiState.uiEditMode === "disabled" - }}" - on:consider="{handleConsider}" - on:finalize="{handleFinalize}" - > - {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} -
- - {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} -
- {/if} -
- {/each} -
- {#if showHandles} -
- {/if} - -
+{#if container} + {:else if widget}
1} class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)} @@ -160,88 +63,16 @@ {/if} diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 4edd1f3..2ba49db 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -1,7 +1,7 @@ import { get, writable } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store'; import type ComfyApp from "$lib/components/ComfyApp" -import type { LGraphNode, IWidget } from "@litegraph-ts/core" +import type { LGraphNode, IWidget, LGraph } from "@litegraph-ts/core" import nodeState from "$lib/state/nodeState"; import type { NodeStateStore } from './nodeState'; import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action'; @@ -17,9 +17,62 @@ export type LayoutState = { allItems: Record, currentId: number, currentSelection: DragItemID[], + isConfiguring: boolean, isMenuOpen: boolean } +export type AttributesSpec = { + name: string, + type: string, + editable: boolean +} + +export type AttributesCategorySpec = { + categoryName: string, + specs: AttributesSpec[] +} + +export type AttributesSpecList = AttributesCategorySpec[] + +const ALL_ATTRIBUTES: AttributesSpecList = [ + { + categoryName: "appearance", + specs: [ + { + name: "title", + type: "string", + editable: true, + }, + { + name: "showTitle", + type: "boolean", + editable: true, + }, + { + name: "direction", + type: "string", + editable: true, + }, + { + name: "classes", + type: "string", + editable: true, + }, + ] + }, + { + categoryName: "behavior", + specs: [ + { + name: "associatedNode", + type: "number", + editable: false, + }, + ] + } +]; +export { ALL_ATTRIBUTES }; + export type Attributes = { direction: string, title: string, @@ -54,6 +107,7 @@ type LayoutStateOps = { updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[], nodeAdded: (node: LGraphNode) => void, nodeRemoved: (node: LGraphNode) => void, + configureFinished: (graph: LGraph) => void, groupItems: (dragItems: IDragItem[]) => ContainerLayout, ungroup: (container: ContainerLayout) => void, getCurrentSelection: () => IDragItem[], @@ -64,11 +118,12 @@ type LayoutStateOps = { export type WritableLayoutStateStore = Writable & LayoutStateOps; const store: Writable = writable({ root: null, - allItems: [], + allItems: {}, currentId: 0, currentSelection: [], isMenuOpen: false }) +addContainer(null, { direction: "horizontal", showTitle: false }); function findDefaultContainerForInsertion(): ContainerLayout | null { const state = get(store); @@ -160,6 +215,10 @@ function updateChildren(parent: IDragItem, newChildren?: IDragItem[]): IDragItem } function nodeAdded(node: LGraphNode) { + const state = get(store) + if (state.isConfiguring) + return; + const parent = findDefaultContainerForInsertion(); // Add default node panel containing all widgets if (node.widgets && node.widgets.length > 0) { @@ -187,11 +246,14 @@ function removeEntry(state: LayoutState, id: DragItemID) { function nodeRemoved(node: LGraphNode) { const state = get(store) + console.debug("[layoutState] nodeRemoved", node) + // Remove widgets bound to the node let del = Object.entries(state.allItems).filter(pair => pair[1].dragItem.type === "widget" && pair[1].dragItem.attrs.associatedNode === node.id) - for (const id in del) { + for (const item of del) { + const [id, dragItem] = item; console.debug("[layoutState] Remove widget", id, state.allItems[id]) removeEntry(state, id) } @@ -209,7 +271,7 @@ function nodeRemoved(node: LGraphNode) { } // Remove empty containers bound to the node - for (const id in delContainers) { + for (const id of delContainers) { console.debug("[layoutState] Remove container", id, state.allItems[id]) removeEntry(state, id) } @@ -217,6 +279,24 @@ function nodeRemoved(node: LGraphNode) { store.set(state) } +function configureFinished(graph: LGraph) { + const state = get(store) + const id = 0; + + state.isConfiguring = false; + + state.root = addContainer(null, { direction: "horizontal", showTitle: false }); + const left = addContainer(state.root.id, { direction: "vertical", showTitle: false }); + const right = addContainer(state.root.id, { direction: "vertical", showTitle: false }); + + for (const node of graph.computeExecutionOrder(false, null)) { + nodeAdded(node) + } + + console.debug("[layoutState] configureFinished", state) + store.set(state) +} + function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) { const state = get(store) const entry = state.allItems[target.id] @@ -311,6 +391,15 @@ function ungroup(container: ContainerLayout) { } function clear() { + store.set({ + root: null, + allItems: {}, + currentId: 0, + currentSelection: [], + isMenuOpen: false, + isConfiguring: true, + }) + addContainer(null, { direction: "horizontal", showTitle: false }); } function resetLayout() { @@ -326,6 +415,7 @@ const layoutStateStore: WritableLayoutStateStore = updateChildren, nodeAdded, nodeRemoved, + configureFinished, getCurrentSelection, groupItems, ungroup, diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c495cea..79a5f99 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,6 +3,8 @@ import ComboWidget from "$lib/widgets/ComboWidget.svelte"; import RangeWidget from "$lib/widgets/RangeWidget.svelte"; import TextWidget from "$lib/widgets/TextWidget.svelte"; import { type WidgetUIState } from "$lib/stores/nodeState"; +import { get } from "svelte/store" +import layoutState from "$lib/stores/layoutState" export function download(filename: string, text: string, type: string = "text/plain") { const blob = new Blob([text], { type: type }); @@ -39,3 +41,32 @@ export function getComponentForWidgetState(item: WidgetUIState): any { return null; } + +export function startDrag(evt: MouseEvent) { + const dragItemId: string = evt.target.dataset["dragItemId"]; + const ls = get(layoutState) + + if (evt.button !== 0) { + if (ls.currentSelection.length <= 1 && !ls.isMenuOpen) + ls.currentSelection = [dragItemId] + return; + } + + const item = ls.allItems[dragItemId].dragItem + + if (evt.ctrlKey) { + const index = ls.currentSelection.indexOf(item.id) + if (index === -1) + ls.currentSelection.push(item.id); + else + ls.currentSelection.splice(index, 1); + ls.currentSelection = ls.currentSelection; + } + else { + ls.currentSelection = [item.id] + } + layoutState.set(ls) +}; + +export function stopDrag(evt: MouseEvent) { +};