Save panel ordering to workflow file

This commit is contained in:
space-nuko
2023-04-07 09:34:29 -05:00
parent c40143ef88
commit eed4e29004
3 changed files with 107 additions and 19 deletions

View File

@@ -4,10 +4,10 @@
import { Pane, Splitpanes } from 'svelte-splitpanes'; import { Pane, Splitpanes } from 'svelte-splitpanes';
import { Button } from "@gradio/button"; import { Button } from "@gradio/button";
import ComfyUIPane from "./ComfyUIPane.svelte"; import ComfyUIPane from "./ComfyUIPane.svelte";
import ComfyApp from "./ComfyApp"; import ComfyApp, { type SerializedAppState } from "./ComfyApp";
import widgetState from "$lib/stores/widgetState"; import widgetState from "$lib/stores/widgetState";
import { LGraphNode } from "@litegraph-ts/core"; import { LGraph, LGraphNode } from "@litegraph-ts/core";
let app: ComfyApp = undefined; let app: ComfyApp = undefined;
let uiPane: ComfyUIPane = undefined; let uiPane: ComfyUIPane = undefined;
@@ -51,6 +51,22 @@
let graphResizeTimer: typeof Timer = -1; let graphResizeTimer: typeof Timer = -1;
function doAutosave(graph: LGraph): void {
const serializedGraph = graph.serialize()
const serializedPaneOrder = uiPane.serialize()
const savedWorkflow = {
graph: serializedGraph,
panes: serializedPaneOrder
}
localStorage.setItem("workflow", JSON.stringify(savedWorkflow))
}
function doRestore(workflow: SerializedAppState) {
uiPane.restore(workflow.panes);
}
onMount(async () => { onMount(async () => {
app = new ComfyApp(); app = new ComfyApp();
@@ -58,11 +74,14 @@
app.eventBus.on("nodeRemoved", widgetState.nodeRemoved); app.eventBus.on("nodeRemoved", widgetState.nodeRemoved);
app.eventBus.on("configured", widgetState.configureFinished); app.eventBus.on("configured", widgetState.configureFinished);
app.eventBus.on("cleared", widgetState.clear); app.eventBus.on("cleared", widgetState.clear);
app.eventBus.on("autosave", doAutosave);
app.eventBus.on("restored", doRestore);
await app.setup(); await app.setup();
refreshView(); refreshView();
(window as any).app = app; (window as any).app = app;
(window as any).appPane = uiPane;
let wrappers = containerElem.querySelectorAll<HTMLDivNode>(".pane-wrapper") let wrappers = containerElem.querySelectorAll<HTMLDivNode>(".pane-wrapper")
for (const wrapper of wrappers) { for (const wrapper of wrappers) {

View File

@@ -22,12 +22,25 @@ if (typeof window !== "undefined") {
type QueueItem = { num: number, batchCount: number } type QueueItem = { num: number, batchCount: number }
export type SerializedPanes = {
panels: { nodeId: number }[][]
}
export type SerializedAppState = {
panes: SerializedPanes,
workflow: SerializedLGraph
}
type ComfyAppEvents = { type ComfyAppEvents = {
configured: (graph: LGraph) => void configured: (graph: LGraph) => void
nodeAdded: (node: LGraphNode) => void nodeAdded: (node: LGraphNode) => void
nodeRemoved: (node: LGraphNode) => void nodeRemoved: (node: LGraphNode) => void
nodeConnectionChanged: (kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) => void nodeConnectionChanged: (kind: LConnectionKind, node: LGraphNode, slot: INodeSlot, targetNode: LGraphNode, targetSlot: INodeSlot) => void
cleared: () => void cleared: () => void
beforeChange: (graph: LGraph, param: any) => void
afterChange: (graph: LGraph, param: any) => void
autosave: (graph: LGraph) => void
restored: (workflow: SerializedAppState) => void
} }
interface ComfyGraphNodeExecutable extends LGraphNodeExecutable { interface ComfyGraphNodeExecutable extends LGraphNodeExecutable {
@@ -85,8 +98,9 @@ export default class ComfyApp {
try { try {
const json = localStorage.getItem("workflow"); const json = localStorage.getItem("workflow");
if (json) { if (json) {
const workflow = JSON.parse(json); const workflow = JSON.parse(json) as SerializedAppState;
this.loadGraphData(workflow); this.loadGraphData(workflow["workflow"]);
this.eventBus.emit("restored", workflow);
restored = true; restored = true;
} }
} catch (err) { } catch (err) {
@@ -99,7 +113,7 @@ export default class ComfyApp {
} }
// Save current workflow automatically // Save current workflow automatically
setInterval(() => localStorage.setItem("workflow", JSON.stringify(this.lGraph.serialize())), 1000); setInterval(this.requestAutosave.bind(this), 1000);
this.addApiUpdateHandlers(); this.addApiUpdateHandlers();
this.addDropHandler(); this.addDropHandler();
@@ -158,6 +172,10 @@ export default class ComfyApp {
this.eventBus.emit("cleared"); this.eventBus.emit("cleared");
} }
private requestAutosave() {
this.eventBus.emit("autosave", this.lGraph);
}
private addGraphLifecycleHooks() { private addGraphLifecycleHooks() {
this.lGraph.onConfigure = this.graphOnConfigure.bind(this); this.lGraph.onConfigure = this.graphOnConfigure.bind(this);
this.lGraph.onBeforeChange = this.graphOnBeforeChange.bind(this); this.lGraph.onBeforeChange = this.graphOnBeforeChange.bind(this);
@@ -372,11 +390,11 @@ export default class ComfyApp {
* Populates the graph with the specified workflow data * Populates the graph with the specified workflow data
* @param {*} graphData A serialized graph object * @param {*} graphData A serialized graph object
*/ */
loadGraphData(graphData: any = null) { loadGraphData(graphData?: SerializedLGraph) {
this.clean(); this.clean();
if (!graphData) { if (!graphData) {
graphData = structuredClone(defaultGraph); graphData = structuredClone(defaultGraph) as SerializedLGraph;
} }
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now // Patch T2IAdapterLoader to ControlNetLoader since they are the same node now

View File

@@ -2,24 +2,30 @@
import { get } from "svelte/store"; import { get } from "svelte/store";
import { LGraphNode, LGraph } from "@litegraph-ts/core"; import { LGraphNode, LGraph } from "@litegraph-ts/core";
import type { IWidget } from "@litegraph-ts/core"; import type { IWidget } from "@litegraph-ts/core";
import ComfyApp from "./ComfyApp"; import ComfyApp from "./ComfyApp";
import type { SerializedPanes } from "./ComfyApp"
import ComfyPane from "./ComfyPane.svelte"; import ComfyPane from "./ComfyPane.svelte";
import widgetState from "$lib/stores/widgetState"; import widgetState, { type WidgetUIState } from "$lib/stores/widgetState";
type DragItem = {
id: number,
node: LGraphNode
}
export let app: ComfyApp; export let app: ComfyApp;
let dragConfigured: boolean = false; let dragConfigured: boolean = false;
export let dragItemss: any[][] = [] export let dragItems: DragItem[][] = []
export let totalId = 0; export let totalId = 0;
function addUIForNewNode(node: LGraphNode) { function findLeastPopulatedPaneIndex(): number {
let minWidgetCount = 2 ** 64; let minWidgetCount = 2 ** 64;
let minIndex = 0; let minIndex = 0;
let state = get(widgetState); let state = get(widgetState);
for (let i = 0; i < dragItemss.length; i++) { for (let i = 0; i < dragItems.length; i++) {
let widgetCount = 0; let widgetCount = 0;
for (let j = 0; j < dragItemss[i].length; j++) { for (let j = 0; j < dragItems[i].length; j++) {
const nodeID = dragItemss[i][j].node.id; const nodeID = dragItems[i][j].node.id;
widgetCount += state[nodeID].length; widgetCount += state[nodeID].length;
} }
if (widgetCount < minWidgetCount) { if (widgetCount < minWidgetCount) {
@@ -27,7 +33,13 @@
minIndex = i; minIndex = i;
} }
} }
dragItemss[minIndex].push({ id: totalId++, node: node }); return minIndex
}
function addUIForNewNode(node: LGraphNode, paneIndex?: number) {
if (!paneIndex)
paneIndex = findLeastPopulatedPaneIndex();
dragItems[paneIndex].push({ id: totalId++, node: node });
} }
$: if(app && !dragConfigured) { $: if(app && !dragConfigured) {
@@ -38,15 +50,54 @@
/* /*
* Serialize UI panel order so it can be restored when workflow is loaded * Serialize UI panel order so it can be restored when workflow is loaded
*/ */
function getUIState(): any { export function serialize(): any {
let panels = []
for (let i = 0; i < dragItems.length; i++) {
panels[i] = [];
for (let j = 0; j < dragItems[i].length; j++) {
panels[i].push({ nodeId: dragItems[i][j].node.id });
}
}
return {
panels
}
}
export function restore(panels: SerializedPanes) {
let nodeIdToDragItem: Record<number, DragItem> = {};
for (let i = 0; i < dragItems.length; i++) {
for (const dragItem of dragItems[i]) {
nodeIdToDragItem[dragItem.node.id] = dragItem
}
}
for (let i = 0; i < panels.panels.length; i++) {
dragItems[i].length = 0;
for (const panel of panels.panels[i]) {
const dragItem = nodeIdToDragItem[panel.nodeId];
if (dragItem) {
delete nodeIdToDragItem[panel.nodeId];
dragItems[i].push(dragItem)
}
}
}
// Put everything left over into other columns
if (Object.keys(nodeIdToDragItem).length > 0) {
console.warn("Extra panels without ordering found", nodeIdToDragItem)
for (const nodeId in nodeIdToDragItem) {
const dragItem = nodeIdToDragItem[nodeId];
const paneIndex = findLeastPopulatedPaneIndex();
dragItems[paneIndex].push(dragItem);
}
}
} }
</script> </script>
<div id="comfy-ui-panes" > <div id="comfy-ui-panes" >
<ComfyPane bind:dragItems={dragItemss[0]} /> <ComfyPane bind:dragItems={dragItems[0]} />
<ComfyPane bind:dragItems={dragItemss[1]} /> <ComfyPane bind:dragItems={dragItems[1]} />
<ComfyPane bind:dragItems={dragItemss[2]} /> <ComfyPane bind:dragItems={dragItems[2]} />
</div> </div>
<style> <style>