Files
ComfyBox/src/lib/components/ComfyApp.svelte
space-nuko 7880c68d7f Serialize node links instead of widget values
Syncing litegraph widget state is kinda annoying, and unnecessary since
everything will be moved to separate UI component nodes. Instead I
modified the input slot type to store the min/max/step to be copied into
the default UI node later. Now nothing uses litegraph's widgets anymore
2023-04-30 16:37:15 -07:00

284 lines
7.5 KiB
Svelte

<script lang="ts">
import { onMount } from "svelte";
import { get } from "svelte/store";
import { Pane, Splitpanes } from 'svelte-splitpanes';
import { Button } from "@gradio/button";
import ComfyUIPane from "./ComfyUIPane.svelte";
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
import { Checkbox } from "@gradio/form"
import nodeState from "$lib/stores/nodeState";
import uiState from "$lib/stores/uiState";
import layoutState from "$lib/stores/layoutState";
import { ImageViewer } from "$lib/ImageViewer";
import { download } from "$lib/utils"
import { LGraph, LGraphNode } from "@litegraph-ts/core";
import LightboxModal from "./LightboxModal.svelte";
import { Block } from "@gradio/atoms";
import ComfyQueue from "./ComfyQueue.svelte";
import type { ComfyAPIStatus } from "$lib/api";
import queueState from "$lib/stores/queueState";
let app: ComfyApp = undefined;
let imageViewer: ImageViewer;
let uiPane: ComfyUIPane = undefined;
let queue: ComfyQueue = undefined;
let mainElem: HTMLDivElement;
let containerElem: HTMLDivElement;
let resizeTimeout: typeof Timer = -1;
function refreshView(event?: Event) {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250);
}
function queuePrompt() {
console.log("Queuing!");
app.queuePrompt(0, 1);
}
$: if (app) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
$: if (app) app.lCanvas.allow_interaction = !$uiState.graphLocked;
$: if ($uiState.uiEditMode)
$layoutState.currentSelection = []
let graphSize = null;
function toggleGraph() {
if (graphSize == 0) {
graphSize = 100;
app.resizeCanvas();
}
else {
graphSize = 0;
}
}
let sidebarSize = 20;
function toggleSidebar() {
if (sidebarSize == 0) {
sidebarSize = 20;
app.resizeCanvas();
}
else {
sidebarSize = 0;
}
}
let graphResizeTimer: typeof Timer = -1;
function serializeAppState(): SerializedAppState {
const graph = app.lGraph;
const serializedGraph = graph.serialize()
const serializedPaneOrder = uiPane.serialize()
return {
createdBy: "ComfyBox",
version: 1,
workflow: serializedGraph,
panes: serializedPaneOrder
}
}
function doAutosave(graph: LGraph): void {
const savedWorkflow = serializeAppState();
localStorage.setItem("workflow", JSON.stringify(savedWorkflow))
}
function doRestore(workflow: SerializedAppState) {
uiPane.restore(workflow.panes);
}
function doSave(): void {
if (!app?.lGraph)
return;
const date = new Date();
const formattedDate = date.toISOString().replace(/:/g, '-').replace(/\.\d{3}/g, '').replace('T', '_').replace("Z", "");
download(`workflow-${formattedDate}.json`, JSON.stringify(serializeAppState()), "application/json")
}
onMount(async () => {
app = new ComfyApp();
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", layoutState.nodeAdded);
// app.eventBus.on("nodeRemoved", layoutState.nodeRemoved);
// app.eventBus.on("configured", layoutState.configureFinished);
// app.eventBus.on("cleared", layoutState.clear);
app.eventBus.on("autosave", doAutosave);
app.eventBus.on("restored", doRestore);
app.api.addEventListener("status", (ev: CustomEvent) => {
queueState.statusUpdated(ev.detail as ComfyAPIStatus);
});
await app.setup();
(window as any).app = app;
(window as any).appPane = uiPane;
refreshView();
imageViewer = new ImageViewer(app.rootEl);
let wrappers = containerElem.querySelectorAll<HTMLDivElement>(".pane-wrapper")
for (const wrapper of wrappers) {
const paneNode = wrapper.parentNode as HTMLElement; // get the node inside the <Pane/>
paneNode.ontransitionend = () => {
app.resizeCanvas()
}
}
})
</script>
<div id="main" bind:this={mainElem}>
<div id="dropzone" class="dropzone"></div>
<div id="container" bind:this={containerElem}>
<Splitpanes theme="comfy" on:resize={refreshView}>
<Pane>
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
<Pane>
<ComfyUIPane bind:this={uiPane} {app} />
</Pane>
<Pane bind:size={graphSize}>
<div class="canvas-wrapper pane-wrapper">
<canvas id="graph-canvas" />
</div>
</Pane>
</Splitpanes>
</Pane>
<Pane bind:size={sidebarSize}>
<div class="sidebar-wrapper pane-wrapper">
<ComfyQueue bind:this={queue} />
</div>
</Pane>
</Splitpanes>
</div>
<div id="bottombar">
<Button variant="primary" on:click={queuePrompt}>
Run
</Button>
<Button variant="secondary" on:click={toggleGraph}>
Toggle Graph
</Button>
<Button variant="secondary" on:click={toggleSidebar}>
Toggle Sidebar
</Button>
<Button variant="secondary" on:click={doSave}>
Save
</Button>
<Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/>
<label for="enable-ui-editing">Enable UI Editing</label>
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
<option value="disabled">Disabled</option>
<option value="widgets">Widgets</option>
</select>
</div>
<LightboxModal />
</div>
<style lang="scss">
#container {
height: calc(100vh - 60px);
max-width: 100vw;
display: grid;
width: 100%;
}
#comfy-content {
grid-area: content;
height: 100vh;
}
#comfy-ui {
}
#comfy-graph {
}
#graph-canvas {
}
#bottombar {
display: flex;
flex-wrap: wrap;
gap: var(--layout-gap);
margin: 10px;
}
.canvas-wrapper {
width: 100%;
height: 100%;
background-color: #333;
}
.sidebar-wrapper {
width: 100%;
height: 100%;
}
.dropzone {
box-sizing: border-box;
display: none;
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 99999;
background: #60a7dc80;
border: 4px dashed #60a7dc;
}
:global(html, body) {
width: 100%;
height: 100%;
margin: 0px;
font-family: Arial;
}
:global(.splitpanes.comfy>.splitpanes__splitter) {
background-color: var(--secondary-100);
&:hover:not([disabled]) {
background-color: var(--secondary-300);
}
&:active:not([disabled]) {
background-color: var(--secondary-400);
}
}
:global(.splitpanes.comfy.splitpanes--horizontal>.splitpanes__splitter) {
min-height: 20px;
cursor: row-resize;
}
:global(.splitpanes.comfy.splitpanes--vertical>.splitpanes__splitter) {
min-width: 20px;
cursor: col-resize;
}
:global(.splitpanes.comfy) {
max-height: calc(100vh - 60px);
max-width: 100vw;
}
:global(.splitpanes__pane) {
box-shadow: 0 0 3px rgba(0, 0, 0, .2) inset;
justify-content: center;
align-items: center;
display: flex;
position: relative;
}
</style>