This commit is contained in:
space-nuko
2023-04-25 04:55:55 -07:00
parent 377b0cb59e
commit 76a22c47f6
11 changed files with 5845 additions and 533 deletions

View File

@@ -1,5 +1,6 @@
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 type ComfyApp from "./components/ComfyApp";
import type { Unsubscriber } from "svelte/store";
@@ -8,6 +9,11 @@ type WidgetSubStore = {
unsubscribe: Unsubscriber
}
type NodeSubStore = {
store: NodeUIStateStore,
unsubscribe: Unsubscriber
}
/*
* Responsible for watching for and synchronizing state changes from the
* frontend to the litegraph instance.
@@ -32,10 +38,12 @@ export default class GraphSync {
constructor(app: ComfyApp) {
this.graph = app.lGraph;
this._unsubscribe = widgetState.subscribe(this.onAllWidgetStateChanged.bind(this));
this._unsubscribeWidget = widgetState.subscribe(this.onAllWidgetStateChanged.bind(this));
this._unsubscribeNode = nodeState.subscribe(this.onAllNodeStateChanged.bind(this));
this._finalizer = new FinalizationRegistry((id: number) => {
console.log(`${this} has been garbage collected`);
this._unsubscribe();
this._unsubscribeWidget();
this._unsubscribeNode();
});
}
@@ -58,6 +66,16 @@ export default class GraphSync {
}
}
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) {
if (this.stores[nodeId]) {
console.warn("Stores already exist!", nodeId, this.stores[nodeId])
@@ -67,7 +85,7 @@ export default class GraphSync {
for (const wuis of state[nodeId]) {
const unsub = wuis.value.subscribe((v) => this.onWidgetStateChanged(wuis, v))
this.stores[nodeId].push({ store: wuis.vlue, unsubscribe: unsub });
this.stores[nodeId].push({ store: wuis.value, unsubscribe: unsub });
}
console.log("NEWSTORES", this.stores[nodeId])

View File

@@ -179,7 +179,7 @@ export default class ComfyAPI extends EventTarget {
prompt: output,
extra_data: { extra_pnginfo: { workflow } },
front: false,
number: null
number: number
};
if (number === -1) {

View File

@@ -7,6 +7,8 @@
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";
import { download } from "$lib/utils"
@@ -23,8 +25,6 @@
let queue: ComfyQueue = undefined;
let mainElem: HTMLDivElement;
let containerElem: HTMLDivElement;
let nodesLocked: boolean = false;
let graphLocked: boolean = false;
let resizeTimeout: typeof Timer = -1;
function refreshView(event?: Event) {
@@ -37,8 +37,8 @@
app.queuePrompt(0, 1);
}
$: if (app) app.lCanvas.allow_dragnodes = !nodesLocked;
$: if (app) app.lCanvas.allow_interaction = !graphLocked;
$: if (app) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
$: if (app) app.lCanvas.allow_interaction = !$uiState.graphLocked;
let graphSize = null;
@@ -102,6 +102,12 @@
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);
@@ -167,8 +173,9 @@
<Button variant="secondary" on:click={doSave}>
Save
</Button>
<Checkbox label="Lock Nodes" bind:value={nodesLocked}/>
<Checkbox label="Disable Interaction" bind:value={graphLocked}/>
<Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/>
<Checkbox label="Enable UI Editing" bind:value={$uiState.unlocked}/>
</div>
<LightboxModal />
</div>

View File

@@ -8,6 +8,8 @@
import TextWidget from "$lib/widgets/TextWidget.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";
import { dndzone, SHADOW_ITEM_MARKER_PROPERTY_NAME } from 'svelte-dnd-action';
@@ -21,6 +23,7 @@
export let dragItems: DragItem[] = [];
let dragDisabled = true;
let unlockUI = false;
const flipDurationMs = 200;
const handleConsider = evt => {
@@ -76,6 +79,11 @@
return null;
}
function updateNodeName(node: LGraphNode, value: string) {
console.log("CHA")
nodeState.nodeStateChanged(node);
}
</script>
@@ -89,11 +97,22 @@
{@const id = node.id}
<div class="animation-wrapper" class:is-executing={dragItem.isNodeExecuting} animate:flip={{duration:flipDurationMs}}>
<Block>
{#if $uiState.unlocked}
<div class="handle" on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}>
<Move/>
</div>
{/if}
<label for={String(id)}>
<BlockTitle>{node.title}</BlockTitle>
<BlockTitle>
{#if $uiState.unlocked}
<input bind:value={dragItem.node.title} type="text" minlength="1" on:input="{(v) => { updateNodeName(node, v) }}"/>
{:else}
{node.title}
{/if}
{#if node.title !== node.type}
<span class="node-type">({node.type})</span>
{/if}
</BlockTitle>
</label>
{#each $widgetState[id] as item}
<svelte:component this={getComponentForWidgetState(item)} {item} />
@@ -145,4 +164,9 @@
opacity: 0.5;
margin: 0;
}
.node-type {
font-size: smaller;
color: var(--neutral-400);
}
</style>

View File

@@ -0,0 +1,71 @@
import { writable, get } from 'svelte/store';
import type { LGraph, LGraphNode } from "@litegraph-ts/core";
import type { Readable, Writable } from 'svelte/store';
import type ComfyGraphNode from '$lib/nodes/ComfyGraphNode';
/** store for one node's state */
export type NodeUIStateStore = Writable<any>
export type NodeUIState = {
name: string,
node: LGraphNode
}
type NodeID = number;
type NodeStateOps = {
nodeAdded: (node: LGraphNode) => void,
nodeRemoved: (node: LGraphNode) => void,
configureFinished: (graph: LGraph) => void,
nodeStateChanged: (node: LGraphNode) => void,
clear: () => void,
}
export type NodeStateStore = Record<NodeID, NodeUIState>;
type WritableNodeStateStore = Writable<NodeStateStore> & NodeStateOps;
const store: Writable<NodeStateStore> = writable({})
function clear() {
store.set({})
}
function nodeAdded(node: LGraphNode) {
let state = get(store)
state[node.id] = { node: node, name: node.name }
store.set(state);
}
function nodeRemoved(node: LGraphNode) {
const state = get(store)
delete state[node.id]
store.set(state)
}
function nodeStateChanged(node: LGraphNode) {
const state = get(store)
const nodeState = state[node.id]
nodeState.name = node.name
store.set(state);
}
function configureFinished(graph: LGraph) {
let state = get(store);
for (const node of graph.computeExecutionOrder(false, null)) {
state[node.id].name = name;
}
store.set(state)
}
const nodeStateStore: WritableNodeStateStore =
{
...store,
nodeAdded,
nodeRemoved,
nodeStateChanged,
configureFinished,
clear
}
export default nodeStateStore;

14
src/lib/stores/uiState.ts Normal file
View File

@@ -0,0 +1,14 @@
import { writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
export type UIState = {
unlocked: boolean,
}
const store: Writable<UIState> = writable({ unlocked: false })
const uiStateStore: WritableUIStateStore =
{
...store
}
export default uiStateStore;

View File

@@ -4,8 +4,6 @@ import type { Readable, Writable } from 'svelte/store';
import type ComfyGraphNode from '$lib/nodes/ComfyGraphNode';
import type ComfyWidget from '$lib/widgets/ComfyWidget';
import { subStore } from "immer-loves-svelte"
/** store for one widget's state */
export type WidgetUIStateStore = Writable<any>