From 3aad2598693521f55f56a5b8a816b93b7360b71d Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 20 May 2023 23:13:31 -0500 Subject: [PATCH] Fix props pane --- src/lib/components/ComfyApp.ts | 70 ++++-- src/lib/components/ComfyProperties.svelte | 240 ++++++++++--------- src/lib/components/ComfyWorkflowsView.svelte | 93 +++++-- src/lib/stores/layoutStates.ts | 7 + src/lib/stores/workflowState.ts | 22 +- 5 files changed, 261 insertions(+), 171 deletions(-) diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index 4c06ad9..a713d31 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -42,7 +42,7 @@ import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt"; import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer"; import selectionState from "$lib/stores/selectionState"; import layoutStates from "$lib/stores/layoutStates"; -import { ComfyWorkflow, type WorkflowAttributes } from "$lib/stores/workflowState"; +import { ComfyWorkflow, type WorkflowAttributes, type WorkflowInstID } from "$lib/stores/workflowState"; import workflowState from "$lib/stores/workflowState"; export const COMFYBOX_SERIAL_VERSION = 1; @@ -181,12 +181,13 @@ export default class ComfyApp { this.lCanvas.allow_interaction = uiUnlocked; // await this.#invokeExtensionsAsync("init"); - await this.registerNodes(); + const defs = await this.api.getNodeDefs(); + await this.registerNodes(defs); // Load previous workflow let restored = false; try { - restored = await this.loadStateFromLocalStorage(); + restored = await this.loadStateFromLocalStorage(defs); } catch (err) { console.error("Error loading previous workflow", err); notify(`Error loading previous workflow:\n${err}`, { type: "error", timeout: null }) @@ -194,7 +195,7 @@ export default class ComfyApp { // We failed to restore a workflow so load the default if (!restored) { - await this.initDefaultWorkflow(); + await this.initDefaultWorkflow(defs); } // Save current workflow automatically @@ -249,9 +250,11 @@ export default class ComfyApp { saveStateToLocalStorage() { try { uiState.update(s => { s.isSavingToLocalStorage = true; return s; }) - const workflows = get(workflowState).openedWorkflows + const state = get(workflowState) + const workflows = state.openedWorkflows const savedWorkflows = workflows.map(w => this.serialize(w)); - const json = JSON.stringify(savedWorkflows); + const activeWorkflowIndex = workflows.findIndex(w => state.activeWorkflowID === w.id); + const json = JSON.stringify({ workflows: savedWorkflows, activeWorkflowIndex }); localStorage.setItem("workflows", json) for (const workflow of workflows) workflow.isModified = false; @@ -266,24 +269,33 @@ export default class ComfyApp { } } - async loadStateFromLocalStorage(): Promise { + async loadStateFromLocalStorage(defs: Record): Promise { const json = localStorage.getItem("workflows"); if (!json) { return false } - const workflows = JSON.parse(json) as SerializedAppState[]; - for (const workflow of workflows) - await this.openWorkflow(workflow) + + const state = JSON.parse(json); + if (!("workflows" in state)) + return false; + + const workflows = state.workflows as SerializedAppState[]; + for (const workflow of workflows) { + await this.openWorkflow(workflow, defs) + } + + if (typeof state.activeWorkflowIndex === "number") { + workflowState.setActiveWorkflow(this.lCanvas, state.activeWorkflowIndex); + selectionState.clear(); + } + return true; } static node_type_overrides: Record = {} static widget_type_overrides: Record = {} - private async registerNodes() { - // Load node definitions from the backend - const defs = await this.api.getNodeDefs(); - + private async registerNodes(defs: Record) { // Register a node for each definition for (const [nodeId, nodeDef] of Object.entries(defs)) { const typeOverride = ComfyApp.node_type_overrides[nodeId] @@ -504,7 +516,7 @@ export default class ComfyApp { setColor(BuiltInSlotType.ACTION, "lightseagreen") } - async openWorkflow(data: SerializedAppState): Promise { + async openWorkflow(data: SerializedAppState, refreshCombos: boolean | Record = true): Promise { if (data.version !== COMFYBOX_SERIAL_VERSION) { throw `Invalid ComfyBox saved data format: ${data.version}` } @@ -515,27 +527,38 @@ export default class ComfyApp { // Restore canvas offset/zoom this.lCanvas.deserialize(data.canvas) - await this.refreshComboInNodes(workflow); + if (refreshCombos) { + let defs = null; + if (typeof refreshCombos === "object") + defs = refreshCombos; + await this.refreshComboInNodes(workflow, defs); + } return workflow; } - setActiveWorkflow(index: number) { + setActiveWorkflow(id: WorkflowInstID) { + const index = get(workflowState).openedWorkflows.findIndex(w => w.id === id) + if (index === -1) + return; workflowState.setActiveWorkflow(this.lCanvas, index); selectionState.clear(); } - createNewWorkflow(index: number) { + createNewWorkflow() { workflowState.createNewWorkflow(this.lCanvas, undefined, true); selectionState.clear(); } - closeWorkflow(index: number) { + closeWorkflow(id: WorkflowInstID) { + const index = get(workflowState).openedWorkflows.findIndex(w => w.id === id) + if (index === -1) + return; workflowState.closeWorkflow(this.lCanvas, index); selectionState.clear(); } - async initDefaultWorkflow() { + async initDefaultWorkflow(defs?: Record) { let state = null; try { const graphResponse = await fetch("/workflows/defaultWorkflow.json"); @@ -546,7 +569,7 @@ export default class ComfyApp { notify(`Failed to load default graph: ${error}`, { type: "error" }) state = structuredClone(blankGraph) } - await this.openWorkflow(state) + await this.openWorkflow(state, defs) } clear() { @@ -783,14 +806,15 @@ export default class ComfyApp { /** * Refresh combo list on whole nodes */ - async refreshComboInNodes(workflow?: ComfyWorkflow, flashUI: boolean = false) { + async refreshComboInNodes(workflow?: ComfyWorkflow, defs?: Record, flashUI: boolean = false) { workflow ||= workflowState.getActiveWorkflow(); if (workflow == null) { notify("No active workflow!", { type: "error" }) return } - const defs = await this.api.getNodeDefs(); + if (defs == null) + defs = await this.api.getNodeDefs(); const isComfyComboNode = (node: LGraphNode): node is ComfyComboNode => { return node diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte index 568a20a..384684e 100644 --- a/src/lib/components/ComfyProperties.svelte +++ b/src/lib/components/ComfyProperties.svelte @@ -17,6 +17,8 @@ let layoutState: WritableLayoutStateStore | null = null + $: layoutState = workflow?.layout + let target: IDragItem | null = null; let node: LGraphNode | null = null; @@ -246,7 +248,7 @@ doRefreshPanel() } - workflow.notifyModified() + workflow.notifyModified(); } function getWorkflowAttribute(spec: AttributesSpec): any { @@ -279,10 +281,10 @@ if (spec.onChanged) spec.onChanged($layoutState, value, prevValue) - // if (spec.refreshPanelOnChange) + if (spec.refreshPanelOnChange) doRefreshPanel() - workflow.notifyModified() + workflow.notifyModified(); } function doRefreshPanel() { @@ -300,176 +302,178 @@ ({targetType}) {/if} - +
{#if workflow != null && layoutState != null} - {#key $layoutStates.refreshPropsPanel} - {#each ALL_ATTRIBUTES as category(category.categoryName)} -
- - {category.categoryName} - -
- {#each category.specs as spec(spec.id)} - {#if validWidgetAttribute(spec, target)} -
- {#if spec.type === "string"} - updateAttribute(spec, target, e.detail)} - on:input={(e) => updateAttribute(spec, target, e.detail)} - disabled={!$uiState.uiUnlocked || !spec.editable} - label={spec.name} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - + + {category.categoryName} + +
+ {#each category.specs as spec(spec.id)} + {#if validWidgetAttribute(spec, target)} +
+ {#if spec.type === "string"} + updateAttribute(spec, target, e.detail)} + on:input={(e) => updateAttribute(spec, target, e.detail)} disabled={!$uiState.uiUnlocked || !spec.editable} label={spec.name} + max_lines={spec.multiline ? 5 : 1} /> - {:else if spec.type === "number"} - updateAttribute(spec, target, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} /> - {:else if spec.type === "enum"} - updateAttribute(spec, target, e.detail)} /> - {/if} -
- {:else if node} - {#if validNodeProperty(spec, node)} -
- {#if spec.type === "string"} - updateProperty(spec, e.detail)} - on:input={(e) => updateProperty(spec, e.detail)} - label={spec.name} - disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - updateProperty(spec, e.detail)} - /> - {:else if spec.type === "number"} - updateProperty(spec, e.detail)} - /> {:else if spec.type === "enum"} updateProperty(spec, e.detail)} + on:change={(e) => updateAttribute(spec, target, e.detail)} /> {/if}
- {:else if validNodeVar(spec, node)} + {:else if node} + {#if validNodeProperty(spec, node)} +
+ {#if spec.type === "string"} + updateProperty(spec, e.detail)} + on:input={(e) => updateProperty(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={spec.multiline ? 5 : 1} + /> + {:else if spec.type === "boolean"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "number"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateProperty(spec, e.detail)} + /> + {/if} +
+ {:else if validNodeVar(spec, node)} +
+ {#if spec.type === "string"} + updateVar(spec, e.detail)} + on:input={(e) => updateVar(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={spec.multiline ? 5 : 1} + /> + {:else if spec.type === "boolean"} + updateVar(spec, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateVar(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateVar(spec, e.detail)} + /> + {/if} +
+ {/if} + {:else if !node && !target && validWorkflowAttribute(spec)}
{#if spec.type === "string"} updateVar(spec, e.detail)} - on:input={(e) => updateVar(spec, e.detail)} + value={getWorkflowAttribute(spec)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} + on:input={(e) => updateWorkflowAttribute(spec, e.detail)} label={spec.name} disabled={!$uiState.uiUnlocked || !spec.editable} max_lines={spec.multiline ? 5 : 1} /> {:else if spec.type === "boolean"} updateVar(spec, e.detail)} + value={getWorkflowAttribute(spec)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} disabled={!$uiState.uiUnlocked || !spec.editable} label={spec.name} /> {:else if spec.type === "number"} updateVar(spec, e.detail)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} /> {:else if spec.type === "enum"} updateVar(spec, e.detail)} + on:change={(e) => updateWorkflowAttribute(spec, e.detail)} /> {/if}
{/if} - {:else if !node && !target && validWorkflowAttribute(spec)} -
- {#if spec.type === "string"} - updateWorkflowAttribute(spec, e.detail)} - on:input={(e) => updateWorkflowAttribute(spec, e.detail)} - label={spec.name} - disabled={!$uiState.uiUnlocked || !spec.editable} - max_lines={spec.multiline ? 5 : 1} - /> - {:else if spec.type === "boolean"} - updateWorkflowAttribute(spec, e.detail)} - disabled={!$uiState.uiUnlocked || !spec.editable} - label={spec.name} - /> - {:else if spec.type === "number"} - updateWorkflowAttribute(spec, e.detail)} - /> - {:else if spec.type === "enum"} - updateWorkflowAttribute(spec, e.detail)} - /> - {/if} -
- {/if} + {/each} {/each} - {/each} + {/key} {/key} {/if}
diff --git a/src/lib/components/ComfyWorkflowsView.svelte b/src/lib/components/ComfyWorkflowsView.svelte index cfbab07..80c5856 100644 --- a/src/lib/components/ComfyWorkflowsView.svelte +++ b/src/lib/components/ComfyWorkflowsView.svelte @@ -14,12 +14,15 @@ import selectionState from "$lib/stores/selectionState"; import type ComfyApp from './ComfyApp'; import { onMount } from "svelte"; - import type { WritableLayoutStateStore } from '$lib/stores/layoutStates'; + import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action'; + import { fade } from 'svelte/transition'; + import { cubicIn } from 'svelte/easing'; export let app: ComfyApp; export let uiTheme: string = "gradio-dark" // TODO config let workflow: ComfyWorkflow | null = null; + let openedWorkflows = [] let containerElem: HTMLDivElement; let resizeTimeout: NodeJS.Timeout | null; @@ -30,6 +33,7 @@ let appSetupPromise: Promise = null; $: workflow = $workflowState.activeWorkflow; + $: openedWorkflows = $workflowState.openedWorkflows.map(w => { return { id: w.id } }) onMount(async () => { appSetupPromise = app.setup().then(() => { @@ -154,7 +158,7 @@ app.createNewWorkflow(); } - function closeWorkflow(event: Event, index: number, workflow: ComfyWorkflow) { + function closeWorkflow(event: Event, workflow: ComfyWorkflow) { event.preventDefault(); event.stopImmediatePropagation() @@ -163,15 +167,33 @@ return; } - app.closeWorkflow(index); + app.closeWorkflow(workflow.id); } + + function handleConsider(evt: any) { + console.warn(openedWorkflows.length, openedWorkflows, evt.detail.items.length, evt.detail.items) + openedWorkflows = evt.detail.items; + // openedWorkflows = evt.detail.items.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID); + // workflowState.update(s => { + // s.openedWorkflows = openedWorkflows.map(w => workflowState.getWorkflow(w.id)); + // return s; + // }) + }; + + function handleFinalize(evt: any) { + openedWorkflows = evt.detail.items; + workflowState.update(s => { + s.openedWorkflows = openedWorkflows.filter(w => w.id !== SHADOW_PLACEHOLDER_ITEM_ID).map(w => workflowState.getWorkflow(w.id)); + return s; + }) + };
@@ -195,22 +217,38 @@
- {#each $workflowState.openedWorkflows as workflow, index} - + {#if workflow[SHADOW_ITEM_MARKER_PROPERTY_NAME]} +
{/if} - - - - {/each} + {/each} +