Huge refactoring for multiple workflows
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type { DragItemID, IDragItem } from './layoutState';
|
||||
import type { DragItemID, IDragItem } from './layoutStates';
|
||||
import type { LGraphNode, NodeID } from '@litegraph-ts/core';
|
||||
|
||||
export type SelectionState = {
|
||||
|
||||
261
src/lib/stores/workflowState.ts
Normal file
261
src/lib/stores/workflowState.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import type { SerializedGraphCanvasState } from '$lib/ComfyGraphCanvas';
|
||||
import type { LGraphCanvas, SerializedLGraph, UUID } from '@litegraph-ts/core';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import type { Readable, Writable } from 'svelte/store';
|
||||
import type { SerializedLayoutState, WritableLayoutStateStore } from './layoutStates';
|
||||
import ComfyGraph from '$lib/ComfyGraph';
|
||||
import layoutStates from './layoutStates';
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type ComfyGraphCanvas from '$lib/ComfyGraphCanvas';
|
||||
import { blankGraph } from '$lib/defaultGraph';
|
||||
import type { SerializedAppState } from '$lib/components/ComfyApp';
|
||||
|
||||
type ActiveCanvas = {
|
||||
canvas: LGraphCanvas | null;
|
||||
canvasHandler: () => void | null;
|
||||
state: SerializedGraphCanvasState;
|
||||
}
|
||||
|
||||
export type SerializedWorkflowState = {
|
||||
graph: SerializedLGraph,
|
||||
layout: SerializedLayoutState
|
||||
}
|
||||
|
||||
/*
|
||||
* ID for an opened workflow.
|
||||
*
|
||||
* Unlike NodeID and PromptID, these are *not* saved to the workflow itself.
|
||||
* They are only used for identifying an open workflow in the program. If the
|
||||
* workflow is closed and reopened, a different workflow ID will be assigned to
|
||||
* it.
|
||||
*/
|
||||
export type WorkflowInstID = UUID;
|
||||
|
||||
export class ComfyWorkflow {
|
||||
/*
|
||||
* Used for uniquely identifying the instance of the opened workflow in the frontend.
|
||||
*/
|
||||
id: WorkflowInstID;
|
||||
title: string;
|
||||
graph: ComfyGraph;
|
||||
|
||||
get layout(): WritableLayoutStateStore | null {
|
||||
return layoutStates.getLayout(this.id)
|
||||
}
|
||||
|
||||
canvases: Record<string, ActiveCanvas> = {};
|
||||
|
||||
constructor(title: string) {
|
||||
this.id = uuidv4();
|
||||
this.title = title;
|
||||
this.graph = new ComfyGraph(this.id);
|
||||
}
|
||||
|
||||
start(key: string, canvas: ComfyGraphCanvas) {
|
||||
if (this.canvases[key] != null)
|
||||
throw new Error(`This workflow is already being displayed on canvas ${key}`)
|
||||
|
||||
const canvasHandler = () => canvas.draw(true);
|
||||
|
||||
this.canvases[key] = {
|
||||
canvas,
|
||||
canvasHandler,
|
||||
state: {
|
||||
// TODO
|
||||
offset: [0, 0],
|
||||
scale: 1
|
||||
}
|
||||
}
|
||||
|
||||
this.graph.attachCanvas(canvas);
|
||||
this.graph.eventBus.on("afterExecute", canvasHandler)
|
||||
|
||||
if (Object.keys(this.canvases).length === 1)
|
||||
this.graph.start();
|
||||
}
|
||||
|
||||
stop(key: string) {
|
||||
const canvas = this.canvases[key]
|
||||
if (canvas == null)
|
||||
throw new Error(`This workflow is not being displayed on canvas ${key}`)
|
||||
|
||||
this.graph.detachCanvas(canvas.canvas);
|
||||
this.graph.eventBus.removeListener("afterExecute", canvas.canvasHandler)
|
||||
|
||||
delete this.canvases[key]
|
||||
|
||||
if (Object.keys(this.canvases).length === 0)
|
||||
this.graph.stop();
|
||||
}
|
||||
|
||||
stopAll() {
|
||||
for (const key of Object.keys(this.canvases))
|
||||
this.stop(key)
|
||||
this.graph.stop()
|
||||
}
|
||||
|
||||
serialize(layoutState: WritableLayoutStateStore): SerializedWorkflowState {
|
||||
const graph = this.graph;
|
||||
|
||||
const serializedGraph = graph.serialize()
|
||||
const serializedLayout = layoutState.serialize()
|
||||
|
||||
return {
|
||||
graph: serializedGraph,
|
||||
layout: serializedLayout
|
||||
}
|
||||
}
|
||||
|
||||
static create(title: string = "New Workflow"): [ComfyWorkflow, WritableLayoutStateStore] {
|
||||
const workflow = new ComfyWorkflow(title);
|
||||
const layoutState = layoutStates.create(workflow);
|
||||
return [workflow, layoutState]
|
||||
}
|
||||
|
||||
deserialize(layoutState: WritableLayoutStateStore, data: SerializedWorkflowState) {
|
||||
// Ensure loadGraphData does not trigger any state changes in layoutState
|
||||
// (isConfiguring is set to true here)
|
||||
// lGraph.configure will add new nodes, triggering onNodeAdded, but we
|
||||
// want to restore the layoutState ourselves
|
||||
layoutState.onStartConfigure();
|
||||
|
||||
// Patch T2IAdapterLoader to ControlNetLoader since they are the same node now
|
||||
for (let n of data.graph.nodes) {
|
||||
if (n.type == "T2IAdapterLoader") n.type = "ControlNetLoader";
|
||||
}
|
||||
|
||||
this.graph.configure(data.graph);
|
||||
|
||||
for (const node of this.graph._nodes) {
|
||||
const size = node.computeSize();
|
||||
size[0] = Math.max(node.size[0], size[0]);
|
||||
size[1] = Math.max(node.size[1], size[1]);
|
||||
node.size = size;
|
||||
// this.#invokeExtensions("loadedGraphNode", node);
|
||||
}
|
||||
|
||||
// Now restore the layout
|
||||
// Subsequent added nodes will add the UI data to layoutState
|
||||
// TODO
|
||||
layoutState.deserialize(data.layout, this.graph)
|
||||
}
|
||||
}
|
||||
|
||||
export type WorkflowState = {
|
||||
openedWorkflows: ComfyWorkflow[],
|
||||
openedWorkflowsByID: Record<WorkflowInstID, ComfyWorkflow>,
|
||||
activeWorkflowIdx: number,
|
||||
activeWorkflow: ComfyWorkflow | null,
|
||||
}
|
||||
|
||||
type WorkflowStateOps = {
|
||||
getWorkflow: (id: WorkflowInstID) => ComfyWorkflow | null
|
||||
getActiveWorkflow: () => ComfyWorkflow | null
|
||||
createNewWorkflow: () => ComfyWorkflow,
|
||||
openWorkflow: (data: SerializedAppState) => ComfyWorkflow,
|
||||
closeWorkflow: (index: number) => void,
|
||||
closeAllWorkflows: () => void,
|
||||
setActiveWorkflow: (index: number) => ComfyWorkflow | null
|
||||
}
|
||||
|
||||
export type WritableWorkflowStateStore = Writable<WorkflowState> & WorkflowStateOps;
|
||||
const store: Writable<WorkflowState> = writable(
|
||||
{
|
||||
openedWorkflows: [],
|
||||
openedWorkflowsByID: {},
|
||||
activeWorkflowIdx: -1
|
||||
})
|
||||
|
||||
function getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
||||
return get(store).openedWorkflowsByID[id];
|
||||
}
|
||||
|
||||
function getActiveWorkflow(): ComfyWorkflow | null {
|
||||
const state = get(store);
|
||||
if (state.activeWorkflowIdx === -1)
|
||||
return null;
|
||||
return state.openedWorkflows[state.activeWorkflowIdx];
|
||||
}
|
||||
|
||||
function createNewWorkflow(): ComfyWorkflow {
|
||||
const workflow = new ComfyWorkflow("Workflow X");
|
||||
const layoutState = layoutStates.create(workflow);
|
||||
workflow.deserialize(layoutState, { graph: blankGraph.workflow, layout: blankGraph.layout })
|
||||
|
||||
const state = get(store);
|
||||
this.openedWorkflows.push(workflow);
|
||||
setActiveWorkflow(state.openedWorkflows.length - 1)
|
||||
|
||||
store.set(state)
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
function openWorkflow(data: SerializedAppState): ComfyWorkflow {
|
||||
const [workflow, layoutState] = ComfyWorkflow.create("Workflow X")
|
||||
workflow.deserialize(layoutState, { graph: data.workflow, layout: data.layout })
|
||||
|
||||
const state = get(store);
|
||||
state.openedWorkflows.push(workflow);
|
||||
setActiveWorkflow(state.openedWorkflows.length - 1)
|
||||
|
||||
store.set(state)
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
function closeWorkflow(index: number) {
|
||||
const state = get(store);
|
||||
|
||||
if (index < 0 || index >= state.openedWorkflows.length)
|
||||
return;
|
||||
|
||||
const workflow = state.openedWorkflows[index];
|
||||
workflow.stopAll();
|
||||
|
||||
state.openedWorkflows.splice(index, 1)
|
||||
setActiveWorkflow(0);
|
||||
|
||||
store.set(state);
|
||||
}
|
||||
|
||||
function closeAllWorkflows() {
|
||||
const state = get(store)
|
||||
while (state.openedWorkflows.length > 0)
|
||||
closeWorkflow(0)
|
||||
}
|
||||
|
||||
function setActiveWorkflow(index: number): ComfyWorkflow | null {
|
||||
const state = get(store);
|
||||
|
||||
if (state.openedWorkflows.length === 0) {
|
||||
state.activeWorkflowIdx = -1;
|
||||
state.activeWorkflow = null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= state.openedWorkflows.length || state.activeWorkflowIdx === index)
|
||||
return state.activeWorkflow;
|
||||
|
||||
if (state.activeWorkflow != null)
|
||||
state.activeWorkflow.stop("app")
|
||||
|
||||
const workflow = state.openedWorkflows[index]
|
||||
state.activeWorkflowIdx = index;
|
||||
state.activeWorkflow = workflow;
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
const workflowStateStore: WritableWorkflowStateStore =
|
||||
{
|
||||
...store,
|
||||
getWorkflow,
|
||||
getActiveWorkflow,
|
||||
createNewWorkflow,
|
||||
openWorkflow,
|
||||
closeWorkflow,
|
||||
closeAllWorkflows,
|
||||
setActiveWorkflow,
|
||||
}
|
||||
export default workflowStateStore;
|
||||
Reference in New Issue
Block a user