Actual loading screen
This commit is contained in:
@@ -1,46 +1,22 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ListIcon as List, ImageIcon as Image, SettingsIcon as Settings } from "svelte-feather-icons";
|
import { ListIcon as List, ImageIcon as Image, SettingsIcon as Settings } from "svelte-feather-icons";
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { get, writable, type Writable } from "svelte/store";
|
|
||||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import { BlockTitle } from "@gradio/atoms";
|
|
||||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
|
||||||
import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp";
|
import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp";
|
||||||
import { Checkbox, TextBox } from "@gradio/form"
|
|
||||||
import uiState from "$lib/stores/uiState";
|
import uiState from "$lib/stores/uiState";
|
||||||
import layoutState from "$lib/stores/layoutState";
|
import layoutState from "$lib/stores/layoutState";
|
||||||
import selectionState from "$lib/stores/selectionState";
|
|
||||||
import { ImageViewer } from "$lib/ImageViewer";
|
|
||||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||||
|
|
||||||
import { LGraph } from "@litegraph-ts/core";
|
|
||||||
import LightboxModal from "./LightboxModal.svelte";
|
import LightboxModal from "./LightboxModal.svelte";
|
||||||
import ComfyQueue from "./ComfyQueue.svelte";
|
|
||||||
import ComfyProperties from "./ComfyProperties.svelte";
|
|
||||||
import Sidebar from "./Sidebar.svelte";
|
import Sidebar from "./Sidebar.svelte";
|
||||||
import SidebarItem from "./SidebarItem.svelte";
|
import SidebarItem from "./SidebarItem.svelte";
|
||||||
import queueState from "$lib/stores/queueState";
|
// import Modal from "./Modal.svelte";
|
||||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
// import A1111PromptDisplay from "./A1111PromptDisplay.svelte";
|
||||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
import notify from "$lib/notify";
|
||||||
import { download, jsonToJsObject } from "$lib/utils";
|
import ComfyWorkflowsView from "./ComfyWorkflowsView.svelte";
|
||||||
import notify from "$lib/notify";
|
|
||||||
import Modal from "./Modal.svelte";
|
|
||||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
|
||||||
import A1111PromptDisplay from "./A1111PromptDisplay.svelte";
|
|
||||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
|
||||||
import { TabItem, Tabs } from "@gradio/tabs";
|
|
||||||
|
|
||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
let alreadySetup: Writable<boolean> = writable(false);
|
|
||||||
let a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
|
||||||
let mainElem: HTMLDivElement;
|
|
||||||
let props: ComfyProperties = undefined;
|
|
||||||
let containerElem: HTMLDivElement;
|
|
||||||
let resizeTimeout: NodeJS.Timeout | null;
|
|
||||||
let hasShownUIHelpToast: boolean = false;
|
let hasShownUIHelpToast: boolean = false;
|
||||||
let uiTheme: string = "gradio-dark";
|
let uiTheme: string = "gradio-dark";
|
||||||
let fileInput: HTMLInputElement = undefined;
|
|
||||||
|
|
||||||
let debugLayout: boolean = false;
|
let debugLayout: boolean = false;
|
||||||
|
|
||||||
@@ -51,110 +27,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if(app) {
|
|
||||||
alreadySetup = app.alreadySetup;
|
|
||||||
a1111Prompt = app.a1111Prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshView(event?: Event) {
|
|
||||||
clearTimeout(resizeTimeout);
|
|
||||||
resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (app?.lCanvas) {
|
|
||||||
app.lCanvas.allow_dragnodes = $uiState.uiUnlocked;
|
|
||||||
app.lCanvas.allow_interaction = $uiState.uiUnlocked;
|
|
||||||
|
|
||||||
if (!$uiState.uiUnlocked) {
|
|
||||||
app.lCanvas.deselectAllNodes();
|
|
||||||
$selectionState.currentSelectionNodes = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if ($uiState.uiEditMode)
|
|
||||||
$selectionState.currentSelection = []
|
|
||||||
|
|
||||||
let graphSize = 0;
|
|
||||||
let graphTransitioning = false;
|
|
||||||
|
|
||||||
function queuePrompt() {
|
|
||||||
app.runDefaultQueueAction()
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleGraph() {
|
|
||||||
if (graphSize == 0) {
|
|
||||||
graphSize = 50;
|
|
||||||
app.resizeCanvas();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
graphSize = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let propsSidebarSize = 0;
|
|
||||||
|
|
||||||
function toggleProps() {
|
|
||||||
if (propsSidebarSize == 0) {
|
|
||||||
propsSidebarSize = 15;
|
|
||||||
app.resizeCanvas();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
propsSidebarSize = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let queueSidebarSize = 20;
|
|
||||||
|
|
||||||
function toggleQueue() {
|
|
||||||
if (queueSidebarSize == 0) {
|
|
||||||
queueSidebarSize = 20;
|
|
||||||
app.resizeCanvas();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
queueSidebarSize = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSave(): void {
|
|
||||||
if (!app?.lGraph)
|
|
||||||
return;
|
|
||||||
|
|
||||||
app.querySave()
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLoad(): void {
|
|
||||||
if (!app?.lGraph || !fileInput)
|
|
||||||
return;
|
|
||||||
|
|
||||||
fileInput.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadWorkflow(): void {
|
|
||||||
app.handleFile(fileInput.files[0]);
|
|
||||||
fileInput.files = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSaveLocal(): void {
|
|
||||||
if (!app?.lGraph)
|
|
||||||
return;
|
|
||||||
|
|
||||||
app.saveStateToLocalStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doLoadDefault() {
|
|
||||||
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
|
||||||
if (confirmed) {
|
|
||||||
await app.initDefaultGraph();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doClear(): void {
|
|
||||||
var confirmed = confirm("Are you sure you want to clear the current workflow?");
|
|
||||||
if (confirmed) {
|
|
||||||
app.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if ($uiState.uiUnlocked && !hasShownUIHelpToast) {
|
$: if ($uiState.uiUnlocked && !hasShownUIHelpToast) {
|
||||||
hasShownUIHelpToast = true;
|
hasShownUIHelpToast = true;
|
||||||
notify("Right-click to open context menu.")
|
notify("Right-click to open context menu.")
|
||||||
@@ -166,34 +38,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (containerElem) {
|
|
||||||
const canvas = containerElem.querySelector<HTMLDivElement>("#graph-canvas")
|
|
||||||
if (canvas) {
|
|
||||||
const paneNode = canvas.closest(".splitpanes__pane")
|
|
||||||
if (paneNode) {
|
|
||||||
(paneNode as HTMLElement).ontransitionstart = () => {
|
|
||||||
graphTransitioning = true
|
|
||||||
}
|
|
||||||
(paneNode as HTMLElement).ontransitionend = () => {
|
|
||||||
graphTransitioning = false
|
|
||||||
app.resizeCanvas()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
await app.setup();
|
|
||||||
|
|
||||||
// await import('../../scss/ux.scss');
|
|
||||||
|
|
||||||
refreshView();
|
|
||||||
})
|
|
||||||
|
|
||||||
async function doRefreshCombos() {
|
|
||||||
await app.refreshComboInNodes(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (uiTheme === "gradio-dark") {
|
$: if (uiTheme === "gradio-dark") {
|
||||||
document.getElementById("app-root").classList.add("dark")
|
document.getElementById("app-root").classList.add("dark")
|
||||||
}
|
}
|
||||||
@@ -201,11 +45,11 @@
|
|||||||
document.getElementById("app-root").classList.remove("dark")
|
document.getElementById("app-root").classList.remove("dark")
|
||||||
}
|
}
|
||||||
|
|
||||||
let showModal: boolean = false;
|
// let showModal: boolean = false;
|
||||||
|
//
|
||||||
$: showModal = $a1111Prompt != null
|
// $: showModal = $a1111Prompt != null
|
||||||
|
//
|
||||||
let selectedTab
|
// let selectedTab
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -214,312 +58,39 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Modal bind:showModal on:close={() => ($a1111Prompt = null)}>
|
<!--
|
||||||
<div slot="header" class="prompt-modal-header">
|
<Modal bind:showModal on:close={() => ($a1111Prompt = null)}>
|
||||||
<h1 style="padding-bottom: 1rem;">A1111 Prompt Details</h1>
|
<div slot="header" class="prompt-modal-header">
|
||||||
</div>
|
<h1 style="padding-bottom: 1rem;">A1111 Prompt Details</h1>
|
||||||
<A1111PromptDisplay prompt={$a1111Prompt} />
|
</div>
|
||||||
<div slot="buttons" let:closeDialog>
|
<A1111PromptDisplay prompt={$a1111Prompt} />
|
||||||
<Button variant="secondary" on:click={closeDialog}>
|
<div slot="buttons" let:closeDialog>
|
||||||
Close
|
<Button variant="secondary" on:click={closeDialog}>
|
||||||
</Button>
|
Close
|
||||||
</div>
|
</Button>
|
||||||
</Modal>
|
</div>
|
||||||
|
</Modal>
|
||||||
|
-->
|
||||||
|
|
||||||
<div id="main" class:dark={uiTheme === "gradio-dark"}>
|
<div id="main" class:dark={uiTheme === "gradio-dark"}>
|
||||||
<div id="container" bind:this={containerElem}>
|
<div id="container">
|
||||||
<Sidebar selected="generate">
|
<Sidebar selected="generate">
|
||||||
<SidebarItem id="generate" name="Generate" icon={Image}>
|
<SidebarItem id="generate" name="Generate" icon={Image}>
|
||||||
<div id="comfy-content">
|
<ComfyWorkflowsView {app} {uiTheme} />
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
|
||||||
<Pane bind:size={propsSidebarSize}>
|
|
||||||
<div class="sidebar-wrapper pane-wrapper">
|
|
||||||
<ComfyProperties bind:this={props} />
|
|
||||||
</div>
|
|
||||||
</Pane>
|
|
||||||
<Pane>
|
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
|
||||||
<Pane>
|
|
||||||
<ComfyUIPane {app} />
|
|
||||||
</Pane>
|
|
||||||
<Pane bind:size={graphSize}>
|
|
||||||
<ComfyGraphView {app} transitioning={graphTransitioning} />
|
|
||||||
</Pane>
|
|
||||||
</Splitpanes>
|
|
||||||
</Pane>
|
|
||||||
<Pane bind:size={queueSidebarSize}>
|
|
||||||
<div class="sidebar-wrapper pane-wrapper">
|
|
||||||
<ComfyQueue {app} />
|
|
||||||
</div>
|
|
||||||
</Pane>
|
|
||||||
</Splitpanes>
|
|
||||||
<div id="workflow-tabs">
|
|
||||||
<div class="workflow-tab selected">
|
|
||||||
txt2img
|
|
||||||
<!-- <Image /> -->
|
|
||||||
</div>
|
|
||||||
<div class="workflow-tab">
|
|
||||||
img2img
|
|
||||||
<!-- <Image /> -->
|
|
||||||
</div>
|
|
||||||
<div class="workflow-tab">
|
|
||||||
asdflkj
|
|
||||||
<!-- <Image /> -->
|
|
||||||
</div>
|
|
||||||
<div class="workflow-tab">
|
|
||||||
asdkajw
|
|
||||||
<!-- <Image /> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="bottombar">
|
|
||||||
<div class="bottombar-content">
|
|
||||||
<div class="left">
|
|
||||||
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
|
||||||
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
|
||||||
{$layoutState.attrs.queuePromptButtonName}
|
|
||||||
</Button>
|
|
||||||
{/if}
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleGraph}>
|
|
||||||
Toggle Graph
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleProps}>
|
|
||||||
Toggle Props
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleQueue}>
|
|
||||||
Toggle Queue
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSave}>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSaveLocal}>
|
|
||||||
Save Local
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoad}>
|
|
||||||
Load
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doClear}>
|
|
||||||
Clear
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoadDefault}>
|
|
||||||
Load Default
|
|
||||||
</Button>
|
|
||||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doRefreshCombos}>
|
|
||||||
🔄
|
|
||||||
</Button>
|
|
||||||
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
|
||||||
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/> -->
|
|
||||||
<span style="display: inline-flex !important">
|
|
||||||
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
|
||||||
</span>
|
|
||||||
<span class="label" for="ui-edit-mode">
|
|
||||||
<BlockTitle>UI Edit mode</BlockTitle>
|
|
||||||
<select id="ui-edit-mode" name="ui-edit-mode" bind:value={$uiState.uiEditMode}>
|
|
||||||
<option value="widgets">Widgets</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
<span class="label" for="ui-theme">
|
|
||||||
<BlockTitle>Theme</BlockTitle>
|
|
||||||
<select id="ui-theme" name="ui-theme" bind:value={uiTheme}>
|
|
||||||
<option value="gradio-dark">Gradio Dark</option>
|
|
||||||
<option value="gradio-light">Gradio Light</option>
|
|
||||||
<option value="anapnoe">Anapnoe</option>
|
|
||||||
</select>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<ComfyUnlockUIButton bind:toggled={$uiState.uiUnlocked} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
<SidebarItem id="settings" name="Settings" icon={Settings}>
|
<SidebarItem id="settings" name="Settings" icon={Settings}>
|
||||||
</SidebarItem>
|
</SidebarItem>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
</div>
|
</div>
|
||||||
<LightboxModal />
|
<LightboxModal />
|
||||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
|
||||||
</div>
|
</div>
|
||||||
<SvelteToast options={toastOptions} />
|
<SvelteToast options={toastOptions} />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
$top-bar-height: 3.5rem;
|
|
||||||
$workflow-tabs-height: 2.5rem;
|
|
||||||
$bottom-bar-height: 5rem;
|
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
display: relative;
|
display: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#comfy-content {
|
|
||||||
grid-area: content;
|
|
||||||
height: calc(100vh - $bottom-bar-height - $workflow-tabs-height);
|
|
||||||
}
|
|
||||||
|
|
||||||
#workflow-tabs {
|
|
||||||
display: flex;
|
|
||||||
padding-right: 1em;
|
|
||||||
margin-top: auto;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
#topbar {
|
|
||||||
background: var(--neutral-900);
|
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
|
||||||
float: left;
|
|
||||||
position: sticky;
|
|
||||||
top: 0px;
|
|
||||||
padding: 0;
|
|
||||||
width: 4rem;
|
|
||||||
|
|
||||||
.topbar-button {
|
|
||||||
background: var(--neutral-800);
|
|
||||||
color: var(--neutral-500);
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-right: 3px solid transparent;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-right: 1px solid var(--neutral-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--neutral-700);
|
|
||||||
color: var(--neutral-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: var(--neutral-700);
|
|
||||||
color: var(--neutral-300);
|
|
||||||
border-right-color: var(--primary-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
#workflow-tabs {
|
|
||||||
background: var(--neutral-800);
|
|
||||||
.workflow-tab {
|
|
||||||
background: var(--neutral-800);
|
|
||||||
color: var(--neutral-500);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-top: 3px solid transparent;
|
|
||||||
border-left: 1px solid var(--neutral-600);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-right: 1px solid var(--neutral-600);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--neutral-700);
|
|
||||||
color: var(--neutral-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background: var(--neutral-700);
|
|
||||||
color: var(--neutral-300);
|
|
||||||
border-top-color: var(--primary-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#bottombar {
|
|
||||||
background: var(--neutral-900);
|
|
||||||
border-left: 2px solid var(--neutral-700);
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
gap: var(--layout-gap);
|
|
||||||
overflow-x: hidden;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
height: $bottom-bar-height;
|
|
||||||
|
|
||||||
> .bottombar-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin: auto 0;
|
|
||||||
padding-left: 1rem;
|
|
||||||
|
|
||||||
> .left {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .right {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: 1rem;
|
|
||||||
padding-left: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(html, body) {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0px;
|
|
||||||
font-family: Arial;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.splitpanes.comfy>.splitpanes__splitter) {
|
|
||||||
background: var(--comfy-splitpanes-background-fill);
|
|
||||||
|
|
||||||
&:hover:not([disabled]) {
|
|
||||||
background: var(--comfy-splitpanes-background-fill-hover);
|
|
||||||
}
|
|
||||||
&:active:not([disabled]) {
|
|
||||||
background: var(--comfy-splitpanes-background-fill-active);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$splitter-size: 1rem;
|
|
||||||
|
|
||||||
:global(.splitpanes.comfy.splitpanes--horizontal>.splitpanes__splitter) {
|
|
||||||
min-height: $splitter-size;
|
|
||||||
cursor: row-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.splitpanes.comfy.splitpanes--vertical>.splitpanes__splitter) {
|
|
||||||
min-width: $splitter-size;
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.splitpanes.comfy) {
|
|
||||||
max-height: calc(100vh - $bottom-bar-height);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
label.label > :global(span) {
|
|
||||||
top: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.left {
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#comfy-file-input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -128,6 +128,17 @@ type BackendComboNode = {
|
|||||||
backendNode: ComfyBackendNode
|
backendNode: ComfyBackendNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CanvasState = {
|
||||||
|
canvasEl: HTMLCanvasElement,
|
||||||
|
canvasCtx: CanvasRenderingContext2D,
|
||||||
|
canvas: ComfyGraphCanvas,
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowState = {
|
||||||
|
title: string,
|
||||||
|
graph: ComfyGraph,
|
||||||
|
}
|
||||||
|
|
||||||
export default class ComfyApp {
|
export default class ComfyApp {
|
||||||
api: ComfyAPI;
|
api: ComfyAPI;
|
||||||
rootEl: HTMLDivElement | null = null;
|
rootEl: HTMLDivElement | null = null;
|
||||||
|
|||||||
484
src/lib/components/ComfyWorkflowsView.svelte
Normal file
484
src/lib/components/ComfyWorkflowsView.svelte
Normal file
@@ -0,0 +1,484 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||||
|
import { Button } from "@gradio/button";
|
||||||
|
import { BlockTitle } from "@gradio/atoms";
|
||||||
|
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||||
|
import { Checkbox, TextBox } from "@gradio/form"
|
||||||
|
import ComfyQueue from "./ComfyQueue.svelte";
|
||||||
|
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||||
|
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||||
|
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 selectionState from "$lib/stores/selectionState";
|
||||||
|
import type ComfyApp from './ComfyApp';
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import Spinner from './Spinner.svelte';
|
||||||
|
|
||||||
|
export let app: ComfyApp;
|
||||||
|
export let uiTheme: string = "gradio-dark" // TODO config
|
||||||
|
|
||||||
|
let containerElem: HTMLDivElement;
|
||||||
|
let resizeTimeout: NodeJS.Timeout | null;
|
||||||
|
let alreadySetup: Writable<boolean> = writable(false);
|
||||||
|
let fileInput: HTMLInputElement = undefined;
|
||||||
|
let loading = true;
|
||||||
|
|
||||||
|
let appSetupPromise: Promise<void> = null;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
appSetupPromise = app.setup().then(() => {
|
||||||
|
loading = false;
|
||||||
|
refreshView();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
refreshView();
|
||||||
|
})
|
||||||
|
|
||||||
|
$: if (app) {
|
||||||
|
alreadySetup = app.alreadySetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doRefreshCombos() {
|
||||||
|
await app.refreshComboInNodes(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshView(event?: Event) {
|
||||||
|
clearTimeout(resizeTimeout);
|
||||||
|
resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (app?.lCanvas) {
|
||||||
|
app.lCanvas.allow_dragnodes = $uiState.uiUnlocked;
|
||||||
|
app.lCanvas.allow_interaction = $uiState.uiUnlocked;
|
||||||
|
|
||||||
|
if (!$uiState.uiUnlocked) {
|
||||||
|
app.lCanvas.deselectAllNodes();
|
||||||
|
$selectionState.currentSelectionNodes = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if ($uiState.uiEditMode)
|
||||||
|
$selectionState.currentSelection = []
|
||||||
|
|
||||||
|
let graphSize = 0;
|
||||||
|
let graphTransitioning = false;
|
||||||
|
|
||||||
|
$: if (containerElem) {
|
||||||
|
const canvas = containerElem.querySelector<HTMLDivElement>("#graph-canvas")
|
||||||
|
if (canvas) {
|
||||||
|
const paneNode = canvas.closest(".splitpanes__pane")
|
||||||
|
if (paneNode) {
|
||||||
|
(paneNode as HTMLElement).ontransitionstart = () => {
|
||||||
|
graphTransitioning = true
|
||||||
|
}
|
||||||
|
(paneNode as HTMLElement).ontransitionend = () => {
|
||||||
|
graphTransitioning = false
|
||||||
|
app.resizeCanvas()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queuePrompt() {
|
||||||
|
app.runDefaultQueueAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGraph() {
|
||||||
|
if (graphSize == 0) {
|
||||||
|
graphSize = 50;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let propsSidebarSize = 0;
|
||||||
|
|
||||||
|
function toggleProps() {
|
||||||
|
if (propsSidebarSize == 0) {
|
||||||
|
propsSidebarSize = 15;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
propsSidebarSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let queueSidebarSize = 20;
|
||||||
|
|
||||||
|
function toggleQueue() {
|
||||||
|
if (queueSidebarSize == 0) {
|
||||||
|
queueSidebarSize = 20;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queueSidebarSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSave(): void {
|
||||||
|
if (!app?.lGraph)
|
||||||
|
return;
|
||||||
|
|
||||||
|
app.querySave()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLoad(): void {
|
||||||
|
if (!app?.lGraph || !fileInput)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fileInput.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWorkflow(): void {
|
||||||
|
app.handleFile(fileInput.files[0]);
|
||||||
|
fileInput.files = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doSaveLocal(): void {
|
||||||
|
if (!app?.lGraph)
|
||||||
|
return;
|
||||||
|
|
||||||
|
app.saveStateToLocalStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doLoadDefault() {
|
||||||
|
var confirmed = confirm("Are you sure you want to clear the current workflow and load the default graph?");
|
||||||
|
if (confirmed) {
|
||||||
|
await app.initDefaultGraph();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doClear(): void {
|
||||||
|
var confirmed = confirm("Are you sure you want to clear the current workflow?");
|
||||||
|
if (confirmed) {
|
||||||
|
app.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="comfy-content" bind:this={containerElem} class:loading>
|
||||||
|
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||||
|
<Pane bind:size={propsSidebarSize}>
|
||||||
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
|
<ComfyProperties />
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
|
<Pane>
|
||||||
|
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||||
|
<Pane>
|
||||||
|
<ComfyUIPane {app} />
|
||||||
|
</Pane>
|
||||||
|
<Pane bind:size={graphSize}>
|
||||||
|
<ComfyGraphView {app} transitioning={graphTransitioning} />
|
||||||
|
</Pane>
|
||||||
|
</Splitpanes>
|
||||||
|
</Pane>
|
||||||
|
<Pane bind:size={queueSidebarSize}>
|
||||||
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
|
<ComfyQueue {app} />
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
|
</Splitpanes>
|
||||||
|
<div id="workflow-tabs">
|
||||||
|
<div class="workflow-tab selected">
|
||||||
|
txt2img
|
||||||
|
<!-- <Image /> -->
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tab">
|
||||||
|
img2img
|
||||||
|
<!-- <Image /> -->
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tab">
|
||||||
|
asdflkj
|
||||||
|
<!-- <Image /> -->
|
||||||
|
</div>
|
||||||
|
<div class="workflow-tab">
|
||||||
|
asdkajw
|
||||||
|
<!-- <Image /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="bottombar">
|
||||||
|
<div class="bottombar-content">
|
||||||
|
<div class="left">
|
||||||
|
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||||
|
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
||||||
|
{$layoutState.attrs.queuePromptButtonName}
|
||||||
|
</Button>
|
||||||
|
{/if}
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleGraph}>
|
||||||
|
Toggle Graph
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleProps}>
|
||||||
|
Toggle Props
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleQueue}>
|
||||||
|
Toggle Queue
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSaveLocal}>
|
||||||
|
Save Local
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoad}>
|
||||||
|
Load
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doClear}>
|
||||||
|
Clear
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoadDefault}>
|
||||||
|
Load Default
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" disabled={!$alreadySetup} on:click={doRefreshCombos}>
|
||||||
|
🔄
|
||||||
|
</Button>
|
||||||
|
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||||
|
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/> -->
|
||||||
|
<span style="display: inline-flex !important">
|
||||||
|
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
||||||
|
</span>
|
||||||
|
<span class="label" for="ui-edit-mode">
|
||||||
|
<BlockTitle>UI Edit mode</BlockTitle>
|
||||||
|
<select id="ui-edit-mode" name="ui-edit-mode" bind:value={$uiState.uiEditMode}>
|
||||||
|
<option value="widgets">Widgets</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
<span class="label" for="ui-theme">
|
||||||
|
<BlockTitle>Theme</BlockTitle>
|
||||||
|
<select id="ui-theme" name="ui-theme" bind:value={uiTheme}>
|
||||||
|
<option value="gradio-dark">Gradio Dark</option>
|
||||||
|
<option value="gradio-light">Gradio Light</option>
|
||||||
|
<option value="anapnoe">Anapnoe</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<ComfyUnlockUIButton bind:toggled={$uiState.uiUnlocked} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||||
|
|
||||||
|
{#if appSetupPromise}
|
||||||
|
{#await appSetupPromise}
|
||||||
|
<div class="comfy-app-loading">
|
||||||
|
<span>Loading...</span>
|
||||||
|
</div>
|
||||||
|
{:catch error}
|
||||||
|
<div class="comfy-loading-error">
|
||||||
|
<div>
|
||||||
|
Error loading app
|
||||||
|
</div>
|
||||||
|
<div>{error}</div>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$top-bar-height: 3.5rem;
|
||||||
|
$workflow-tabs-height: 2.5rem;
|
||||||
|
$bottom-bar-height: 5rem;
|
||||||
|
|
||||||
|
.comfy-app-loading, .comfy-loading-error {
|
||||||
|
font-size: 40px;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100000000;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comfy-app-loading > span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comfy-content {
|
||||||
|
grid-area: content;
|
||||||
|
height: calc(100vh - $bottom-bar-height - $workflow-tabs-height);
|
||||||
|
|
||||||
|
&.loading {
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#workflow-tabs {
|
||||||
|
display: flex;
|
||||||
|
padding-right: 1em;
|
||||||
|
margin-top: auto;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
#topbar {
|
||||||
|
background: var(--neutral-900);
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
float: left;
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
padding: 0;
|
||||||
|
width: 4rem;
|
||||||
|
|
||||||
|
.topbar-button {
|
||||||
|
background: var(--neutral-800);
|
||||||
|
color: var(--neutral-500);
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-right: 3px solid transparent;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: 1px solid var(--neutral-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--neutral-700);
|
||||||
|
color: var(--neutral-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--neutral-700);
|
||||||
|
color: var(--neutral-300);
|
||||||
|
border-right-color: var(--primary-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
#workflow-tabs {
|
||||||
|
background: var(--neutral-800);
|
||||||
|
.workflow-tab {
|
||||||
|
background: var(--neutral-800);
|
||||||
|
color: var(--neutral-500);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-top: 3px solid transparent;
|
||||||
|
border-left: 1px solid var(--neutral-600);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: 1px solid var(--neutral-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--neutral-700);
|
||||||
|
color: var(--neutral-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background: var(--neutral-700);
|
||||||
|
color: var(--neutral-300);
|
||||||
|
border-top-color: var(--primary-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottombar {
|
||||||
|
background: var(--neutral-900);
|
||||||
|
border-left: 2px solid var(--neutral-700);
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
gap: var(--layout-gap);
|
||||||
|
overflow-x: hidden;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
height: $bottom-bar-height;
|
||||||
|
|
||||||
|
> .bottombar-content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: auto 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
|
||||||
|
> .left {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .right {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 1rem;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html, body) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0px;
|
||||||
|
font-family: Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.splitpanes.comfy>.splitpanes__splitter) {
|
||||||
|
background: var(--comfy-splitpanes-background-fill);
|
||||||
|
|
||||||
|
&:hover:not([disabled]) {
|
||||||
|
background: var(--comfy-splitpanes-background-fill-hover);
|
||||||
|
}
|
||||||
|
&:active:not([disabled]) {
|
||||||
|
background: var(--comfy-splitpanes-background-fill-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$splitter-size: 1rem;
|
||||||
|
|
||||||
|
:global(.splitpanes.comfy.splitpanes--horizontal>.splitpanes__splitter) {
|
||||||
|
min-height: $splitter-size;
|
||||||
|
cursor: row-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.splitpanes.comfy.splitpanes--vertical>.splitpanes__splitter) {
|
||||||
|
min-width: $splitter-size;
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.splitpanes.comfy) {
|
||||||
|
max-height: calc(100vh - $bottom-bar-height);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
label.label > :global(span) {
|
||||||
|
top: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.left {
|
||||||
|
right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comfy-file-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -28,18 +28,21 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id={elem_id}
|
id={elem_id}
|
||||||
class="tabitem {elem_classes.join(' ')}"
|
class="sidebar-item {elem_classes.join(' ')}"
|
||||||
style:display={$selected_tab === id ? "block" : "none"}
|
style:display={$selected_tab === id ? "block" : "none"}
|
||||||
>
|
>
|
||||||
<Column>
|
<div style:height="100%">
|
||||||
<slot />
|
<slot />
|
||||||
</Column>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
div {
|
||||||
display: flex;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% - 4rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-item {
|
||||||
|
width: calc(100% - 4rem);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user