Jump to node from widget properties button

This commit is contained in:
space-nuko
2023-05-28 11:49:00 -05:00
parent 3be662c598
commit 0bc9d06910
9 changed files with 333 additions and 204 deletions

View File

@@ -11,6 +11,7 @@ import queueState from "./stores/queueState";
import selectionState from "./stores/selectionState"; import selectionState from "./stores/selectionState";
import templateState from "./stores/templateState"; import templateState from "./stores/templateState";
import { calcNodesBoundingBox } from "./utils"; import { calcNodesBoundingBox } from "./utils";
import interfaceState from "./stores/interfaceState";
export type SerializedGraphCanvasState = { export type SerializedGraphCanvasState = {
offset: Vector2, offset: Vector2,
@@ -118,13 +119,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
// color = "yellow"; // color = "yellow";
// thickness = 5; // thickness = 5;
// } // }
if (ss.currentHoveredNodes.has(node.id)) { if (nodeErrors) {
color = "lightblue";
}
else if (isRunningNode) {
color = "#0f0";
}
else if (nodeErrors) {
const hasExecutionError = nodeErrors.find(e => e.errorType === "execution"); const hasExecutionError = nodeErrors.find(e => e.errorType === "execution");
if (hasExecutionError) { if (hasExecutionError) {
blink = true; blink = true;
@@ -139,6 +134,12 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
color = "cyan"; color = "cyan";
thickness = 2 thickness = 2
} }
else if (ss.currentHoveredNodes.has(node.id)) {
color = "lightblue";
}
else if (isRunningNode) {
color = "#0f0";
}
if (blink) { if (blink) {
if (nodeErrors && nodeErrors.includes(this.blinkError) && this.blinkErrorTime > 0) { if (nodeErrors && nodeErrors.includes(this.blinkError) && this.blinkErrorTime > 0) {
@@ -700,6 +701,8 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
} }
jumpToNode(node: LGraphNode) { jumpToNode(node: LGraphNode) {
interfaceState.update(s => { s.isJumpingToNode = true; return s; })
this.closeAllSubgraphs(); this.closeAllSubgraphs();
const subgraphs = Array.from(node.iterateParentSubgraphNodes()).reverse(); const subgraphs = Array.from(node.iterateParentSubgraphNodes()).reverse();
@@ -709,6 +712,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
} }
this.centerOnNode(node); this.centerOnNode(node);
this.selectNode(node);
} }
jumpToNodeAndInput(node: LGraphNode, slotIndex: number) { jumpToNodeAndInput(node: LGraphNode, slotIndex: number) {

View File

@@ -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) { async function showError(promptIDWithError: PromptID) {
hideError(); hideError();
@@ -247,23 +267,7 @@
app.lCanvas.jumpToFirstError(); app.lCanvas.jumpToFirstError();
} }
const newGraphSize = Math.max(50, graphSize); await openGraph(jumpToError)
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()
}
} }
function hideError() { function hideError() {
@@ -273,7 +277,8 @@
} }
setContext(WORKFLOWS_VIEW, { setContext(WORKFLOWS_VIEW, {
showError showError,
openGraph
}); });
</script> </script>

View File

@@ -15,7 +15,8 @@
import ComfyProperties from "./ComfyProperties.svelte"; import ComfyProperties from "./ComfyProperties.svelte";
import ComfyQueue from "./ComfyQueue.svelte"; import ComfyQueue from "./ComfyQueue.svelte";
import ComfyTemplates from "./ComfyTemplates.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 app: ComfyApp
export let mode: ComfyPaneMode = "none"; export let mode: ComfyPaneMode = "none";
@@ -40,7 +41,7 @@
{:else if mode === "graph"} {:else if mode === "graph"}
<ComfyGraphView {app} /> <ComfyGraphView {app} />
{:else if mode === "properties"} {:else if mode === "properties"}
<ComfyProperties workflow={$workflowState.activeWorkflow} /> <ComfyProperties {app} workflow={$workflowState.activeWorkflow} />
{:else if mode === "templates"} {:else if mode === "templates"}
<ComfyTemplates {app} /> <ComfyTemplates {app} />
{:else if mode === "queue"} {:else if mode === "queue"}
@@ -55,6 +56,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<button class="mode-button ternary" <button class="mode-button ternary"
disabled={mode === theMode} disabled={mode === theMode}
title={capitalize(theMode)}
class:selected={mode === theMode} class:selected={mode === theMode}
on:click={() => switchMode(theMode)}> on:click={() => switchMode(theMode)}>
<svelte:component this={icon} width="100%" height="100%" /> <svelte:component this={icon} width="100%" height="100%" />

View File

@@ -4,6 +4,7 @@
import { LGraphNode } from "@litegraph-ts/core" import { LGraphNode } from "@litegraph-ts/core"
import { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec, type WritableLayoutStateStore } from "$lib/stores/layoutStates" import { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec, type WritableLayoutStateStore } from "$lib/stores/layoutStates"
import uiState from "$lib/stores/uiState" import uiState from "$lib/stores/uiState"
import interfaceState from "$lib/stores/interfaceState"
import workflowState from "$lib/stores/workflowState" import workflowState from "$lib/stores/workflowState"
import layoutStates from "$lib/stores/layoutStates" import layoutStates from "$lib/stores/layoutStates"
import selectionState from "$lib/stores/selectionState" import selectionState from "$lib/stores/selectionState"
@@ -11,8 +12,12 @@
import ComfyNumberProperty from "./ComfyNumberProperty.svelte"; import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
import ComfyComboProperty from "./ComfyComboProperty.svelte"; import ComfyComboProperty from "./ComfyComboProperty.svelte";
import type { ComfyWidgetNode } from "$lib/nodes/widgets"; import type { ComfyWidgetNode } from "$lib/nodes/widgets";
import type { ComfyBoxWorkflow } from "$lib/stores/workflowState"; import type { ComfyBoxWorkflow } from "$lib/stores/workflowState";
import { Diagram3 } from "svelte-bootstrap-icons";
import { getContext } from "svelte";
import { WORKFLOWS_VIEW } from "./ComfyBoxWorkflowsView.svelte";
export let app: ComfyApp
export let workflow: ComfyBoxWorkflow | null; export let workflow: ComfyBoxWorkflow | null;
let layoutState: WritableLayoutStateStore | null = null let layoutState: WritableLayoutStateStore | null = null
@@ -22,30 +27,44 @@
let target: IDragItem | null = null; let target: IDragItem | null = null;
let node: LGraphNode | null = null; let node: LGraphNode | null = null;
$: if (layoutState) { $: {
if ($selectionState.currentSelection.length > 0) { if ($interfaceState.isJumpingToNode) {
node = null; $interfaceState.isJumpingToNode = false;
const targetId = $selectionState.currentSelection.slice(-1)[0] }
const entry = $layoutState.allItems[targetId] {
if (entry != null) { if (layoutState) {
target = entry.dragItem if ($selectionState.currentSelection.length > 0) {
if (target.type === "widget") { node = null;
node = (target as WidgetLayout).node const targetId = $selectionState.currentSelection.slice(-1)[0]
const entry = $layoutState.allItems[targetId]
if (entry != null) {
target = entry.dragItem
if (target.type === "widget") {
node = (target as WidgetLayout).node
}
}
}
else if ($selectionState.currentSelectionNodes.length > 0) {
target = null;
node = $selectionState.currentSelectionNodes[0]
if (node != null && layoutState != null) {
const dragItem = layoutState.findLayoutForNode(node.id);
if (dragItem != null) {
target = dragItem;
}
}
}
else {
target = null
node = null;
} }
} }
else {
target = null;
node = null;
}
} }
else if ($selectionState.currentSelectionNodes.length > 0) {
target = null;
node = $selectionState.currentSelectionNodes[0]
}
else {
target = null
node = null;
}
}
else {
target = null;
node = null;
} }
$: if (target) { $: if (target) {
@@ -291,195 +310,244 @@
console.warn("[ComfyProperties] doRefreshPanel") console.warn("[ComfyProperties] doRefreshPanel")
$layoutStates.refreshPropsPanel += 1; $layoutStates.refreshPropsPanel += 1;
} }
const workflowsViewContext = getContext(WORKFLOWS_VIEW) as any;
async function jumpToNode() {
if (!workflowsViewContext) {
// strange svelte bug caused by HMR
// https://github.com/sveltejs/svelte/issues/8655
console.error("[ComfyProperties] No workflows view context!")
return;
}
if (app?.lCanvas == null || workflow == null || node == null)
return;
const activeWorkflow = workflowState.setActiveWorkflow(app.lCanvas, workflow.id);
if (activeWorkflow == null || !activeWorkflow.graph.getNodeByIdRecursive(node.id))
return;
await workflowsViewContext.openGraph(() => {
app.lCanvas.jumpToNode(node);
})
}
</script> </script>
<div class="props"> <div class="props">
<div class="top"> <div class="props-scroller">
<div class="target-name"> <div class="top">
<span> <div class="target-name">
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}<span> <div class="target-title-wrapper">
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}</span>
{#if targetType !== ""} {#if targetType !== ""}
<span class="type">({targetType})</span> <span class="type">({targetType})</span>
{/if} {/if}
</span> </div>
</span> {#if node != null}
<div class="target-name-button">
<button class="mode-button ternary"
disabled={node == null}
title="View in Graph"
on:click={jumpToNode}
>
<Diagram3 width="100%" height="100%" />
</button>
</div>
{/if}
</div>
</div> </div>
</div> <div class="props-entries">
<div class="props-entries"> {#if workflow != null && layoutState != null}
{#if workflow != null && layoutState != null} {#key workflow.id}
{#key workflow.id} {#key $layoutStates.refreshPropsPanel}
{#key $layoutStates.refreshPropsPanel} {#each ALL_ATTRIBUTES as category(category.categoryName)}
{#each ALL_ATTRIBUTES as category(category.categoryName)} <div class="category-name">
<div class="category-name"> <span>
<span> <span class="title">{category.categoryName}</span>
<span class="title">{category.categoryName}</span> </span>
</span> </div>
</div> {#each category.specs as spec(spec.id)}
{#each category.specs as spec(spec.id)} {#if validWidgetAttribute(spec, target)}
{#if validWidgetAttribute(spec, target)} <div class="props-entry">
<div class="props-entry"> {#if spec.type === "string"}
{#if spec.type === "string"} <TextBox
<TextBox
value={getAttribute(target, spec)}
on:change={(e) => 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"}
<Checkbox
value={getAttribute(target, spec)} value={getAttribute(target, spec)}
on:change={(e) => updateAttribute(spec, target, e.detail)} on:change={(e) => updateAttribute(spec, target, e.detail)}
on:input={(e) => updateAttribute(spec, target, e.detail)}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
label={spec.name} label={spec.name}
max_lines={spec.multiline ? 5 : 1}
/> />
{:else if spec.type === "number"} {:else if spec.type === "boolean"}
<ComfyNumberProperty <Checkbox
name={spec.name}
value={getAttribute(target, spec)} value={getAttribute(target, spec)}
step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateAttribute(spec, target, e.detail)} on:change={(e) => updateAttribute(spec, target, e.detail)}
disabled={!$uiState.uiUnlocked || !spec.editable}
label={spec.name}
/> />
{:else if spec.type === "enum"} {:else if spec.type === "number"}
<ComfyComboProperty <ComfyNumberProperty
name={spec.name} name={spec.name}
value={getAttribute(target, spec)} value={getAttribute(target, spec)}
values={spec.values} step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateAttribute(spec, target, e.detail)} on:change={(e) => updateAttribute(spec, target, e.detail)}
/> />
{/if}
</div>
{:else if node}
{#if validNodeProperty(spec, node)}
<div class="props-entry">
{#if spec.type === "string"}
<TextBox
value={getProperty(node, spec)}
on:change={(e) => 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"}
<Checkbox
value={getProperty(node, spec)}
label={spec.name}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)}
/>
{:else if spec.type === "number"}
<ComfyNumberProperty
name={spec.name}
value={getProperty(node, spec)}
step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)}
/>
{:else if spec.type === "enum"} {:else if spec.type === "enum"}
<ComfyComboProperty <ComfyComboProperty
name={spec.name} name={spec.name}
value={getProperty(node, spec)} value={getAttribute(target, spec)}
values={spec.values} values={spec.values}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)} on:change={(e) => updateAttribute(spec, target, e.detail)}
/> />
{/if} {/if}
</div> </div>
{:else if validNodeVar(spec, node)} {:else if node}
{#if validNodeProperty(spec, node)}
<div class="props-entry">
{#if spec.type === "string"}
<TextBox
value={getProperty(node, spec)}
on:change={(e) => 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"}
<Checkbox
value={getProperty(node, spec)}
label={spec.name}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)}
/>
{:else if spec.type === "number"}
<ComfyNumberProperty
name={spec.name}
value={getProperty(node, spec)}
step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)}
/>
{:else if spec.type === "enum"}
<ComfyComboProperty
name={spec.name}
value={getProperty(node, spec)}
values={spec.values}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateProperty(spec, e.detail)}
/>
{/if}
</div>
{:else if validNodeVar(spec, node)}
<div class="props-entry">
{#if spec.type === "string"}
<TextBox
value={getVar(node, spec)}
on:change={(e) => 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"}
<Checkbox
value={getVar(node, spec)}
on:change={(e) => updateVar(spec, e.detail)}
disabled={!$uiState.uiUnlocked || !spec.editable}
label={spec.name}
/>
{:else if spec.type === "number"}
<ComfyNumberProperty
name={spec.name}
value={getVar(node, spec)}
step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateVar(spec, e.detail)}
/>
{:else if spec.type === "enum"}
<ComfyComboProperty
name={spec.name}
value={getVar(node, spec)}
values={spec.values}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateVar(spec, e.detail)}
/>
{/if}
</div>
{/if}
{:else if !node && !target && validWorkflowAttribute(spec)}
<div class="props-entry"> <div class="props-entry">
{#if spec.type === "string"} {#if spec.type === "string"}
<TextBox <TextBox
value={getVar(node, spec)} value={getWorkflowAttribute(spec)}
on:change={(e) => updateVar(spec, e.detail)} on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
on:input={(e) => updateVar(spec, e.detail)} on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
label={spec.name} label={spec.name}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
max_lines={spec.multiline ? 5 : 1} max_lines={spec.multiline ? 5 : 1}
/> />
{:else if spec.type === "boolean"} {:else if spec.type === "boolean"}
<Checkbox <Checkbox
value={getVar(node, spec)} value={getWorkflowAttribute(spec)}
on:change={(e) => updateVar(spec, e.detail)} on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
label={spec.name} label={spec.name}
/> />
{:else if spec.type === "number"} {:else if spec.type === "number"}
<ComfyNumberProperty <ComfyNumberProperty
name={spec.name} name={spec.name}
value={getVar(node, spec)} value={getWorkflowAttribute(spec)}
step={spec.step || 1} step={spec.step || 1}
min={spec.min || -1024} min={spec.min || -1024}
max={spec.max || 1024} max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateVar(spec, e.detail)} on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
/> />
{:else if spec.type === "enum"} {:else if spec.type === "enum"}
<ComfyComboProperty <ComfyComboProperty
name={spec.name} name={spec.name}
value={getVar(node, spec)} value={getWorkflowAttribute(spec)}
values={spec.values} values={spec.values}
disabled={!$uiState.uiUnlocked || !spec.editable} disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateVar(spec, e.detail)} on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
/> />
{/if} {/if}
</div> </div>
{:else if !node && !target && validWorkflowAttribute(spec)} {/if}
<div class="props-entry">
{#if spec.type === "string"}
<TextBox
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"}
<Checkbox
value={getWorkflowAttribute(spec)}
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
disabled={!$uiState.uiUnlocked || !spec.editable}
label={spec.name}
/>
{:else if spec.type === "number"}
<ComfyNumberProperty
name={spec.name}
value={getWorkflowAttribute(spec)}
step={spec.step || 1}
min={spec.min || -1024}
max={spec.max || 1024}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
/>
{:else if spec.type === "enum"}
<ComfyComboProperty
name={spec.name}
value={getWorkflowAttribute(spec)}
values={spec.values}
disabled={!$uiState.uiUnlocked || !spec.editable}
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
/>
{/if}
</div>
{/each} {/each}
{/each} {/each}
{/key} {/key}
{/key} {/key}
{/key} {/if}
</div> </div>
</div> </div>
</div> </div>
<style lang="scss">
$bottom-bar-height: 2.5rem;
.props {
width: 100%;
height: 100%;
}
.props-scroller {
width: 100%;
height: calc(100% - $bottom-bar-height);
overflow-x: hidden;
overflow-y: auto;
}
.props-entry { .props-entry {
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
@@ -491,7 +559,6 @@
.target-name { .target-name {
background: var(--input-background-fill); background: var(--input-background-fill);
border-color: var(--input-border-color);
border-color: var(--input-border-color); border-color: var(--input-border-color);
.title { .title {
@@ -501,6 +568,48 @@
padding-left: 0.25rem; padding-left: 0.25rem;
font-weight: normal; font-weight: normal;
} }
}
width: 100%;
display: flex;
flex-direction: row;
> .target-title-wrapper {
padding: 0.8rem 0 0.8rem 1.0rem;
display: flex;
flex-direction: row;
width: 100%;
text-align: center;
> span {
display: flex;
flex-direction: column;
justify-content: center;
}
}
> .target-name-button {
padding: 0.5rem;
.mode-button {
color: var(--comfy-accent-soft);
height: $bottom-bar-height;
width: 2.5rem;
height: 2.5rem;
margin: 1.0rem;
padding: 0.5rem;
margin-left: auto;
@include square-button;
color: var(--neutral-300);
&:hover:not(:disabled) {
filter: brightness(120%) !important;
}
&:active:not(:disabled) {
filter: brightness(50%) !important;
}
}
} }
} }
@@ -518,13 +627,5 @@
color: var(--neutral-500); color: var(--neutral-500);
} }
} }
.bottom {
/* width: 100%;
height: auto;
position: absolute;
bottom: 0;
padding: 0.5em; */
}
@include disable-inputs; @include disable-inputs;

View File

@@ -1,5 +1,5 @@
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas"; import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
import ComfyGraphNode from "./ComfyGraphNode"; import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import ComfyWidgets from "$lib/widgets" import ComfyWidgets from "$lib/widgets"
import type { ComfyWidgetNode } from "$lib/nodes/widgets"; import type { ComfyWidgetNode } from "$lib/nodes/widgets";
import { BuiltInSlotShape, BuiltInSlotType, LiteGraph, type SerializedLGraphNode } from "@litegraph-ts/core"; 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 { iterateNodeDefOutputs, type ComfyNodeDef, iterateNodeDefInputs } from "$lib/ComfyNodeDef";
import type { SerializedPromptOutput } from "$lib/utils"; import type { SerializedPromptOutput } from "$lib/utils";
export interface ComfyBackendNodeProperties extends ComfyGraphNodeProperties {
noOutputDisplay: boolean
}
/* /*
* Base class for any node with configuration sent by the backend. * Base class for any node with configuration sent by the backend.
*/ */
export class ComfyBackendNode extends ComfyGraphNode { export class ComfyBackendNode extends ComfyGraphNode {
override properties: ComfyBackendNodeProperties = {
tags: [],
noOutputDisplay: false
}
comfyClass: string; comfyClass: string;
comfyNodeDef: ComfyNodeDef; comfyNodeDef: ComfyNodeDef;
displayName: string | null; 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 // comfy class -> input name -> input config
private static defaultInputConfigs: Record<string, Record<string, ComfyInputConfig>> = {} private static defaultInputConfigs: Record<string, Record<string, ComfyInputConfig>> = {}

View File

@@ -11,6 +11,7 @@ export type InterfaceState = {
indicatorValue: any, indicatorValue: any,
graphTransitioning: boolean graphTransitioning: boolean
isJumpingToNode: boolean
} }
type InterfaceStateOps = { type InterfaceStateOps = {
@@ -25,7 +26,8 @@ const store: Writable<InterfaceState> = writable(
showIndicator: false, showIndicator: false,
indicatorValue: null, indicatorValue: null,
graphTransitioning: false graphTransitioning: false,
isJumpingToNode: false,
}) })
const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000) const debounceDrag = debounce(() => { store.update(s => { s.showIndicator = false; return s }) }, 1000)

View File

@@ -812,8 +812,8 @@ type LayoutStateOps = {
moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void, moveItem: (target: IDragItem, to: ContainerLayout, index?: number) => void,
groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout, groupItems: (dragItemIDs: DragItemID[], attrs?: Partial<Attributes>) => ContainerLayout,
ungroup: (container: ContainerLayout) => void, ungroup: (container: ContainerLayout) => void,
findLayoutEntryForNode: (nodeId: ComfyNodeID) => DragItemEntry | null, findLayoutEntryForNode: (nodeId: NodeID) => DragItemEntry | null,
findLayoutForNode: (nodeId: ComfyNodeID) => IDragItem | null, findLayoutForNode: (nodeId: NodeID) => IDragItem | null,
iterateBreadthFirst: (id?: DragItemID | null) => Iterable<DragItemEntry>, iterateBreadthFirst: (id?: DragItemID | null) => Iterable<DragItemEntry>,
serialize: () => SerializedLayoutState, serialize: () => SerializedLayoutState,
serializeAtRoot: (rootID: DragItemID) => SerializedLayoutState, serializeAtRoot: (rootID: DragItemID) => SerializedLayoutState,
@@ -1216,7 +1216,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat
store.set(state) store.set(state)
} }
function findLayoutEntryForNode(nodeId: ComfyNodeID): DragItemEntry | null { function findLayoutEntryForNode(nodeId: NodeID): DragItemEntry | null {
const state = get(store) const state = get(store)
const found = Object.entries(state.allItems).find(pair => const found = Object.entries(state.allItems).find(pair =>
pair[1].dragItem.type === "widget" pair[1].dragItem.type === "widget"
@@ -1226,7 +1226,7 @@ function createRaw(workflow: ComfyBoxWorkflow | null = null): WritableLayoutStat
return null; return null;
} }
function findLayoutForNode(nodeId: ComfyNodeID): WidgetLayout | null { function findLayoutForNode(nodeId: NodeID): WidgetLayout | null {
const found = findLayoutEntryForNode(nodeId); const found = findLayoutEntryForNode(nodeId);
if (!found) if (!found)
return null; return null;
@@ -1511,16 +1511,12 @@ function getLayoutByDragItemID(dragItemID: DragItemID): WritableLayoutStateStore
return Object.values(get(layoutStates).all).find(l => get(l).allItems[dragItemID] != null) 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); const layout = getLayoutByNode(node);
if (layout == null) if (layout == null)
return null; return null;
const entry = get(layout).allItemsByNode[node.id] return layout.findLayoutForNode(node.id);
if (entry && entry.dragItem.type === "widget")
return entry.dragItem as WidgetLayout;
return null;
} }
export type LayoutStateStores = { export type LayoutStateStores = {
@@ -1543,7 +1539,7 @@ export type LayoutStateStoresOps = {
getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null, getLayoutByGraph: (graph: LGraph) => WritableLayoutStateStore | null,
getLayoutByNode: (node: LGraphNode) => WritableLayoutStateStore | null, getLayoutByNode: (node: LGraphNode) => WritableLayoutStateStore | null,
getLayoutByDragItemID: (dragItemID: DragItemID) => WritableLayoutStateStore | null, getLayoutByDragItemID: (dragItemID: DragItemID) => WritableLayoutStateStore | null,
getDragItemByNode: (node: LGraphNode) => WidgetLayout | null, getDragItemByNode: (node: LGraphNode) => IDragItem | null,
} }
export type WritableLayoutStateStores = Writable<LayoutStateStores> & LayoutStateStoresOps; export type WritableLayoutStateStores = Writable<LayoutStateStores> & LayoutStateStoresOps;

View File

@@ -404,7 +404,7 @@ function setActiveWorkflow(canvas: ComfyGraphCanvas, index: number | WorkflowIns
const workflow = state.openedWorkflows[index] const workflow = state.openedWorkflows[index]
if (workflow.id === state.activeWorkflowID) if (workflow.id === state.activeWorkflowID)
return; return state.activeWorkflow;
if (state.activeWorkflow != null) if (state.activeWorkflow != null)
state.activeWorkflow.stop("app") state.activeWorkflow.stop("app")

View File

@@ -92,34 +92,40 @@ body {
&.primary { &.primary {
background: var(--button-primary-background-fill); background: var(--button-primary-background-fill);
&:hover { &:hover:not(:disabled) {
background: var(--button-primary-background-fill-hover); background: var(--button-primary-background-fill-hover);
} }
} }
&.secondary { &.secondary {
background: var(--button-secondary-background-fill); background: var(--button-secondary-background-fill);
&:hover { &:hover:not(:disabled) {
background: var(--button-secondary-background-fill-hover); background: var(--button-secondary-background-fill-hover);
} }
} }
&.ternary { &.ternary {
background: var(--panel-background-fill); background: var(--panel-background-fill);
&:hover { &:hover:not(:disabled) {
background: var(--block-background-fill); background: var(--block-background-fill);
} }
} }
&:hover { &:hover:not(:disabled) {
filter: brightness(85%); filter: brightness(85%);
} }
&:active { &:active:not(:disabled) {
filter: brightness(50%) filter: brightness(50%)
} }
&.selected { &.selected:not(:disabled) {
filter: brightness(80%) filter: brightness(80%)
} }
&:disabled {
background: var(--neutral-700);
color: var(--neutral-400);
opacity: 50%;
}
} }
@mixin disable-input { @mixin disable-input {