Sync frontend state to backend
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import type { LGraph } from "@litegraph-ts/core";
|
import type { LGraph } from "@litegraph-ts/core";
|
||||||
import widgetState, { type WidgetStateStore, type WidgetUIStateStore } from "./stores/widgetState";
|
import widgetState, { type WidgetStateStore, type WidgetUIState, type WidgetUIStateStore } from "./stores/widgetState";
|
||||||
import type ComfyApp from "./components/ComfyApp";
|
import type ComfyApp from "./components/ComfyApp";
|
||||||
import type { Unsubscriber } from "svelte/store";
|
import type { Unsubscriber } from "svelte/store";
|
||||||
|
|
||||||
@@ -9,12 +9,18 @@ type WidgetSubStore = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Responsible for watching and synchronizing state changes from the frontend to the litegraph instance.
|
* Responsible for watching for and synchronizing state changes from the
|
||||||
* The other way around is unnecessary since the nodes in ComfyBox can't be interacted with.
|
* frontend to the litegraph instance.
|
||||||
|
*
|
||||||
|
* The other way around is unnecessary since the nodes in ComfyBox can't be
|
||||||
|
* interacted with. If that were true the implementation would be way more
|
||||||
|
* complex since litegraph doesn't (currently) expose a global
|
||||||
|
* event-emitter-like thing for when nodes/widgets are changed.
|
||||||
*
|
*
|
||||||
* Assumptions:
|
* Assumptions:
|
||||||
* - Widgets can't be added to a node after they're created
|
* - Widgets can't be added to a node after they're created (messes up the indices in WidgetSubStore[])
|
||||||
* - Widgets can't be interacted with from the graph, only from the frontend
|
* - Widgets can't be interacted with from the graph, only from the frontend
|
||||||
|
* - Only one workflow/graph can ever be loaded into the program
|
||||||
*/
|
*/
|
||||||
export default class GraphSync {
|
export default class GraphSync {
|
||||||
graph: LGraph;
|
graph: LGraph;
|
||||||
@@ -26,18 +32,19 @@ export default class GraphSync {
|
|||||||
|
|
||||||
constructor(app: ComfyApp) {
|
constructor(app: ComfyApp) {
|
||||||
this.graph = app.lGraph;
|
this.graph = app.lGraph;
|
||||||
this._unsubscribe = widgetState.subscribe(this.onWidgetStateChanged.bind(this));
|
this._unsubscribe = widgetState.subscribe(this.onAllWidgetStateChanged.bind(this));
|
||||||
this._finalizer = new FinalizationRegistry((id: number) => {
|
this._finalizer = new FinalizationRegistry((id: number) => {
|
||||||
console.log(`${this} has been garbage collected`);
|
console.log(`${this} has been garbage collected`);
|
||||||
this._unsubscribe();
|
this._unsubscribe();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWidgetStateChanged(state: WidgetStateStore) {
|
/*
|
||||||
|
* Fired when the entire widget graph changes.
|
||||||
|
*/
|
||||||
|
private onAllWidgetStateChanged(state: WidgetStateStore) {
|
||||||
// TODO assumes only a single graph's widget state.
|
// TODO assumes only a single graph's widget state.
|
||||||
|
|
||||||
console.warn("ONWIDGETSTATECHANGE")
|
|
||||||
|
|
||||||
for (let nodeId in state) {
|
for (let nodeId in state) {
|
||||||
if (!this.stores[nodeId]) {
|
if (!this.stores[nodeId]) {
|
||||||
this.addStores(state, nodeId);
|
this.addStores(state, nodeId);
|
||||||
@@ -59,10 +66,8 @@ export default class GraphSync {
|
|||||||
this.stores[nodeId] = []
|
this.stores[nodeId] = []
|
||||||
|
|
||||||
for (const wuis of state[nodeId]) {
|
for (const wuis of state[nodeId]) {
|
||||||
const unsub = wuis.value.subscribe((v) => {
|
const unsub = wuis.value.subscribe((v) => this.onWidgetStateChanged(wuis, v))
|
||||||
console.log("CHANGE", 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])
|
console.log("NEWSTORES", this.stores[nodeId])
|
||||||
@@ -75,4 +80,12 @@ export default class GraphSync {
|
|||||||
}
|
}
|
||||||
delete this.stores[nodeId]
|
delete this.stores[nodeId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fired when a single widget's value changes.
|
||||||
|
*/
|
||||||
|
private onWidgetStateChanged(wuis: WidgetUIState, value: any) {
|
||||||
|
wuis.widget.value = value;
|
||||||
|
this.graph.setDirtyCanvas(true, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,19 @@ import { subStore } from "immer-loves-svelte"
|
|||||||
export type WidgetUIStateStore = Writable<any>
|
export type WidgetUIStateStore = Writable<any>
|
||||||
|
|
||||||
export type WidgetUIState = {
|
export type WidgetUIState = {
|
||||||
|
/** position in the node's list of widgets */
|
||||||
|
index: number,
|
||||||
|
/** parent node containing the widget */
|
||||||
node: LGraphNode,
|
node: LGraphNode,
|
||||||
|
/** actual widget instance */
|
||||||
widget: IWidget,
|
widget: IWidget,
|
||||||
|
/** widget value as a store, to react to changes */
|
||||||
value: WidgetUIStateStore,
|
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
|
isVirtual: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,18 +9,23 @@
|
|||||||
let option: any;
|
let option: any;
|
||||||
|
|
||||||
$: if(item) {
|
$: if(item) {
|
||||||
option = get(item.value);
|
if (!itemValue)
|
||||||
itemValue = item.value
|
itemValue = item.value;
|
||||||
|
if (!option)
|
||||||
|
option = get(item.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$: if (option && itemValue) {
|
||||||
|
$itemValue = option.value
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#if item}
|
{#if item && $itemValue}
|
||||||
<label>
|
<label>
|
||||||
<BlockTitle show_label={true}>{item.widget.name}</BlockTitle>
|
<BlockTitle show_label={true}>{item.widget.name}</BlockTitle>
|
||||||
<Select
|
<Select
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
bind:justValue={$itemValue}
|
|
||||||
bind:items={item.widget.options.values}
|
bind:items={item.widget.options.values}
|
||||||
disabled={item.widget.options.values.length === 0}
|
disabled={item.widget.options.values.length === 0}
|
||||||
clearable={false}
|
clearable={false}
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
import type { WidgetUIState, WidgetUIStateStore } from "$lib/stores/widgetState";
|
||||||
import { Range } from "@gradio/form";
|
import { Range } from "@gradio/form";
|
||||||
|
import { get } from "svelte/store";
|
||||||
export let item: WidgetUIState | null = null;
|
export let item: WidgetUIState | null = null;
|
||||||
let itemValue: WidgetUIStateStore | null = null;
|
let itemValue: WidgetUIStateStore | null = null;
|
||||||
$: if (item) { itemValue = item.value; }
|
let option: number | null = null;
|
||||||
|
|
||||||
|
$: if (item && !option) {
|
||||||
|
if (!itemValue)
|
||||||
|
itemValue = item.value;
|
||||||
|
option = get(item.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRelease(e: Event) {
|
||||||
|
if (itemValue && option) {
|
||||||
|
$itemValue = option
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
{#if item && itemValue}
|
{#if item && option}
|
||||||
<Range
|
<Range
|
||||||
bind:value={$itemValue}
|
bind:value={option}
|
||||||
minimum={item.widget.options.min}
|
minimum={item.widget.options.min}
|
||||||
maximum={item.widget.options.max}
|
maximum={item.widget.options.max}
|
||||||
step={item.widget.options.step}
|
step={item.widget.options.step}
|
||||||
label={item.widget.name}
|
label={item.widget.name}
|
||||||
show_label={true}
|
show_label={true}
|
||||||
|
on:release={onRelease}
|
||||||
on:change
|
on:change
|
||||||
on:release
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user