From 0bc9d06910a3440c9c14ccdaddc0f9e645a3b233 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sun, 28 May 2023 11:49:00 -0500 Subject: [PATCH] Jump to node from widget properties button --- src/lib/ComfyGraphCanvas.ts | 18 +- .../components/ComfyBoxWorkflowsView.svelte | 41 +- src/lib/components/ComfyPaneView.svelte | 6 +- src/lib/components/ComfyProperties.svelte | 415 +++++++++++------- src/lib/nodes/ComfyBackendNode.ts | 15 +- src/lib/stores/interfaceState.ts | 4 +- src/lib/stores/layoutStates.ts | 18 +- src/lib/stores/workflowState.ts | 2 +- src/scss/global.scss | 18 +- 9 files changed, 333 insertions(+), 204 deletions(-) diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 72a7107..f3c098c 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -11,6 +11,7 @@ import queueState from "./stores/queueState"; import selectionState from "./stores/selectionState"; import templateState from "./stores/templateState"; import { calcNodesBoundingBox } from "./utils"; +import interfaceState from "./stores/interfaceState"; export type SerializedGraphCanvasState = { offset: Vector2, @@ -118,13 +119,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { // color = "yellow"; // thickness = 5; // } - if (ss.currentHoveredNodes.has(node.id)) { - color = "lightblue"; - } - else if (isRunningNode) { - color = "#0f0"; - } - else if (nodeErrors) { + if (nodeErrors) { const hasExecutionError = nodeErrors.find(e => e.errorType === "execution"); if (hasExecutionError) { blink = true; @@ -139,6 +134,12 @@ export default class ComfyGraphCanvas extends LGraphCanvas { color = "cyan"; thickness = 2 } + else if (ss.currentHoveredNodes.has(node.id)) { + color = "lightblue"; + } + else if (isRunningNode) { + color = "#0f0"; + } if (blink) { if (nodeErrors && nodeErrors.includes(this.blinkError) && this.blinkErrorTime > 0) { @@ -700,6 +701,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } jumpToNode(node: LGraphNode) { + interfaceState.update(s => { s.isJumpingToNode = true; return s; }) + this.closeAllSubgraphs(); const subgraphs = Array.from(node.iterateParentSubgraphNodes()).reverse(); @@ -709,6 +712,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } this.centerOnNode(node); + this.selectNode(node); } jumpToNodeAndInput(node: LGraphNode, slotIndex: number) { diff --git a/src/lib/components/ComfyBoxWorkflowsView.svelte b/src/lib/components/ComfyBoxWorkflowsView.svelte index 816dc39..d0eff8d 100644 --- a/src/lib/components/ComfyBoxWorkflowsView.svelte +++ b/src/lib/components/ComfyBoxWorkflowsView.svelte @@ -220,6 +220,26 @@ } } + async function openGraph(cb: () => void) { + const newGraphSize = Math.max(50, graphSize); + const willOpenPane = newGraphSize != graphSize + graphSize = newGraphSize + + if (willOpenPane) { + const graphPane = getGraphPane(); + if (graphPane) { + graphPane.addEventListener("transitionend", cb, { once: true }) + await tick() + } + else { + cb() + } + } + else { + cb() + } + } + async function showError(promptIDWithError: PromptID) { hideError(); @@ -247,23 +267,7 @@ app.lCanvas.jumpToFirstError(); } - const newGraphSize = Math.max(50, graphSize); - const willOpenPane = newGraphSize != graphSize - graphSize = newGraphSize - - if (willOpenPane) { - const graphPane = getGraphPane(); - if (graphPane) { - graphPane.addEventListener("transitionend", jumpToError, { once: true }) - await tick() - } - else { - jumpToError() - } - } - else { - jumpToError() - } + await openGraph(jumpToError) } function hideError() { @@ -273,7 +277,8 @@ } setContext(WORKFLOWS_VIEW, { - showError + showError, + openGraph }); diff --git a/src/lib/components/ComfyPaneView.svelte b/src/lib/components/ComfyPaneView.svelte index f066510..9e2784c 100644 --- a/src/lib/components/ComfyPaneView.svelte +++ b/src/lib/components/ComfyPaneView.svelte @@ -15,7 +15,8 @@ import ComfyProperties from "./ComfyProperties.svelte"; import ComfyQueue from "./ComfyQueue.svelte"; import ComfyTemplates from "./ComfyTemplates.svelte"; - import { SvelteComponent } from "svelte"; + import { SvelteComponent } from "svelte"; + import { capitalize } from "$lib/utils"; export let app: ComfyApp export let mode: ComfyPaneMode = "none"; @@ -40,7 +41,7 @@ {:else if mode === "graph"} {:else if mode === "properties"} - + {:else if mode === "templates"} {:else if mode === "queue"} @@ -55,6 +56,7 @@ + + {/if} + - -
- {#if workflow != null && layoutState != null} - {#key workflow.id} - {#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"} - + {#if workflow != null && layoutState != null} + {#key workflow.id} + {#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 === "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} - {/key} - {/if} + {/if} +
diff --git a/src/lib/nodes/ComfyBackendNode.ts b/src/lib/nodes/ComfyBackendNode.ts index 1678326..2aed376 100644 --- a/src/lib/nodes/ComfyBackendNode.ts +++ b/src/lib/nodes/ComfyBackendNode.ts @@ -1,5 +1,5 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas"; -import ComfyGraphNode from "./ComfyGraphNode"; +import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import ComfyWidgets from "$lib/widgets" import type { ComfyWidgetNode } from "$lib/nodes/widgets"; import { BuiltInSlotShape, BuiltInSlotType, LiteGraph, type SerializedLGraphNode } from "@litegraph-ts/core"; @@ -8,10 +8,19 @@ import type { ComfyInputConfig } from "$lib/IComfyInputSlot"; import { iterateNodeDefOutputs, type ComfyNodeDef, iterateNodeDefInputs } from "$lib/ComfyNodeDef"; import type { SerializedPromptOutput } from "$lib/utils"; +export interface ComfyBackendNodeProperties extends ComfyGraphNodeProperties { + noOutputDisplay: boolean +} + /* * Base class for any node with configuration sent by the backend. */ export class ComfyBackendNode extends ComfyGraphNode { + override properties: ComfyBackendNodeProperties = { + tags: [], + noOutputDisplay: false + } + comfyClass: string; comfyNodeDef: ComfyNodeDef; displayName: string | null; @@ -37,6 +46,10 @@ export class ComfyBackendNode extends ComfyGraphNode { } } + get isOutputNode(): boolean { + return this.comfyNodeDef.output_node; + } + // comfy class -> input name -> input config private static defaultInputConfigs: Record> = {} diff --git a/src/lib/stores/interfaceState.ts b/src/lib/stores/interfaceState.ts index 88b1d9c..c792dd5 100644 --- a/src/lib/stores/interfaceState.ts +++ b/src/lib/stores/interfaceState.ts @@ -11,6 +11,7 @@ export type InterfaceState = { indicatorValue: any, graphTransitioning: boolean + isJumpingToNode: boolean } type InterfaceStateOps = { @@ -25,7 +26,8 @@ const store: Writable = writable( showIndicator: false, indicatorValue: null, - graphTransitioning: false + graphTransitioning: false, + isJumpingToNode: false, }) const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000) diff --git a/src/lib/stores/layoutStates.ts b/src/lib/stores/layoutStates.ts index 2149c19..9af726a 100644 --- a/src/lib/stores/layoutStates.ts +++ b/src/lib/stores/layoutStates.ts @@ -812,8 +812,8 @@ type LayoutStateOps = { moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void, groupItems: (dragItemIDs: DragItemID[], attrs?: Partial) => ContainerLayout, ungroup: (container: ContainerLayout) => void, - findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null, - findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null, + findLayoutEntryForNode: (nodeId: NodeID) => DragItemEntry | null, + findLayoutForNode: (nodeId: NodeID) => IDragItem | null, iterateBreadthFirst: (id?: DragItemID | null) => Iterable, serialize: () => SerializedLayoutState, serializeAtRoot: (rootID: DragItemID) => SerializedLayoutState, @@ -1216,7 +1216,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat store.set(state) } - function findLayoutEntryForNode(nodeId: ComfyNodeID): DragItemEntry | null { + function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null { const state = get(store) const found = Object.entries(state.allItems).find(pair => pair[1].dragItem.type === "widget" @@ -1226,7 +1226,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat return null; } - function findLayoutForNode(nodeId: ComfyNodeID): WidgetLayout | null { + function findLayoutForNode(nodeId: NodeID): WidgetLayout | null { const found = findLayoutEntryForNode(nodeId); if (!found) return null; @@ -1511,16 +1511,12 @@ function getLayoutByDragItemID(dragItemID: DragItemID): WritableLayoutStateStore return Object.values(get(layoutStates).all).find(l => get(l).allItems[dragItemID] != null) } -function getDragItemByNode(node: LGraphNode): WidgetLayout | null { +function getDragItemByNode(node: LGraphNode): IDragItem | null { const layout = getLayoutByNode(node); if (layout == null) return null; - const entry = get(layout).allItemsByNode[node.id] - if (entry && entry.dragItem.type === "widget") - return entry.dragItem as WidgetLayout; - - return null; + return layout.findLayoutForNode(node.id); } export type LayoutStateStores = { @@ -1543,7 +1539,7 @@ export type LayoutStateStoresOps = { getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null, getLayoutByNode: (node: LGraphNode) => WritableLayoutStateStore | null, getLayoutByDragItemID: (dragItemID: DragItemID) => WritableLayoutStateStore | null, - getDragItemByNode: (node: LGraphNode) => WidgetLayout | null, + getDragItemByNode: (node: LGraphNode) => IDragItem | null, } export type WritableLayoutStateStores = Writable & LayoutStateStoresOps; diff --git a/src/lib/stores/workflowState.ts b/src/lib/stores/workflowState.ts index 55ba772..c7e0707 100644 --- a/src/lib/stores/workflowState.ts +++ b/src/lib/stores/workflowState.ts @@ -404,7 +404,7 @@ function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowIns const workflow = state.openedWorkflows[index] if (workflow.id === state.activeWorkflowID) - return; + return state.activeWorkflow; if (state.activeWorkflow != null) state.activeWorkflow.stop("app") diff --git a/src/scss/global.scss b/src/scss/global.scss index f3545c4..5093fec 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -92,34 +92,40 @@ body { &.primary { background: var(--button-primary-background-fill); - &:hover { + &:hover:not(:disabled) { background: var(--button-primary-background-fill-hover); } } &.secondary { background: var(--button-secondary-background-fill); - &:hover { + &:hover:not(:disabled) { background: var(--button-secondary-background-fill-hover); } } &.ternary { background: var(--panel-background-fill); - &:hover { + &:hover:not(:disabled) { background: var(--block-background-fill); } } - &:hover { + &:hover:not(:disabled) { filter: brightness(85%); } - &:active { + &:active:not(:disabled) { filter: brightness(50%) } - &.selected { + &.selected:not(:disabled) { filter: brightness(80%) } + + &:disabled { + background: var(--neutral-700); + color: var(--neutral-400); + opacity: 50%; + } } @mixin disable-input {