Huge refactoring for multiple workflows
This commit is contained in:
@@ -11,11 +11,12 @@
|
||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
||||
import {cubicIn} from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutStates";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let layoutState: WritableLayoutStateStore;
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
@@ -59,6 +60,14 @@
|
||||
navigator.vibrate(20)
|
||||
$isOpen = e.detail
|
||||
}
|
||||
|
||||
function _startDrag(e: MouseEvent | TouchEvent) {
|
||||
startDrag(e, layoutState)
|
||||
}
|
||||
|
||||
function _stopDrag(e: MouseEvent | TouchEvent) {
|
||||
stopDrag(e, layoutState)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if container}
|
||||
@@ -93,7 +102,7 @@
|
||||
animate:flip={{duration:flipDurationMs}}
|
||||
style={item?.attrs?.style || ""}
|
||||
>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
<WidgetContainer {layoutState} dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
@@ -101,10 +110,18 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||
<div class="handle handle-hidden"
|
||||
style:z-index={zIndex+100}
|
||||
class:hidden={!edit} />
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||
<div class="handle handle-container"
|
||||
style:z-index={zIndex+100}
|
||||
data-drag-item-id={container.id}
|
||||
on:mousedown={_startDrag}
|
||||
on:touchstart={_startDrag}
|
||||
on:mouseup={_stopDrag}
|
||||
on:touchend={_stopDrag}/>
|
||||
{/if}
|
||||
</Accordion>
|
||||
</Block>
|
||||
@@ -112,7 +129,7 @@
|
||||
<Block elem_classes={["gradio-accordion"]}>
|
||||
<Accordion label={container.attrs.title} open={$isOpen} on:click={handleClick}>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)}
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
<WidgetContainer {layoutState} dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{/each}
|
||||
</Accordion>
|
||||
</Block>
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
||||
import {cubicIn} from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let layoutState: WritableLayoutStateStore;
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
@@ -53,6 +54,14 @@
|
||||
children = layoutState.updateChildren(container, evt.detail.items)
|
||||
// Ensure dragging is stopped on drag finish
|
||||
};
|
||||
|
||||
function _startDrag(e: MouseEvent | TouchEvent) {
|
||||
startDrag(e, layoutState)
|
||||
}
|
||||
|
||||
function _stopDrag(e: MouseEvent | TouchEvent) {
|
||||
stopDrag(e, layoutState)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if container}
|
||||
@@ -92,7 +101,7 @@
|
||||
animate:flip={{duration:flipDurationMs}}
|
||||
style={item?.attrs?.style || ""}
|
||||
>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
<WidgetContainer {layoutState} dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
@@ -100,10 +109,18 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||
<div class="handle handle-hidden"
|
||||
style:z-index={zIndex+100}
|
||||
class:hidden={!edit} />
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||
<div class="handle handle-container"
|
||||
style:z-index={zIndex+100}
|
||||
data-drag-item-id={container.id}
|
||||
on:mousedown={_startDrag}
|
||||
on:touchstart={_startDrag}
|
||||
on:mouseup={_stopDrag}
|
||||
on:touchend={_stopDrag}/>
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { ListIcon as List, ImageIcon as Image, SettingsIcon as Settings } from "svelte-feather-icons";
|
||||
import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||
|
||||
import LightboxModal from "./LightboxModal.svelte";
|
||||
@@ -18,8 +17,6 @@
|
||||
let hasShownUIHelpToast: boolean = false;
|
||||
let uiTheme: string = "gradio-dark";
|
||||
|
||||
let debugLayout: boolean = false;
|
||||
|
||||
const toastOptions = {
|
||||
intro: { duration: 200 },
|
||||
theme: {
|
||||
@@ -32,12 +29,6 @@
|
||||
notify("Right-click to open context menu.")
|
||||
}
|
||||
|
||||
if (debugLayout) {
|
||||
layoutState.subscribe(s => {
|
||||
console.warn("UPDATESTATE", s)
|
||||
})
|
||||
}
|
||||
|
||||
$: if (uiTheme === "gradio-dark") {
|
||||
document.getElementById("app-root").classList.add("dark")
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import { type SvelteComponentDev } from "svelte/internal";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
import type { LayoutState, SerializedLayoutState, WritableLayoutStateStore } from "$lib/stores/layoutState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import type { LayoutState, SerializedLayoutState, WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
@@ -41,7 +40,10 @@ import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import layoutStates from "$lib/stores/layoutStates";
|
||||
import { ComfyWorkflow } from "$lib/stores/workflowState";
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -134,133 +136,6 @@ type CanvasState = {
|
||||
canvas: ComfyGraphCanvas,
|
||||
}
|
||||
|
||||
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;
|
||||
layout: WritableLayoutStateStore;
|
||||
|
||||
canvases: Record<string, ActiveCanvas> = {};
|
||||
|
||||
constructor(title: string, graph: ComfyGraph, layout: WritableLayoutStateStore) {
|
||||
this.id = uuidv4();
|
||||
this.title = title;
|
||||
this.layout = layout;
|
||||
this.graph = graph;
|
||||
}
|
||||
|
||||
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(): SerializedWorkflowState {
|
||||
const graph = this.graph;
|
||||
|
||||
const serializedGraph = graph.serialize()
|
||||
const serializedLayout = this.layout.serialize()
|
||||
|
||||
return {
|
||||
graph: serializedGraph,
|
||||
layout: serializedLayout
|
||||
}
|
||||
}
|
||||
|
||||
static deserialize(data: SerializedWorkflowState): ComfyWorkflow {
|
||||
const layout = layoutState; // TODO
|
||||
// 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
|
||||
layout.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";
|
||||
}
|
||||
|
||||
const graph = new ComfyGraph();
|
||||
graph.configure(data.graph);
|
||||
|
||||
for (const node of 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
|
||||
layout.deserialize(data.layout, graph)
|
||||
|
||||
return new ComfyWorkflow("Workflow X", graph, layout);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ComfyApp {
|
||||
api: ComfyAPI;
|
||||
|
||||
@@ -269,22 +144,6 @@ export default class ComfyApp {
|
||||
canvasCtx: CanvasRenderingContext2D | null = null;
|
||||
lCanvas: ComfyGraphCanvas | null = null;
|
||||
|
||||
openedWorkflows: ComfyWorkflow[] = [];
|
||||
openedWorkflowsByID: Record<WorkflowInstID, ComfyWorkflow> = {};
|
||||
activeWorkflowIdx: number = -1;
|
||||
|
||||
get activeWorkflow(): ComfyWorkflow | null {
|
||||
return this.openedWorkflows[this.activeWorkflowIdx]
|
||||
}
|
||||
|
||||
get activeGraph(): ComfyGraph | null {
|
||||
return this.activeWorkflow?.graph;
|
||||
}
|
||||
|
||||
getWorkflow(id: WorkflowInstID): ComfyWorkflow | null {
|
||||
return this.openedWorkflowsByID[id];
|
||||
}
|
||||
|
||||
shiftDown: boolean = false;
|
||||
ctrlDown: boolean = false;
|
||||
selectedGroupMoving: boolean = false;
|
||||
@@ -312,7 +171,7 @@ export default class ComfyApp {
|
||||
|
||||
this.rootEl = document.getElementById("app-root") as HTMLDivElement;
|
||||
this.canvasEl = document.getElementById("graph-canvas") as HTMLCanvasElement;
|
||||
this.lCanvas = new ComfyGraphCanvas(this, null, this.canvasEl);
|
||||
this.lCanvas = new ComfyGraphCanvas(this, this.canvasEl);
|
||||
this.canvasCtx = this.canvasEl.getContext("2d");
|
||||
|
||||
const uiUnlocked = get(uiState).uiUnlocked;
|
||||
@@ -371,12 +230,12 @@ export default class ComfyApp {
|
||||
this.lCanvas.draw(true, true);
|
||||
}
|
||||
|
||||
serialize(): SerializedAppState {
|
||||
const workflow = this.activeWorkflow
|
||||
if (workflow == null)
|
||||
throw new Error("No workflow active!")
|
||||
serialize(workflow: ComfyWorkflow): SerializedAppState {
|
||||
const layoutState = layoutStates.getLayout(workflow.id);
|
||||
if (layoutState == null)
|
||||
throw new Error("Workflow has no layout!")
|
||||
|
||||
const { graph, layout } = workflow.serialize();
|
||||
const { graph, layout } = workflow.serialize(layoutState);
|
||||
const canvas = this.lCanvas.serialize();
|
||||
|
||||
return {
|
||||
@@ -390,14 +249,15 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
saveStateToLocalStorage() {
|
||||
if (this.activeWorkflow == null) {
|
||||
const workflow = workflowState.getActiveWorkflow();
|
||||
if (workflow == null) {
|
||||
notify("No active workflow!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
uiState.update(s => { s.isSavingToLocalStorage = true; return s; })
|
||||
const savedWorkflow = this.serialize();
|
||||
const savedWorkflow = this.serialize(workflow);
|
||||
const json = JSON.stringify(savedWorkflow);
|
||||
localStorage.setItem("workflow", json)
|
||||
notify("Saved to local storage.")
|
||||
@@ -548,25 +408,27 @@ export default class ComfyApp {
|
||||
|
||||
this.api.addEventListener("progress", (progress: Progress) => {
|
||||
queueState.progressUpdated(progress);
|
||||
this.activeGraph?.setDirtyCanvas(true, false); // TODO PromptID
|
||||
workflowState.getActiveWorkflow()?.graph?.setDirtyCanvas(true, false); // TODO PromptID
|
||||
});
|
||||
|
||||
this.api.addEventListener("executing", (promptID: PromptID | null, nodeID: ComfyNodeID | null) => {
|
||||
const queueEntry = queueState.executingUpdated(promptID, nodeID);
|
||||
if (queueEntry != null) {
|
||||
const workflow = this.getWorkflow(queueEntry.workflowID)
|
||||
workflow?.graph.setDirtyCanvas(true, false);
|
||||
const workflow = workflowState.getWorkflow(queueEntry.workflowID);
|
||||
workflow?.graph?.setDirtyCanvas(true, false);
|
||||
}
|
||||
});
|
||||
|
||||
this.api.addEventListener("executed", (promptID: PromptID, nodeID: ComfyNodeID, output: SerializedPromptOutput) => {
|
||||
const queueEntry = queueState.onExecuted(promptID, nodeID, output)
|
||||
if (queueEntry != null) {
|
||||
const workflow = this.getWorkflow(queueEntry.workflowID)
|
||||
workflow?.graph.setDirtyCanvas(true, false);
|
||||
const node = workflow?.graph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||
if (node?.onExecuted) {
|
||||
node.onExecuted(output);
|
||||
const workflow = workflowState.getWorkflow(queueEntry.workflowID);
|
||||
if (workflow != null) {
|
||||
workflow.graph.setDirtyCanvas(true, false);
|
||||
const node = workflow.graph.getNodeByIdRecursive(nodeID) as ComfyGraphNode;
|
||||
if (node?.onExecuted) {
|
||||
node.onExecuted(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -635,68 +497,31 @@ export default class ComfyApp {
|
||||
setColor(BuiltInSlotType.ACTION, "lightseagreen")
|
||||
}
|
||||
|
||||
createNewWorkflow(): ComfyWorkflow {
|
||||
// TODO remove
|
||||
const workflow = ComfyWorkflow.deserialize({ graph: blankGraph.workflow, layout: blankGraph.layout })
|
||||
this.openedWorkflows.push(workflow);
|
||||
this.setActiveWorkflow(this.openedWorkflows.length - 1)
|
||||
return workflow;
|
||||
}
|
||||
|
||||
async openWorkflow(data: SerializedAppState): Promise<ComfyWorkflow> {
|
||||
if (data.version !== COMFYBOX_SERIAL_VERSION) {
|
||||
throw `Invalid ComfyBox saved data format: ${data.version}`
|
||||
}
|
||||
|
||||
this.clean();
|
||||
|
||||
const workflow = ComfyWorkflow.deserialize({ graph: data.workflow, layout: data.layout })
|
||||
const workflow = workflowState.openWorkflow(data);
|
||||
|
||||
// Restore canvas offset/zoom
|
||||
this.lCanvas.deserialize(data.canvas)
|
||||
|
||||
await this.refreshComboInNodes(workflow);
|
||||
|
||||
this.openedWorkflows.push(workflow);
|
||||
this.setActiveWorkflow(this.openedWorkflows.length - 1)
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
closeWorkflow(index: number) {
|
||||
if (index < 0 || index >= this.openedWorkflows.length)
|
||||
return;
|
||||
|
||||
const workflow = this.openedWorkflows[index];
|
||||
workflow.stopAll();
|
||||
|
||||
this.openedWorkflows.splice(index, 1)
|
||||
this.setActiveWorkflow(0);
|
||||
}
|
||||
|
||||
closeAllWorkflows() {
|
||||
while (this.openedWorkflows.length > 0)
|
||||
this.closeWorkflow(0)
|
||||
}
|
||||
|
||||
setActiveWorkflow(index: number) {
|
||||
if (this.openedWorkflows.length === 0) {
|
||||
this.activeWorkflowIdx = -1;
|
||||
return;
|
||||
const workflow = workflowState.setActiveWorkflow(index);
|
||||
|
||||
if (workflow != null) {
|
||||
workflow.start("app", this.lCanvas);
|
||||
this.lCanvas.deserialize(workflow.canvases["app"].state)
|
||||
}
|
||||
|
||||
if (index < 0 || index >= this.openedWorkflows.length || this.activeWorkflowIdx === index)
|
||||
return;
|
||||
|
||||
if (this.activeWorkflow != null)
|
||||
this.activeWorkflow.stop("app")
|
||||
|
||||
const workflow = this.openedWorkflows[index]
|
||||
this.activeWorkflowIdx = index;
|
||||
|
||||
console.warn("START")
|
||||
workflow.start("app", this.lCanvas);
|
||||
this.lCanvas.deserialize(workflow.canvases["app"].state)
|
||||
selectionState.clear();
|
||||
}
|
||||
|
||||
async initDefaultGraph() {
|
||||
@@ -717,7 +542,7 @@ export default class ComfyApp {
|
||||
this.clean();
|
||||
|
||||
this.lCanvas.closeAllSubgraphs();
|
||||
this.closeAllWorkflows();
|
||||
workflowState.closeAllWorkflows();
|
||||
uiState.update(s => {
|
||||
s.uiUnlocked = true;
|
||||
s.uiEditMode = "widgets";
|
||||
@@ -726,16 +551,17 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
runDefaultQueueAction() {
|
||||
if (this.activeWorkflow == null)
|
||||
const workflow = workflowState.getActiveWorkflow();
|
||||
if (workflow == null)
|
||||
return;
|
||||
|
||||
for (const node of this.activeGraph.iterateNodesInOrderRecursive()) {
|
||||
for (const node of workflow.graph.iterateNodesInOrderRecursive()) {
|
||||
if ("onDefaultQueueAction" in node) {
|
||||
(node as ComfyGraphNode).onDefaultQueueAction()
|
||||
}
|
||||
}
|
||||
|
||||
if (get(layoutState).attrs.queuePromptButtonRunWorkflow) {
|
||||
if (get(workflow.layout).attrs.queuePromptButtonRunWorkflow) {
|
||||
// Hold control to queue at the front
|
||||
const num = this.ctrlDown ? -1 : 0;
|
||||
this.queuePrompt(num, 1);
|
||||
@@ -743,7 +569,8 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
querySave() {
|
||||
if (this.activeWorkflow == null) {
|
||||
const workflow = workflowState.getActiveWorkflow();
|
||||
if (workflow == null) {
|
||||
notify("No active workflow!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
@@ -765,7 +592,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
const indent = 2
|
||||
const json = JSON.stringify(this.serialize(), null, indent)
|
||||
const json = JSON.stringify(this.serialize(workflow), null, indent)
|
||||
|
||||
download(filename, json, "application/json")
|
||||
|
||||
@@ -781,12 +608,13 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||
if (this.activeWorkflow === null) {
|
||||
const activeWorkflow = workflowState.getActiveWorkflow();
|
||||
if (activeWorkflow == null) {
|
||||
notify("No workflow is opened!", { type: "error" })
|
||||
return;
|
||||
}
|
||||
|
||||
this.queueItems.push({ num, batchCount, workflow: this.activeWorkflow });
|
||||
this.queueItems.push({ num, batchCount, workflow: activeWorkflow });
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
if (this.processingQueue) {
|
||||
@@ -830,7 +658,7 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
const p = this.graphToPrompt(workflow, tag);
|
||||
const l = layoutState.serialize();
|
||||
const l = workflow.layout.serialize();
|
||||
console.debug(graphToGraphVis(workflow.graph))
|
||||
console.debug(promptToGraphVis(p))
|
||||
|
||||
@@ -942,7 +770,7 @@ export default class ComfyApp {
|
||||
* Refresh combo list on whole nodes
|
||||
*/
|
||||
async refreshComboInNodes(workflow?: ComfyWorkflow, flashUI: boolean = false) {
|
||||
workflow ||= this.activeWorkflow;
|
||||
workflow ||= workflowState.getActiveWorkflow();
|
||||
if (workflow == null) {
|
||||
notify("No active workflow!", { type: "error" })
|
||||
return
|
||||
|
||||
@@ -2,43 +2,47 @@
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { TextBox, Checkbox } from "@gradio/form";
|
||||
import { LGraphNode } from "@litegraph-ts/core"
|
||||
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
||||
import { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec, type WritableLayoutStateStore } from "$lib/stores/layoutStates"
|
||||
import uiState from "$lib/stores/uiState"
|
||||
import layoutStates from "$lib/stores/layoutStates"
|
||||
import selectionState from "$lib/stores/selectionState"
|
||||
import { get, type Writable, writable } from "svelte/store"
|
||||
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
||||
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||
import type { ComfyWorkflow } from "$lib/stores/workflowState";
|
||||
|
||||
export let workflow: ComfyWorkflow | null;
|
||||
|
||||
let layoutState: WritableLayoutStateStore | null = null
|
||||
|
||||
let target: IDragItem | null = null;
|
||||
let node: LGraphNode | null = null;
|
||||
let attrsChanged: Writable<number> | null = null;
|
||||
|
||||
let refreshPropsPanel: Writable<number> | null
|
||||
|
||||
$: refreshPropsPanel = $layoutState.refreshPropsPanel;
|
||||
|
||||
$: if ($selectionState.currentSelection.length > 0) {
|
||||
node = null;
|
||||
const targetId = $selectionState.currentSelection.slice(-1)[0]
|
||||
const entry = $layoutState.allItems[targetId]
|
||||
if (entry != null) {
|
||||
target = entry.dragItem
|
||||
attrsChanged = target.attrsChanged;
|
||||
if (target.type === "widget") {
|
||||
node = (target as WidgetLayout).node
|
||||
$: if (layoutState) {
|
||||
if ($selectionState.currentSelection.length > 0) {
|
||||
node = null;
|
||||
const targetId = $selectionState.currentSelection.slice(-1)[0]
|
||||
const entry = $layoutState.allItems[targetId]
|
||||
if (entry != null) {
|
||||
target = entry.dragItem
|
||||
if (target.type === "widget") {
|
||||
node = (target as WidgetLayout).node
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ($selectionState.currentSelectionNodes.length > 0) {
|
||||
target = null;
|
||||
node = $selectionState.currentSelectionNodes[0]
|
||||
attrsChanged = null;
|
||||
else if ($selectionState.currentSelectionNodes.length > 0) {
|
||||
target = null;
|
||||
node = $selectionState.currentSelectionNodes[0]
|
||||
}
|
||||
else {
|
||||
target = null
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
target = null
|
||||
target = null;
|
||||
node = null;
|
||||
attrsChanged = null;
|
||||
}
|
||||
|
||||
$: if (target) {
|
||||
@@ -55,7 +59,7 @@
|
||||
let value = spec.defaultValue;
|
||||
target.attrs[spec.name] = value;
|
||||
if (spec.refreshPanelOnChange)
|
||||
$refreshPropsPanel += 1;
|
||||
doRefreshPanel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +269,7 @@
|
||||
|
||||
function doRefreshPanel() {
|
||||
console.warn("[ComfyProperties] doRefreshPanel")
|
||||
$refreshPropsPanel += 1;
|
||||
$layoutStates.refreshPropsPanel += 1;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -282,172 +286,174 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="props-entries">
|
||||
{#key $refreshPropsPanel}
|
||||
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
||||
<div class="category-name">
|
||||
<span>
|
||||
<span class="title">{category.categoryName}</span>
|
||||
</span>
|
||||
</div>
|
||||
{#each category.specs as spec(spec.id)}
|
||||
{#if validWidgetAttribute(spec, target)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getAttribute(target, spec)}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
on:input={(e) => updateAttribute(spec, target, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
{#if workflow != null && layoutState != null}
|
||||
{#key $layoutStates.refreshPropsPanel}
|
||||
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
||||
<div class="category-name">
|
||||
<span>
|
||||
<span class="title">{category.categoryName}</span>
|
||||
</span>
|
||||
</div>
|
||||
{#each category.specs as spec(spec.id)}
|
||||
{#if validWidgetAttribute(spec, target)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getAttribute(target, spec)}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
on:input={(e) => updateAttribute(spec, target, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getAttribute(target, spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getAttribute(target, spec)}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getAttribute(target, spec)}
|
||||
values={spec.values}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if node}
|
||||
{#if validNodeProperty(spec, node)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getProperty(node, spec)}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:input={(e) => updateProperty(spec, e.detail)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getProperty(node, spec)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getProperty(node, spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getProperty(node, spec)}
|
||||
value={getAttribute(target, spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if validNodeVar(spec, node)}
|
||||
{:else if node}
|
||||
{#if validNodeProperty(spec, node)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getProperty(node, spec)}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:input={(e) => updateProperty(spec, e.detail)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getProperty(node, spec)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getProperty(node, spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getProperty(node, spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if validNodeVar(spec, node)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getVar(node, spec)}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
on:input={(e) => updateVar(spec, e.detail)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getVar(node, spec)}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !node && !target && validWorkflowAttribute(spec)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getVar(node, spec)}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
on:input={(e) => updateVar(spec, e.detail)}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getVar(node, spec)}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if !node && !target && validWorkflowAttribute(spec)}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={getWorkflowAttribute(spec)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
label={spec.name}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
max_lines={spec.multiline ? 5 : 1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getWorkflowAttribute(spec)}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
step={spec.step || 1}
|
||||
min={spec.min || -1024}
|
||||
max={spec.max || 1024}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getWorkflowAttribute(spec)}
|
||||
values={spec.values}
|
||||
disabled={!$uiState.uiUnlocked || !spec.editable}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
{/each}
|
||||
{/each}
|
||||
{/key}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte'
|
||||
import { get } from "svelte/store";
|
||||
import { LGraphNode, LGraph } from "@litegraph-ts/core";
|
||||
import type { IWidget } from "@litegraph-ts/core";
|
||||
import ComfyApp from "./ComfyApp";
|
||||
import type { SerializedPanes } from "./ComfyApp"
|
||||
import WidgetContainer from "./WidgetContainer.svelte";
|
||||
import layoutState, { type ContainerLayout, type DragItem, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
|
||||
import Menu from './menu/Menu.svelte';
|
||||
import MenuOption from './menu/MenuOption.svelte';
|
||||
import MenuDivider from './menu/MenuDivider.svelte';
|
||||
import Icon from './menu/Icon.svelte'
|
||||
import type { ComfyWorkflow } from "$lib/stores/workflowState";
|
||||
|
||||
export let app: ComfyApp;
|
||||
export let workflow: ComfyWorkflow;
|
||||
|
||||
let layoutState: WritableLayoutStateStore | null;
|
||||
|
||||
$: layoutState = workflow?.layout;
|
||||
|
||||
let root: IDragItem | null;
|
||||
let dragConfigured: boolean = false;
|
||||
|
||||
/*
|
||||
* Serialize UI panel order so it can be restored when workflow is loaded
|
||||
*/
|
||||
export function serialize(): any {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export function restore(panels: SerializedPanes) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
function moveTo(delta: number | ((cur: number, total: number) => number)) {
|
||||
const dragItemID = $selectionState.currentSelection[0];
|
||||
@@ -149,9 +138,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="comfy-ui-panes" on:contextmenu={onRightClick}>
|
||||
<WidgetContainer bind:dragItem={root} classes={["root-container"]} />
|
||||
</div>
|
||||
{#if layoutState != null}
|
||||
<div id="comfy-workflow-view" on:contextmenu={onRightClick}>
|
||||
<WidgetContainer bind:dragItem={root} classes={["root-container"]} {layoutState} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showMenu}
|
||||
<Menu {...menuPos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||
@@ -188,7 +179,7 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
#comfy-ui-panes {
|
||||
#comfy-workflow-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
import { Button } from "@gradio/button";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyWorkflowView from "./ComfyWorkflowView.svelte";
|
||||
import { Checkbox, TextBox } from "@gradio/form"
|
||||
import ComfyQueue from "./ComfyQueue.svelte";
|
||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||
@@ -10,15 +10,17 @@
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import workflowState from "$lib/stores/workflowState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import type ComfyApp from './ComfyApp';
|
||||
import { onMount } from "svelte";
|
||||
import Spinner from './Spinner.svelte';
|
||||
import type { WritableLayoutStateStore } from '$lib/stores/layoutStates';
|
||||
|
||||
export let app: ComfyApp;
|
||||
export let uiTheme: string = "gradio-dark" // TODO config
|
||||
|
||||
let layoutState: WritableLayoutStateStore | null = null;
|
||||
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
let alreadySetup: Writable<boolean> = writable(false);
|
||||
@@ -27,6 +29,8 @@
|
||||
|
||||
let appSetupPromise: Promise<void> = null;
|
||||
|
||||
$: layoutState = $workflowState.activeWorkflow?.layout;
|
||||
|
||||
onMount(async () => {
|
||||
appSetupPromise = app.setup().then(() => {
|
||||
loading = false;
|
||||
@@ -158,13 +162,17 @@
|
||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||
<Pane bind:size={propsSidebarSize}>
|
||||
<div class="sidebar-wrapper pane-wrapper">
|
||||
<ComfyProperties />
|
||||
<ComfyProperties layoutState={$workflowState.activeWorkflow} />
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane>
|
||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||
<Pane>
|
||||
<ComfyUIPane {app} />
|
||||
{#if $workflowState.activeWorkflow != null}
|
||||
<ComfyWorkflowView {app} workflow={$workflowState.activeWorkflow} />
|
||||
{:else}
|
||||
<span>No workflow loaded</span>
|
||||
{/if}
|
||||
</Pane>
|
||||
<Pane bind:size={graphSize}>
|
||||
<ComfyGraphView {app} transitioning={graphTransitioning} />
|
||||
@@ -178,8 +186,8 @@
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
<div id="workflow-tabs">
|
||||
{#each app.openedWorkflows as workflow, index}
|
||||
<button class="workflow-tab" class:selected={index === app.activeWorkflowIdx}>
|
||||
{#each $workflowState.openedWorkflows as workflow, index}
|
||||
<button class="workflow-tab" class:selected={index === $workflowState.activeWorkflowIdx}>
|
||||
{workflow.title}
|
||||
</button>
|
||||
{/each}
|
||||
@@ -187,7 +195,7 @@
|
||||
<div id="bottombar">
|
||||
<div class="bottombar-content">
|
||||
<div class="left">
|
||||
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||
{#if layoutState != null && $layoutState.attrs.queuePromptButtonName != ""}
|
||||
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
||||
{$layoutState.attrs.queuePromptButtonName}
|
||||
</Button>
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
import TabsContainer from "./TabsContainer.svelte"
|
||||
|
||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
||||
import { type ContainerLayout } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let layoutState: WritableLayoutStateStore;
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
@@ -31,11 +32,11 @@
|
||||
{#key $attrsChanged}
|
||||
{#if edit || !isHidden(container)}
|
||||
{#if container.attrs.variant === "tabs"}
|
||||
<TabsContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
<TabsContainer {layoutState} {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{:else if container.attrs.variant === "accordion"}
|
||||
<AccordionContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
<AccordionContainer {layoutState} {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{:else}
|
||||
<BlockContainer {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
<BlockContainer {layoutState} {container} {zIndex} {classes} {showHandles} {edit} {dragDisabled} {isMobile} />
|
||||
{/if}
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
// notice - fade in works fine but don't add svelte's fade-out (known issue)
|
||||
import {cubicIn} from 'svelte/easing';
|
||||
import { flip } from 'svelte/animate';
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import type { Writable } from "svelte/store";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let layoutState: WritableLayoutStateStore;
|
||||
export let container: ContainerLayout | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
@@ -24,19 +25,17 @@
|
||||
export let dragDisabled: boolean = false;
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
// let attrsChanged: Writable<number> = writable(0);
|
||||
let children: IDragItem[] = [];
|
||||
const flipDurationMs = 100;
|
||||
|
||||
let selectedIndex: number = 0;
|
||||
|
||||
$: if (container) {
|
||||
children = $layoutState.allItems[container.id].children;
|
||||
attrsChanged = container.attrsChanged
|
||||
// attrsChanged = container.attrsChanged
|
||||
}
|
||||
else {
|
||||
children = [];
|
||||
attrsChanged = null
|
||||
// attrsChanged = writable(0)
|
||||
}
|
||||
|
||||
function handleConsider(evt: any) {
|
||||
@@ -66,6 +65,14 @@
|
||||
function handleSelect() {
|
||||
navigator.vibrate(20)
|
||||
}
|
||||
|
||||
function _startDrag(e: MouseEvent | TouchEvent) {
|
||||
startDrag(e, layoutState)
|
||||
}
|
||||
|
||||
function _stopDrag(e: MouseEvent | TouchEvent) {
|
||||
stopDrag(e, layoutState)
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if container}
|
||||
@@ -103,7 +110,7 @@
|
||||
<label for={String(item.id)}>
|
||||
<BlockTitle><strong>Tab {i+1}:</strong> {tabName}</BlockTitle>
|
||||
</label>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
<WidgetContainer {layoutState} dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
{#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]}
|
||||
<div in:fade={{duration:200, easing: cubicIn}} class='drag-item-shadow'/>
|
||||
{/if}
|
||||
@@ -112,18 +119,26 @@
|
||||
{/each}
|
||||
</div>
|
||||
{#if isHidden(container) && edit}
|
||||
<div class="handle handle-hidden" style="z-index: {zIndex+100}" class:hidden={!edit} />
|
||||
<div class="handle handle-hidden"
|
||||
style:z-index={zIndex+100}
|
||||
class:hidden={!edit} />
|
||||
{/if}
|
||||
{#if showHandles}
|
||||
<div class="handle handle-container" style="z-index: {zIndex+100}" data-drag-item-id={container.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||
<div class="handle handle-container"
|
||||
style:z-index={zIndex+100}
|
||||
data-drag-item-id={container.id}
|
||||
on:mousedown={_startDrag}
|
||||
on:touchstart={_startDrag}
|
||||
on:mouseup={_stopDrag}
|
||||
on:touchend={_stopDrag}/>
|
||||
{/if}
|
||||
</Block>
|
||||
{:else}
|
||||
<Tabs elem_classes={["gradio-tabs"]} on:select={handleSelect}>
|
||||
{#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)}
|
||||
{@const tabName = getTabName(container, i)}
|
||||
<TabItem name={tabName}>
|
||||
<WidgetContainer dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
<TabItem id={tabName} name={tabName}>
|
||||
<WidgetContainer {layoutState} dragItem={item} zIndex={zIndex+1} {isMobile} />
|
||||
</TabItem>
|
||||
{/each}
|
||||
</Tabs>
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
|
||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||
import { type ContainerLayout, type WidgetLayout, type IDragItem, type WritableLayoutStateStore } from "$lib/stores/layoutStates";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import { startDrag, stopDrag } from "$lib/utils"
|
||||
import Container from "./Container.svelte"
|
||||
import { type Writable } from "svelte/store"
|
||||
import { writable, type Writable } from "svelte/store"
|
||||
import type { ComfyWidgetNode } from "$lib/nodes/widgets";
|
||||
import { isHidden } from "$lib/widgets/utils";
|
||||
|
||||
export let layoutState: WritableLayoutStateStore;
|
||||
export let dragItem: IDragItem | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
export let isMobile: boolean = false;
|
||||
let container: ContainerLayout | null = null;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let attrsChanged: Writable<number> = writable(0);
|
||||
let propsChanged: Writable<number> = writable(0);
|
||||
let widget: WidgetLayout | null = null;
|
||||
let showHandles: boolean = false;
|
||||
|
||||
@@ -24,8 +25,8 @@
|
||||
dragItem = null;
|
||||
container = null;
|
||||
widget = null;
|
||||
attrsChanged = null;
|
||||
propsChanged = null;
|
||||
attrsChanged = writable(0);
|
||||
propsChanged = writable(0);
|
||||
}
|
||||
else if (dragItem.type === "container") {
|
||||
container = dragItem as ContainerLayout;
|
||||
@@ -40,7 +41,7 @@
|
||||
if (widget.node && "propsChanged" in widget.node)
|
||||
propsChanged = (widget.node as ComfyWidgetNode).propsChanged
|
||||
else
|
||||
propsChanged = null;
|
||||
propsChanged = writable(0);
|
||||
}
|
||||
|
||||
$: showHandles = $uiState.uiUnlocked
|
||||
@@ -62,7 +63,7 @@
|
||||
|
||||
{#if container}
|
||||
{#key $attrsChanged}
|
||||
<Container {container} {classes} {zIndex} {showHandles} {isMobile} />
|
||||
<Container {layoutState} {container} {classes} {zIndex} {showHandles} {isMobile} />
|
||||
{/key}
|
||||
{:else if widget && widget.node}
|
||||
{@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets"}
|
||||
@@ -78,7 +79,7 @@
|
||||
class:is-executing={$queueState.runningNodeID && $queueState.runningNodeID == widget.node.id}
|
||||
class:hidden={hidden}
|
||||
>
|
||||
<svelte:component this={widget.node.svelteComponentType} {widget} {isMobile} />
|
||||
<svelte:component this={widget.node.svelteComponentType} {layoutState} {widget} {isMobile} />
|
||||
</div>
|
||||
{#if hidden && edit}
|
||||
<div class="handle handle-hidden" class:hidden={!edit} />
|
||||
|
||||
Reference in New Issue
Block a user