Drag and drop
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||||
|
import { Button } from "@gradio/button";
|
||||||
import { Backpack, Gear } from 'radix-icons-svelte';
|
import { Backpack, Gear } from 'radix-icons-svelte';
|
||||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||||
import ComfyApp from "./ComfyApp";
|
import ComfyApp from "./ComfyApp";
|
||||||
@@ -14,6 +15,24 @@
|
|||||||
app.resizeCanvas();
|
app.resizeCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function queuePrompt() {
|
||||||
|
const state = uiPane.getState();
|
||||||
|
console.log("Queuing!", state);
|
||||||
|
app.queuePrompt(0, 1, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphSize = null;
|
||||||
|
|
||||||
|
function toggleGraph() {
|
||||||
|
if (graphSize == 0) {
|
||||||
|
graphSize = 100;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
app = new ComfyApp();
|
app = new ComfyApp();
|
||||||
|
|
||||||
@@ -53,13 +72,21 @@
|
|||||||
<Pane minSize={15}>
|
<Pane minSize={15}>
|
||||||
<ComfyUIPane bind:this={uiPane} {app} />
|
<ComfyUIPane bind:this={uiPane} {app} />
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane minSize={10}>
|
<Pane snapSize={10} bind:size={graphSize}>
|
||||||
<canvas id="graph-canvas" />
|
<canvas id="graph-canvas" />
|
||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="bottombar">
|
||||||
|
<Button variant="primary" on:click={queuePrompt}>
|
||||||
|
Run
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" on:click={toggleGraph}>
|
||||||
|
Show/Hide Graph
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
#container {
|
#container {
|
||||||
@@ -84,6 +111,10 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bottombar {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.dropzone {
|
.dropzone {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: none;
|
display: none;
|
||||||
|
|||||||
119
src/lib/components/ComfyPane.svelte
Normal file
119
src/lib/components/ComfyPane.svelte
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button } from "@gradio/button";
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
import { Dropdown, Range, TextBox } from "@gradio/form";
|
||||||
|
|
||||||
|
import { dndzone } from 'svelte-dnd-action';
|
||||||
|
import { flip } from 'svelte/animate';
|
||||||
|
|
||||||
|
export let state: Record<number, any[]> = {};
|
||||||
|
export let items: Record<number, { node: LGraphNode, widget: IWidget }[]> = {};
|
||||||
|
|
||||||
|
export let dragItems = [];
|
||||||
|
let dragDisabled = true;
|
||||||
|
const flipDurationMs = 200;
|
||||||
|
|
||||||
|
const handleConsider = evt => {
|
||||||
|
dragItems = evt.detail.items;
|
||||||
|
// console.log(dragItems);
|
||||||
|
};
|
||||||
|
const handleFinalize = evt => {
|
||||||
|
dragItems = evt.detail.items;
|
||||||
|
// Ensure dragging is stopped on drag finish
|
||||||
|
dragDisabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startDrag = () => {
|
||||||
|
dragDisabled = false;
|
||||||
|
};
|
||||||
|
const stopDrag = () => {
|
||||||
|
dragDisabled = true;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="v-pane"
|
||||||
|
use:dndzone="{{ items: dragItems, dragDisabled, flipDurationMs }}"
|
||||||
|
on:consider="{handleConsider}"
|
||||||
|
on:finalize="{handleFinalize}"
|
||||||
|
>
|
||||||
|
{#each dragItems as dragItem(dragItem.id)}
|
||||||
|
{@const node = dragItem.node}
|
||||||
|
{@const id = node.id}
|
||||||
|
<Block>
|
||||||
|
<div class="handle" on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
|
<label for={id}>
|
||||||
|
<BlockTitle>{node.title}</BlockTitle>
|
||||||
|
</label>
|
||||||
|
{#each items[id] as item, i}
|
||||||
|
{#if item.widget.type == "combo"}
|
||||||
|
<div class="wrapper">
|
||||||
|
<Dropdown
|
||||||
|
bind:value={state[id][i]}
|
||||||
|
choices={item.widget.options.values}
|
||||||
|
multiselect={false}
|
||||||
|
max_choices={1},
|
||||||
|
label={item.widget.name}
|
||||||
|
show_label={true}
|
||||||
|
disabled={item.widget.options.values.length === 0}
|
||||||
|
on:change
|
||||||
|
on:select
|
||||||
|
on:blur
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if item.widget.type == "number"}
|
||||||
|
<div class="wrapper">
|
||||||
|
<Range
|
||||||
|
bind:value={state[id][i]}
|
||||||
|
minimum={item.widget.options.min}
|
||||||
|
maximum={item.widget.options.max}
|
||||||
|
step={item.widget.options.step}
|
||||||
|
label={item.widget.name}
|
||||||
|
show_label={true}
|
||||||
|
on:change
|
||||||
|
on:release
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if item.widget.type == "text"}
|
||||||
|
<div class="wrapper">
|
||||||
|
<TextBox
|
||||||
|
bind:value={state[id][i]}
|
||||||
|
label={item.widget.name}
|
||||||
|
lines={item.widget.options.multiline ? 5 : 1}
|
||||||
|
show_label={true}
|
||||||
|
on:change
|
||||||
|
on:submit
|
||||||
|
on:blur
|
||||||
|
on:select
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</Block>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.v-pane {
|
||||||
|
border: 1px solid grey;
|
||||||
|
float: left;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
cursor: grab;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1em;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
padding: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Button } from "@gradio/button";
|
|
||||||
import { Block, BlockTitle } from "@gradio/atoms";
|
|
||||||
import { Dropdown, Range, TextBox } from "@gradio/form";
|
|
||||||
import { LGraphNode, LGraph } from "litegraph.js";
|
import { LGraphNode, LGraph } from "litegraph.js";
|
||||||
import type { IWidget } from "litegraph.js";
|
import type { IWidget } from "litegraph.js";
|
||||||
import ComfyApp from "./ComfyApp";
|
import ComfyApp from "./ComfyApp";
|
||||||
|
import ComfyPane from "./ComfyPane.svelte";
|
||||||
|
|
||||||
export let app: ComfyApp;
|
export let app: ComfyApp;
|
||||||
|
|
||||||
|
let dragItems = [];
|
||||||
|
|
||||||
export function clear() {
|
export function clear() {
|
||||||
nodes = {};
|
nodes = {};
|
||||||
items = {};
|
items = {};
|
||||||
state = {};
|
state = {};
|
||||||
|
dragItems = []
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addNodeUI(node: LGraphNode) {
|
export function addNodeUI(node: LGraphNode) {
|
||||||
if (node.widgets) {
|
if (node.widgets) {
|
||||||
for (const [i, widget] of node.widgets.entries()) {
|
for (const [i, widget] of node.widgets.entries()) {
|
||||||
|
if (!nodes[node.id]) {
|
||||||
|
dragItems.push({ id: node.id, node: node })
|
||||||
|
}
|
||||||
nodes[node.id] = node;
|
nodes[node.id] = node;
|
||||||
|
|
||||||
node.onPropertyChanged = (k, v) => {
|
node.onPropertyChanged = (k, v) => {
|
||||||
@@ -45,6 +49,9 @@
|
|||||||
delete state[node.id]
|
delete state[node.id]
|
||||||
delete items[node.id]
|
delete items[node.id]
|
||||||
|
|
||||||
|
dragItems = Array.from(dragItems.filter(e => e.id != node.id));
|
||||||
|
console.log("REM", node.id, dragItems)
|
||||||
|
|
||||||
nodes = nodes;
|
nodes = nodes;
|
||||||
items = items;
|
items = items;
|
||||||
state = state;
|
state = state;
|
||||||
@@ -63,13 +70,8 @@
|
|||||||
state = state;
|
state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getState() {
|
export function getState() {
|
||||||
|
return state;
|
||||||
}
|
|
||||||
|
|
||||||
function queuePrompt() {
|
|
||||||
console.log("Queuing!", state);
|
|
||||||
app.queuePrompt(0, 1, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodes: Record<number, LGraphNode> = {};
|
let nodes: Record<number, LGraphNode> = {};
|
||||||
@@ -77,70 +79,10 @@
|
|||||||
let state: Record<number, any[]> = {};
|
let state: Record<number, any[]> = {};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="comfy-ui-panes">
|
<div id="comfy-ui-panes" >
|
||||||
<div class="v-pane">
|
<ComfyPane {dragItems} {items} {state} />
|
||||||
{#each Object.keys(items) as id}
|
<ComfyPane {items} {state} />
|
||||||
{@const node = nodes[id]}
|
<ComfyPane {items} {state} />
|
||||||
<Block>
|
|
||||||
<label for={id}>
|
|
||||||
<BlockTitle>{node.title}</BlockTitle>
|
|
||||||
</label>
|
|
||||||
{#each items[id] as item, i}
|
|
||||||
{#if item.widget.type == "combo"}
|
|
||||||
<div class="wrapper">
|
|
||||||
<Dropdown
|
|
||||||
bind:value={state[id][i]}
|
|
||||||
choices={item.widget.options.values}
|
|
||||||
multiselect={false}
|
|
||||||
max_choices={1},
|
|
||||||
label={item.widget.name}
|
|
||||||
show_label={true}
|
|
||||||
disabled={item.widget.options.values.length === 0}
|
|
||||||
on:change
|
|
||||||
on:select
|
|
||||||
on:blur
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else if item.widget.type == "number"}
|
|
||||||
<div class="wrapper">
|
|
||||||
<Range
|
|
||||||
bind:value={state[id][i]}
|
|
||||||
minimum={item.widget.options.min}
|
|
||||||
maximum={item.widget.options.max}
|
|
||||||
step={item.widget.options.step}
|
|
||||||
label={item.widget.name}
|
|
||||||
show_label={true}
|
|
||||||
on:change
|
|
||||||
on:release
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else if item.widget.type == "text"}
|
|
||||||
<div class="wrapper">
|
|
||||||
<TextBox
|
|
||||||
bind:value={state[id][i]}
|
|
||||||
label={item.widget.name}
|
|
||||||
lines={item.widget.options.multiline ? 5 : 1}
|
|
||||||
show_label={true}
|
|
||||||
on:change
|
|
||||||
on:submit
|
|
||||||
on:blur
|
|
||||||
on:select
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</Block>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
<div class="v-pane">
|
|
||||||
</div>
|
|
||||||
<div class="v-pane">
|
|
||||||
<div class="wrapper">
|
|
||||||
<Button variant="primary" on:click={queuePrompt}>
|
|
||||||
Run
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -158,6 +100,15 @@
|
|||||||
width: 33%;
|
width: 33%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handle {
|
||||||
|
cursor: grab;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 1em;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import FullReload from 'vite-plugin-full-reload'
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
sveltekit()
|
sveltekit()
|
||||||
// FullReload(["src/**/*.{js,ts,svelte}"])
|
FullReload(["src/**/*.{js,ts,svelte}"])
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user