Merge node/widget state
This commit is contained in:
365
src/App.svelte
365
src/App.svelte
@@ -9,369 +9,4 @@
|
||||
<ComfyApp/>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-50: #fff7ed;
|
||||
--primary-100: #ffedd5;
|
||||
--primary-200: #fed7aa;
|
||||
--primary-300: #fdba74;
|
||||
--primary-400: #fb923c;
|
||||
--primary-500: #f97316;
|
||||
--primary-600: #ea580c;
|
||||
--primary-700: #c2410c;
|
||||
--primary-800: #9a3412;
|
||||
--primary-900: #7c2d12;
|
||||
--primary-950: #6c2e12;
|
||||
--secondary-50: #eff6ff;
|
||||
--secondary-100: #dbeafe;
|
||||
--secondary-200: #bfdbfe;
|
||||
--secondary-300: #93c5fd;
|
||||
--secondary-400: #60a5fa;
|
||||
--secondary-500: #3b82f6;
|
||||
--secondary-600: #2563eb;
|
||||
--secondary-700: #1d4ed8;
|
||||
--secondary-800: #1e40af;
|
||||
--secondary-900: #1e3a8a;
|
||||
--secondary-950: #1d3660;
|
||||
--neutral-50: #f9fafb;
|
||||
--neutral-100: #f3f4f6;
|
||||
--neutral-200: #e5e7eb;
|
||||
--neutral-300: #d1d5db;
|
||||
--neutral-400: #9ca3af;
|
||||
--neutral-500: #6b7280;
|
||||
--neutral-600: #4b5563;
|
||||
--neutral-700: #374151;
|
||||
--neutral-800: #1f2937;
|
||||
--neutral-900: #111827;
|
||||
--neutral-950: #0b0f19;
|
||||
--spacing-xxs: 1px;
|
||||
--spacing-xs: 2px;
|
||||
--spacing-sm: 4px;
|
||||
--spacing-md: 6px;
|
||||
--spacing-lg: 8px;
|
||||
--spacing-xl: 10px;
|
||||
--spacing-xxl: 16px;
|
||||
--radius-xxs: 1px;
|
||||
--radius-xs: 2px;
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 6px;
|
||||
--radius-lg: 8px;
|
||||
--radius-xl: 12px;
|
||||
--radius-xxl: 22px;
|
||||
--text-xxs: 9px;
|
||||
--text-xs: 10px;
|
||||
--text-sm: 12px;
|
||||
--text-md: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 22px;
|
||||
--text-xxl: 26px;
|
||||
--color-accent: var(--primary-500);
|
||||
--color-accent-soft: var(--primary-50);
|
||||
--background-fill-primary: white;
|
||||
--background-fill-secondary: var(--neutral-50);
|
||||
--border-color-accent: var(--primary-300);
|
||||
--border-color-primary: var(--neutral-200);
|
||||
--text-color-code-background-fill: var(--neutral-200);
|
||||
--text-color-code-border: var(--border-color-primary);
|
||||
--link-text-color: var(--secondary-600);
|
||||
--link-text-color-active: var(--secondary-600);
|
||||
--link-text-color-hover: var(--secondary-700);
|
||||
--link-text-color-visited: var(--secondary-500);
|
||||
--body-text-color-subdued: var(--neutral-400);
|
||||
--body-background-fill: var(--background-fill-primary);
|
||||
--body-text-color: var(--neutral-800);
|
||||
--body-text-size: var(--text-md);
|
||||
--body-text-weight: 400;
|
||||
--embed-radius: var(--radius-lg);
|
||||
--shadow-drop: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
--shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1),
|
||||
0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-inset: rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset;
|
||||
--shadow-spread: 3px;
|
||||
--block-background-fill: var(--background-fill-primary);
|
||||
--block-border-color: var(--border-color-primary);
|
||||
--block-border-width: 1px;
|
||||
--block-info-text-color: var(--body-text-color-subdued);
|
||||
--block-info-text-size: var(--text-sm);
|
||||
--block-info-text-weight: 400;
|
||||
--block-label-background-fill: var(--background-fill-primary);
|
||||
--block-label-border-color: var(--border-color-primary);
|
||||
--block-label-border-width: 1px;
|
||||
--block-label-text-color: var(--neutral-500);
|
||||
--block-label-icon-color: var(--block-label-text-color);
|
||||
--block-label-margin: 0;
|
||||
--block-label-padding: var(--spacing-sm) var(--spacing-lg);
|
||||
--block-label-radius: calc(var(--radius-lg) - 1px) 0
|
||||
calc(var(--radius-lg) - 1px) 0;
|
||||
--block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0
|
||||
calc(var(--radius-lg) - 1px);
|
||||
--block-label-text-size: var(--text-sm);
|
||||
--block-label-text-weight: 400;
|
||||
--block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
|
||||
--block-radius: var(--radius-lg);
|
||||
--block-shadow: var(--shadow-drop);
|
||||
--block-title-background-fill: none;
|
||||
--block-title-border-color: none;
|
||||
--block-title-border-width: 0px;
|
||||
--block-title-text-color: var(--neutral-500);
|
||||
--block-title-padding: 0;
|
||||
--block-title-radius: none;
|
||||
--block-title-text-size: var(--text-md);
|
||||
--block-title-text-weight: 400;
|
||||
--container-radius: var(--radius-lg);
|
||||
--form-gap-width: 1px;
|
||||
--layout-gap: var(--spacing-xxl);
|
||||
--panel-background-fill: var(--background-fill-secondary);
|
||||
--panel-border-color: var(--border-color-primary);
|
||||
--panel-border-width: 0;
|
||||
--section-header-text-size: var(--text-md);
|
||||
--section-header-text-weight: 400;
|
||||
--checkbox-background-color: var(--background-fill-primary);
|
||||
--checkbox-background-color-focus: var(--background-fill-primary);
|
||||
--checkbox-background-color-hover: var(--background-fill-primary);
|
||||
--checkbox-background-color-selected: var(--secondary-600);
|
||||
--checkbox-border-color: var(--neutral-300);
|
||||
--checkbox-border-color-focus: var(--secondary-500);
|
||||
--checkbox-border-color-hover: var(--neutral-300);
|
||||
--checkbox-border-color-selected: var(--secondary-600);
|
||||
--checkbox-border-radius: var(--radius-sm);
|
||||
--checkbox-border-width: var(--input-border-width);
|
||||
--checkbox-label-background-fill: linear-gradient(
|
||||
to top,
|
||||
var(--neutral-50),
|
||||
white
|
||||
);
|
||||
--checkbox-label-background-fill-hover: linear-gradient(
|
||||
to top,
|
||||
var(--neutral-100),
|
||||
white
|
||||
);
|
||||
--checkbox-label-background-fill-selected: var(
|
||||
--checkbox-label-background-fill
|
||||
);
|
||||
--checkbox-label-border-color: var(--border-color-primary);
|
||||
--checkbox-label-border-color-hover: var(--border-color-primary);
|
||||
--checkbox-label-border-width: var(--input-border-width);
|
||||
--checkbox-label-gap: var(--spacing-lg);
|
||||
--checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
|
||||
--checkbox-label-shadow: var(--shadow-drop);
|
||||
--checkbox-label-text-size: var(--text-md);
|
||||
--checkbox-label-text-weight: 400;
|
||||
--checkbox-shadow: var(--input-shadow);
|
||||
--checkbox-label-text-color: var(--body-text-color);
|
||||
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
|
||||
--error-background-fill: linear-gradient(
|
||||
to right,
|
||||
#fee2e2,
|
||||
var(--background-fill-secondary)
|
||||
);
|
||||
--error-border-color: #fecaca;
|
||||
--error-border-width: 1px;
|
||||
--error-text-color: #ef4444;
|
||||
--prose-header-text-weight: 600;
|
||||
--input-background-fill: white;
|
||||
--input-background-fill-focus: var(--secondary-500);
|
||||
--input-background-fill-hover: var(--input-background-fill);
|
||||
--input-border-color: var(--border-color-primary);
|
||||
--input-border-color-focus: var(--secondary-300);
|
||||
--input-border-color-hover: var(--border-color-primary);
|
||||
--input-border-width: 1px;
|
||||
--input-padding: var(--spacing-xl);
|
||||
--input-placeholder-color: var(--neutral-400);
|
||||
--input-radius: var(--radius-lg);
|
||||
--input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset);
|
||||
--input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50),
|
||||
var(--shadow-inset);
|
||||
--input-text-size: var(--text-md);
|
||||
--input-text-weight: 400;
|
||||
--loader-color: var(--color-accent);
|
||||
--prose-text-size: var(--text-md);
|
||||
--prose-text-weight: 400;
|
||||
--stat-background-fill: linear-gradient(
|
||||
to right,
|
||||
var(--primary-400),
|
||||
var(--primary-200)
|
||||
);
|
||||
--table-border-color: var(--neutral-300);
|
||||
--table-even-background-fill: white;
|
||||
--table-odd-background-fill: var(--neutral-50);
|
||||
--table-radius: var(--radius-lg);
|
||||
--table-row-focus: var(--color-accent-soft);
|
||||
--button-border-width: var(--input-border-width);
|
||||
--button-cancel-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
#fee2e2,
|
||||
#fecaca
|
||||
);
|
||||
--button-cancel-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
#fee2e2,
|
||||
#fee2e2
|
||||
);
|
||||
--button-cancel-border-color: #fecaca;
|
||||
--button-cancel-border-color-hover: var(--button-cancel-border-color);
|
||||
--button-cancel-text-color: #dc2626;
|
||||
--button-cancel-text-color-hover: var(--button-cancel-text-color);
|
||||
--button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
|
||||
--button-large-radius: var(--radius-lg);
|
||||
--button-large-text-size: var(--text-lg);
|
||||
--button-large-text-weight: 600;
|
||||
--button-primary-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
var(--primary-100),
|
||||
var(--primary-300)
|
||||
);
|
||||
--button-primary-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
var(--primary-100),
|
||||
var(--primary-200)
|
||||
);
|
||||
--button-primary-border-color: var(--primary-200);
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-text-color: var(--primary-600);
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-secondary-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
var(--neutral-100),
|
||||
var(--neutral-200)
|
||||
);
|
||||
--button-secondary-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
var(--neutral-100),
|
||||
var(--neutral-100)
|
||||
);
|
||||
--button-secondary-border-color: var(--neutral-200);
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-text-color: var(--neutral-700);
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
--button-shadow: var(--shadow-drop);
|
||||
--button-shadow-active: var(--shadow-inset);
|
||||
--button-shadow-hover: var(--shadow-drop-lg);
|
||||
--button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
|
||||
--button-small-radius: var(--radius-lg);
|
||||
--button-small-text-size: var(--text-md);
|
||||
--button-small-text-weight: 400;
|
||||
--button-transition: none;
|
||||
}
|
||||
.dark {
|
||||
--color-accent-soft: var(--neutral-900);
|
||||
--background-fill-primary: var(--neutral-950);
|
||||
--background-fill-secondary: var(--neutral-900);
|
||||
--border-color-accent: var(--neutral-600);
|
||||
--border-color-primary: var(--neutral-700);
|
||||
--text-color-code-background-fill: var(--neutral-800);
|
||||
--link-text-color-active: var(--secondary-500);
|
||||
--link-text-color: var(--secondary-500);
|
||||
--link-text-color-hover: var(--secondary-400);
|
||||
--link-text-color-visited: var(--secondary-600);
|
||||
--body-text-color-subdued: var(--neutral-400);
|
||||
--body-background-fill: var(--background-fill-primary);
|
||||
--body-text-color: var(--neutral-100);
|
||||
--shadow-spread: 1px;
|
||||
--block-background-fill: var(--neutral-800);
|
||||
--block-border-color: var(--border-color-primary);
|
||||
--block-border-width: 1px;
|
||||
--block-info-text-color: var(--body-text-color-subdued);
|
||||
--block-label-background-fill: var(--background-fill-secondary);
|
||||
--block-label-border-color: var(--border-color-primary);
|
||||
--block-label-border-width: 1px;
|
||||
--block-label-text-color: var(--neutral-200);
|
||||
--block-shadow: none;
|
||||
--block-title-background-fill: none;
|
||||
--block-title-border-color: none;
|
||||
--block-title-border-width: 0px;
|
||||
--block-title-text-color: var(--neutral-200);
|
||||
--panel-background-fill: var(--background-fill-secondary);
|
||||
--panel-border-color: var(--border-color-primary);
|
||||
--checkbox-background-color: var(--neutral-800);
|
||||
--checkbox-background-color-focus: var(--checkbox-background-color);
|
||||
--checkbox-background-color-hover: var(--checkbox-background-color);
|
||||
--checkbox-background-color-selected: var(--secondary-600);
|
||||
--checkbox-border-color: var(--neutral-700);
|
||||
--checkbox-border-color-focus: var(--secondary-500);
|
||||
--checkbox-border-color-hover: var(--neutral-600);
|
||||
--checkbox-border-color-selected: var(--secondary-600);
|
||||
--checkbox-label-background-fill: linear-gradient(
|
||||
to top,
|
||||
var(--neutral-900),
|
||||
var(--neutral-800)
|
||||
);
|
||||
--checkbox-label-background-fill-hover: linear-gradient(
|
||||
to top,
|
||||
var(--neutral-900),
|
||||
var(--neutral-800)
|
||||
);
|
||||
--checkbox-label-background-fill-selected: var(
|
||||
--checkbox-label-background-fill
|
||||
);
|
||||
--checkbox-label-border-color: var(--border-color-primary);
|
||||
--checkbox-label-border-color-hover: var(--border-color-primary);
|
||||
--checkbox-label-text-color: var(--body-text-color);
|
||||
--checkbox-label-text-color-selected: var(--checkbox-label-text-color);
|
||||
--error-background-fill: var(--background-fill-primary);
|
||||
--error-border-color: var(--border-color-primary);
|
||||
--error-border-width: var(--error-border-width);
|
||||
--error-text-color: #ef4444;
|
||||
--input-background-fill: var(--neutral-800);
|
||||
--input-background-fill-focus: var(--secondary-600);
|
||||
--input-background-fill-hover: var(--input-background-fill);
|
||||
--input-border-color: var(--border-color-primary);
|
||||
--input-border-color-focus: var(--neutral-700);
|
||||
--input-border-color-hover: var(--border-color-primary);
|
||||
--input-placeholder-color: var(--neutral-500);
|
||||
--input-shadow: var(--input-shadow);
|
||||
--input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700),
|
||||
var(--shadow-inset);
|
||||
--loader-color: var(--loader-color);
|
||||
--stat-background-fill: linear-gradient(
|
||||
to right,
|
||||
var(--primary-400),
|
||||
var(--primary-600)
|
||||
);
|
||||
--table-border-color: var(--neutral-700);
|
||||
--table-even-background-fill: var(--neutral-950);
|
||||
--table-odd-background-fill: var(--neutral-900);
|
||||
--table-row-focus: var(--color-accent-soft);
|
||||
--button-cancel-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
#dc2626,
|
||||
#b91c1c
|
||||
);
|
||||
--button-cancel-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
#dc2626,
|
||||
#dc2626
|
||||
);
|
||||
--button-cancel-border-color: #dc2626;
|
||||
--button-cancel-border-color-hover: var(--button-cancel-border-color);
|
||||
--button-cancel-text-color: white;
|
||||
--button-cancel-text-color-hover: var(--button-cancel-text-color);
|
||||
--button-primary-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
var(--primary-600),
|
||||
var(--primary-700)
|
||||
);
|
||||
--button-primary-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
var(--primary-600),
|
||||
var(--primary-600)
|
||||
);
|
||||
--button-primary-border-color: var(--primary-600);
|
||||
--button-primary-border-color-hover: var(--button-primary-border-color);
|
||||
--button-primary-text-color: white;
|
||||
--button-primary-text-color-hover: var(--button-primary-text-color);
|
||||
--button-secondary-background-fill: linear-gradient(
|
||||
to bottom right,
|
||||
var(--neutral-600),
|
||||
var(--neutral-700)
|
||||
);
|
||||
--button-secondary-background-fill-hover: linear-gradient(
|
||||
to bottom right,
|
||||
var(--neutral-600),
|
||||
var(--neutral-600)
|
||||
);
|
||||
--button-secondary-border-color: var(--neutral-600);
|
||||
--button-secondary-border-color-hover: var(--button-secondary-border-color);
|
||||
--button-secondary-text-color: white;
|
||||
--button-secondary-text-color-hover: var(--button-secondary-text-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { LGraph } from "@litegraph-ts/core";
|
||||
import widgetState, { type WidgetStateStore, type WidgetUIState, type WidgetUIStateStore } from "./stores/widgetState";
|
||||
import nodeState, { type NodeStateStore, type NodeUIState, type NodeUIStateStore } from "./stores/nodeState";
|
||||
import nodeState, { type WidgetUIState, type WidgetUIStateStore, type NodeStateStore, type NodeUIState, type NodeUIStateStore } from "./stores/nodeState";
|
||||
import type ComfyApp from "./components/ComfyApp";
|
||||
import type { Unsubscriber } from "svelte/store";
|
||||
|
||||
@@ -38,24 +37,20 @@ export default class GraphSync {
|
||||
|
||||
constructor(app: ComfyApp) {
|
||||
this.graph = app.lGraph;
|
||||
this._unsubscribeWidget = widgetState.subscribe(this.onAllWidgetStateChanged.bind(this));
|
||||
this._unsubscribeNode = nodeState.subscribe(this.onAllNodeStateChanged.bind(this));
|
||||
this._unsubscribe = nodeState.subscribe(this.onAllNodeStateChanged.bind(this));
|
||||
this._finalizer = new FinalizationRegistry((id: number) => {
|
||||
console.log(`${this} has been garbage collected`);
|
||||
this._unsubscribeWidget();
|
||||
this._unsubscribeNode();
|
||||
this._unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Fired when the entire widget graph changes.
|
||||
*/
|
||||
private onAllWidgetStateChanged(state: WidgetStateStore) {
|
||||
private onAllNodeStateChanged(state: NodeStateStore) {
|
||||
// TODO assumes only a single graph's widget state.
|
||||
|
||||
for (let nodeId in state) {
|
||||
state[nodeId].node.title = state[nodeId].name;
|
||||
if (!this.stores[nodeId]) {
|
||||
this.addStores(state, nodeId);
|
||||
this.addStores(state[nodeId].widgetStates, nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,26 +59,18 @@ export default class GraphSync {
|
||||
this.removeStores(nodeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onAllNodeStateChanged(state: NodeStateStore) {
|
||||
// TODO assumes only a single graph's widget state.
|
||||
|
||||
for (let nodeId in state) {
|
||||
state[nodeId].node.name = state[nodeId].name;
|
||||
}
|
||||
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
private addStores(state: WidgetStateStore, nodeId: string) {
|
||||
private addStores(widgetStates: WidgetUIState[], nodeId: string) {
|
||||
if (this.stores[nodeId]) {
|
||||
console.warn("Stores already exist!", nodeId, this.stores[nodeId])
|
||||
}
|
||||
|
||||
this.stores[nodeId] = []
|
||||
|
||||
for (const wuis of state[nodeId]) {
|
||||
for (const wuis of widgetStates) {
|
||||
const unsub = wuis.value.subscribe((v) => this.onWidgetStateChanged(wuis, v))
|
||||
this.stores[nodeId].push({ store: wuis.value, unsubscribe: unsub });
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
@@ -102,16 +101,11 @@
|
||||
onMount(async () => {
|
||||
app = new ComfyApp();
|
||||
|
||||
// TODO dedup
|
||||
app.eventBus.on("nodeAdded", nodeState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", nodeState.nodeRemoved);
|
||||
app.eventBus.on("configured", nodeState.configureFinished);
|
||||
app.eventBus.on("cleared", nodeState.clear);
|
||||
|
||||
app.eventBus.on("nodeAdded", widgetState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", widgetState.nodeRemoved);
|
||||
app.eventBus.on("configured", widgetState.configureFinished);
|
||||
app.eventBus.on("cleared", widgetState.clear);
|
||||
app.eventBus.on("autosave", doAutosave);
|
||||
app.eventBus.on("restored", doRestore);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import * as basic from "@litegraph-ts/nodes-basic"
|
||||
import * as nodes from "$lib/nodes/index"
|
||||
import ComfyGraphCanvas from "$lib/ComfyGraphCanvas";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import type { WidgetStateStore, WidgetUIState } from "$lib/stores/widgetState";
|
||||
import * as widgets from "$lib/widgets/index"
|
||||
import type ComfyWidget from "$lib/widgets/ComfyWidget";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { get } from "svelte/store"
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { Move } from 'radix-icons-svelte';
|
||||
import widgetState, { type WidgetDrawState, type WidgetUIState } from "$lib/stores/widgetState";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
@@ -47,7 +46,7 @@
|
||||
dragDisabled = true;
|
||||
};
|
||||
|
||||
const unsubscribe = widgetState.subscribe(state => {
|
||||
const unsubscribe = nodeState.subscribe(state => {
|
||||
dragItems = dragItems.filter(item => item.node.id in state);
|
||||
});
|
||||
|
||||
@@ -88,7 +87,7 @@
|
||||
{/if}
|
||||
</BlockTitle>
|
||||
</label>
|
||||
{#each $widgetState[id] as item}
|
||||
{#each $nodeState[id].widgetStates as item}
|
||||
<svelte:component this={getComponentForWidgetState(item)} {item} />
|
||||
{#if dragItem[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import ComfyApp from "./ComfyApp";
|
||||
import type { SerializedPanes } from "./ComfyApp"
|
||||
import ComfyPane from "./ComfyPane.svelte";
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import type { DragItem } from "./ComfyUIPane";
|
||||
|
||||
export let app: ComfyApp;
|
||||
@@ -17,12 +17,12 @@
|
||||
function findLeastPopulatedPaneIndex(): number {
|
||||
let minWidgetCount = 2 ** 64;
|
||||
let minIndex = 0;
|
||||
let state = get(widgetState);
|
||||
let state = get(nodeState);
|
||||
for (let i = 0; i < dragItems.length; i++) {
|
||||
let widgetCount = 0;
|
||||
for (let j = 0; j < dragItems[i].length; j++) {
|
||||
const nodeID = dragItems[i][j].node.id;
|
||||
widgetCount += state[nodeID].length;
|
||||
widgetCount += state[nodeID].widgetStates.length;
|
||||
}
|
||||
if (widgetCount < minWidgetCount) {
|
||||
minWidgetCount = widgetCount
|
||||
|
||||
@@ -1,23 +1,51 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import type { LGraph, LGraphNode } from "@litegraph-ts/core";
|
||||
import type { LGraph, LGraphNode, IWidget } from "@litegraph-ts/core";
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyGraphNode from '$lib/nodes/ComfyGraphNode';
|
||||
import type ComfyWidget from '$lib/widgets/ComfyWidget';
|
||||
|
||||
/** store for one node's state */
|
||||
export type NodeUIStateStore = Writable<any>
|
||||
|
||||
export type NodeUIState = {
|
||||
name: string,
|
||||
node: LGraphNode
|
||||
node: LGraphNode,
|
||||
widgetStates: WidgetUIState[]
|
||||
}
|
||||
|
||||
type NodeID = number;
|
||||
export type WidgetDrawState = {
|
||||
isNodeExecuting: boolean
|
||||
}
|
||||
|
||||
/** store for one widget's state */
|
||||
export type WidgetUIStateStore = Writable<any>
|
||||
|
||||
export type WidgetUIState = {
|
||||
/** position in the node's list of widgets */
|
||||
index: number,
|
||||
/** parent node containing the widget */
|
||||
node: LGraphNode,
|
||||
/** actual widget instance */
|
||||
widget: IWidget,
|
||||
/** widget value as a store, to react to changes */
|
||||
value: WidgetUIStateStore,
|
||||
/**
|
||||
* true if this widget was added purely from the frontend. what this means:
|
||||
* - this widget's state will not be saved to the workflow
|
||||
* - the widget was added on startup by some subclass of ComfyGraphNode
|
||||
*/
|
||||
isVirtual: boolean
|
||||
}
|
||||
|
||||
export type NodeID = number;
|
||||
|
||||
type NodeStateOps = {
|
||||
nodeAdded: (node: LGraphNode) => void,
|
||||
nodeRemoved: (node: LGraphNode) => void,
|
||||
configureFinished: (graph: LGraph) => void,
|
||||
nodeStateChanged: (node: LGraphNode) => void,
|
||||
widgetStateChanged: (nodeId: number, widget: IWidget<any, any>) => void,
|
||||
findWidgetByName: (nodeId: number, name: string) => WidgetUIState | null,
|
||||
clear: () => void,
|
||||
}
|
||||
|
||||
@@ -32,8 +60,22 @@ function clear() {
|
||||
|
||||
function nodeAdded(node: LGraphNode) {
|
||||
let state = get(store)
|
||||
state[node.id] = { node: node, name: node.name }
|
||||
|
||||
const widgets = [];
|
||||
|
||||
if (node.widgets) {
|
||||
for (const [index, widget] of node.widgets.entries()) {
|
||||
let isVirtual = false;
|
||||
if ("isVirtual" in widget)
|
||||
isVirtual = (widget as ComfyWidget<any, any>).isVirtual;
|
||||
widgets.push({ index, widget, value: writable(widget.value), isVirtual: isVirtual })
|
||||
}
|
||||
}
|
||||
|
||||
state[node.id] = { node: node, name: node.title, widgetStates: widgets }
|
||||
store.set(state);
|
||||
|
||||
console.debug("call nodeAdded", state[node.id])
|
||||
}
|
||||
|
||||
function nodeRemoved(node: LGraphNode) {
|
||||
@@ -45,7 +87,7 @@ function nodeRemoved(node: LGraphNode) {
|
||||
function nodeStateChanged(node: LGraphNode) {
|
||||
const state = get(store)
|
||||
const nodeState = state[node.id]
|
||||
nodeState.name = node.name
|
||||
nodeState.name = node.title
|
||||
store.set(state);
|
||||
}
|
||||
|
||||
@@ -53,12 +95,51 @@ function configureFinished(graph: LGraph) {
|
||||
let state = get(store);
|
||||
|
||||
for (const node of graph.computeExecutionOrder(false, null)) {
|
||||
state[node.id].name = name;
|
||||
state[node.id].name = node.title;
|
||||
|
||||
const widgetStates = state[node.id].widgetStates;
|
||||
if (node.widgets_values) {
|
||||
for (const [i, value] of node.widgets_values.entries()) {
|
||||
if (i < widgetStates.length && !widgetStates[i].isVirtual) {
|
||||
widgetStates[i].value.set(value);
|
||||
}
|
||||
else {
|
||||
console.log("Skip virtual widget", node.id, node.type, widgetStates[i].widget)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function widgetStateChanged(nodeId: number, widget: IWidget<any, any>) {
|
||||
const state = get(store)
|
||||
const entries = state[nodeId].widgetStates
|
||||
if (entries) {
|
||||
let widgetState = entries.find(e => e.widget === widget);
|
||||
if (widgetState) {
|
||||
widgetState.value.set(widget.value);
|
||||
store.set(state);
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed and node was found, but widget was not found in state!", widget, state[nodeId].node, entries)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed but node was not found in state!", widget, state[nodeId].node)
|
||||
}
|
||||
}
|
||||
|
||||
function findWidgetByName(nodeId: NodeID, name: string): WidgetUIState | null {
|
||||
let state = get(store);
|
||||
|
||||
if (!(nodeId in state))
|
||||
return null;
|
||||
|
||||
return state[nodeId].widgetStates.find((v) => v.widget.name === name);
|
||||
}
|
||||
|
||||
const nodeStateStore: WritableNodeStateStore =
|
||||
{
|
||||
...store,
|
||||
@@ -66,6 +147,8 @@ const nodeStateStore: WritableNodeStateStore =
|
||||
nodeRemoved,
|
||||
nodeStateChanged,
|
||||
configureFinished,
|
||||
widgetStateChanged,
|
||||
findWidgetByName,
|
||||
clear
|
||||
}
|
||||
export default nodeStateStore;
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import type { LGraph, LGraphNode, IWidget } from "@litegraph-ts/core";
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type ComfyGraphNode from '$lib/nodes/ComfyGraphNode';
|
||||
import type ComfyWidget from '$lib/widgets/ComfyWidget';
|
||||
|
||||
/** store for one widget's state */
|
||||
export type WidgetUIStateStore = Writable<any>
|
||||
|
||||
export type WidgetUIState = {
|
||||
/** position in the node's list of widgets */
|
||||
index: number,
|
||||
/** parent node containing the widget */
|
||||
node: LGraphNode,
|
||||
/** actual widget instance */
|
||||
widget: IWidget,
|
||||
/** widget value as a store, to react to changes */
|
||||
value: WidgetUIStateStore,
|
||||
/**
|
||||
* true if this widget was added purely from the frontend. what this means:
|
||||
* - this widget's state will not be saved to the workflow
|
||||
* - the widget was added on startup by some subclass of ComfyGraphNode
|
||||
*/
|
||||
isVirtual: boolean
|
||||
}
|
||||
|
||||
export type WidgetDrawState = {
|
||||
isNodeExecuting: boolean
|
||||
}
|
||||
|
||||
type NodeID = number;
|
||||
|
||||
type WidgetStateOps = {
|
||||
nodeAdded: (node: LGraphNode) => void,
|
||||
nodeRemoved: (node: LGraphNode) => void,
|
||||
configureFinished: (graph: LGraph) => void,
|
||||
widgetStateChanged: (nodeId: number, widget: IWidget<any, any>) => void,
|
||||
findWidgetByName: (nodeId: number, name: string) => WidgetUIState | null,
|
||||
clear: () => void,
|
||||
}
|
||||
|
||||
export type WidgetStateStore = Record<NodeID, WidgetUIState[]>;
|
||||
type WritableWidgetStateStore = Writable<WidgetStateStore> & WidgetStateOps;
|
||||
|
||||
const store: Writable<WidgetStateStore> = writable({})
|
||||
|
||||
function clear() {
|
||||
store.set({})
|
||||
}
|
||||
|
||||
function nodeAdded(node: LGraphNode) {
|
||||
let state = get(store)
|
||||
|
||||
if (node.widgets) {
|
||||
for (const [index, widget] of node.widgets.entries()) {
|
||||
if (!state[node.id])
|
||||
state[node.id] = []
|
||||
let isVirtual = false;
|
||||
if ("isVirtual" in widget)
|
||||
isVirtual = (widget as ComfyWidget<any, any>).isVirtual;
|
||||
state[node.id].push({ index, node, widget, value: writable(widget.value), isVirtual: isVirtual })
|
||||
}
|
||||
}
|
||||
|
||||
console.debug("NODEADDED", state)
|
||||
|
||||
store.set(state);
|
||||
}
|
||||
|
||||
function nodeRemoved(node: LGraphNode) {
|
||||
const state = get(store)
|
||||
delete state[node.id]
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function widgetStateChanged(nodeId: number, widget: IWidget<any, any>) {
|
||||
const state = get(store)
|
||||
const entries = state[nodeId]
|
||||
if (entries) {
|
||||
let widgetState = entries.find(e => e.widget === widget);
|
||||
if (widgetState) {
|
||||
widgetState.value.set(widget.value);
|
||||
store.set(state);
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed and node was found, but widget was not found in state!", widget, widget.node, entries)
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("Widget state changed but node was not found in state!", widget, widget.node)
|
||||
}
|
||||
}
|
||||
|
||||
function configureFinished(graph: LGraph) {
|
||||
let state = get(store);
|
||||
|
||||
for (const node of graph.computeExecutionOrder(false, null)) {
|
||||
if (node.widgets_values) {
|
||||
for (const [i, value] of node.widgets_values.entries()) {
|
||||
if (i < state[node.id].length && !state[node.id][i].isVirtual) {
|
||||
state[node.id][i].value.set(value);
|
||||
}
|
||||
else {
|
||||
console.log("Skip virtual widget", node.id, node.type, state[node.id][i].widget)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
function findWidgetByName(nodeId: number, name: string): WidgetUIState | null {
|
||||
let state = get(store);
|
||||
|
||||
if (!(nodeId in state))
|
||||
return null;
|
||||
|
||||
return state[nodeId].find((v) => v.widget.name === name);
|
||||
}
|
||||
|
||||
const widgetStateStore: WritableWidgetStateStore =
|
||||
{
|
||||
...store,
|
||||
nodeAdded,
|
||||
nodeRemoved,
|
||||
widgetStateChanged,
|
||||
configureFinished,
|
||||
findWidgetByName,
|
||||
clear
|
||||
}
|
||||
export default widgetStateStore;
|
||||
@@ -2,7 +2,7 @@ import ComfyApp from "./components/ComfyApp";
|
||||
import ComboWidget from "$lib/widgets/ComboWidget.svelte";
|
||||
import RangeWidget from "$lib/widgets/RangeWidget.svelte";
|
||||
import TextWidget from "$lib/widgets/TextWidget.svelte";
|
||||
import widgetState, { type WidgetDrawState, type WidgetUIState } from "$lib/stores/widgetState";
|
||||
import { type WidgetUIState } from "$lib/stores/nodeState";
|
||||
|
||||
export function download(filename: string, text: string, type: string = "text/plain") {
|
||||
const blob = new Blob([text], { type: type });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetDrawState, WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
||||
import type { WidgetDrawState, WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import { Dropdown } from "@gradio/form";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { Block } from "@gradio/atoms";
|
||||
import { Gallery } from "@gradio/gallery";
|
||||
import type { Styles } from "@gradio/utils";
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { IEnumWidget, IEnumWidgetOptions, INumberWidget, LGraphNode, Widget
|
||||
import ComfyWidget from "./ComfyWidget";
|
||||
import type { ComfyImageResult } from "$lib/nodes/ComfySaveImageNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import widgetState from "$lib/stores/widgetState"
|
||||
import nodeState from "$lib/stores/nodeState"
|
||||
|
||||
export interface ComfyValueControlWidgetOptions extends IEnumWidgetOptions {
|
||||
}
|
||||
@@ -50,6 +50,6 @@ export default class ComfyValueControlWidget extends ComfyWidget<ComfyValueContr
|
||||
if (this.targetWidget.value > max)
|
||||
this.targetWidget.value = max;
|
||||
|
||||
widgetState.widgetStateChanged(this.node.id, this.targetWidget);
|
||||
nodeState.widgetStateChanged(this.node.id, this.targetWidget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import type { IWidget, LGraphNode, SerializedLGraphNode, Vector2, WidgetCallback, WidgetTypes } from "@litegraph-ts/core";
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
|
||||
export default abstract class ComfyWidget<T = any, V = any> implements IWidget<T, V> {
|
||||
name: string;
|
||||
@@ -27,7 +27,7 @@ export default abstract class ComfyWidget<T = any, V = any> implements IWidget<T
|
||||
|
||||
setValue(value: V) {
|
||||
this.value = value;
|
||||
widgetState.widgetStateChanged(this.node.id, this);
|
||||
nodeState.widgetStateChanged(this.node.id, this);
|
||||
}
|
||||
|
||||
draw?(ctx: CanvasRenderingContext2D, node: LGraphNode, width: number, posY: number, height: number): void;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { Range } from "@gradio/form";
|
||||
import { get } from "svelte/store";
|
||||
export let item: WidgetUIState | null = null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/nodeState";
|
||||
import { TextBox } from "@gradio/form";
|
||||
export let item: WidgetUIState | null = null;
|
||||
let itemValue: WidgetUIStateStore | null = null;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
@@ -47,16 +46,11 @@
|
||||
return
|
||||
app = $uiState.app = new ComfyApp();
|
||||
|
||||
// TODO dedup
|
||||
app.eventBus.on("nodeAdded", nodeState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", nodeState.nodeRemoved);
|
||||
app.eventBus.on("configured", nodeState.configureFinished);
|
||||
app.eventBus.on("cleared", nodeState.clear);
|
||||
|
||||
app.eventBus.on("nodeAdded", widgetState.nodeAdded);
|
||||
app.eventBus.on("nodeRemoved", widgetState.nodeRemoved);
|
||||
app.eventBus.on("configured", widgetState.configureFinished);
|
||||
app.eventBus.on("cleared", widgetState.clear);
|
||||
app.eventBus.on("autosave", doAutosave);
|
||||
app.eventBus.on("restored", doRestore);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import ComfyApp, { type SerializedAppState } from "$lib/components/ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import widgetState from "$lib/stores/widgetState";
|
||||
import nodeState from "$lib/stores/nodeState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
@@ -29,7 +28,7 @@
|
||||
<Page name="subworkflow">
|
||||
<Navbar title="Workflow {subworkflowID}" backLink="Back" />
|
||||
|
||||
{#each Object.entries($widgetState) as [id, ws]}
|
||||
{#each Object.entries($nodeState) as [id, ws]}
|
||||
{@const node = app.lGraph.getNodeById(id)}
|
||||
<div class:is-executing={$queueState.runningNodeId === node.id}>
|
||||
<Block>
|
||||
@@ -38,7 +37,7 @@
|
||||
{node.title}
|
||||
</BlockTitle>
|
||||
</label>
|
||||
{#each $widgetState[id] as item}
|
||||
{#each $nodeState[id].widgetStates as item}
|
||||
<svelte:component this={getComponentForWidgetState(item)} {item} />
|
||||
{/each}
|
||||
</Block>
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
"checkJs": true,
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"$lib": ["lib"],
|
||||
"$lib/*": ["lib/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
||||
Reference in New Issue
Block a user