Jump to node from widget properties button
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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%" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>> = {}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user