Start of properties panel
This commit is contained in:
@@ -16,14 +16,17 @@
|
|||||||
export let zIndex: number = 0;
|
export let zIndex: number = 0;
|
||||||
export let classes: string[] = [];
|
export let classes: string[] = [];
|
||||||
export let showHandles: boolean = false;
|
export let showHandles: boolean = false;
|
||||||
|
let attrsChanged: Writable<boolean> | null = null;
|
||||||
let children: IDragItem[] | null = null;
|
let children: IDragItem[] | null = null;
|
||||||
const flipDurationMs = 100;
|
const flipDurationMs = 100;
|
||||||
|
|
||||||
$: if (container) {
|
$: if (container) {
|
||||||
children = $layoutState.allItems[container.id].children;
|
children = $layoutState.allItems[container.id].children;
|
||||||
|
attrsChanged = container.attrsChanged
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
children = null;
|
children = null;
|
||||||
|
attrsChanged = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleConsider(evt: any) {
|
function handleConsider(evt: any) {
|
||||||
@@ -38,6 +41,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if container && children}
|
{#if container && children}
|
||||||
|
{#key $attrsChanged}
|
||||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
||||||
class:root-container={zIndex === 0}
|
class:root-container={zIndex === 0}
|
||||||
@@ -46,13 +50,7 @@
|
|||||||
<Block>
|
<Block>
|
||||||
{#if container.attrs.showTitle}
|
{#if container.attrs.showTitle}
|
||||||
<label for={String(container.id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
<label for={String(container.id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
||||||
<BlockTitle>
|
<BlockTitle>{container.attrs.title}</BlockTitle>
|
||||||
{#if $uiState.uiEditMode === "widgets"}
|
|
||||||
<input class="edit-title" bind:value={container.attrs.title} type="text" minlength="1" />
|
|
||||||
{:else}
|
|
||||||
{container.attrs.title}
|
|
||||||
{/if}
|
|
||||||
</BlockTitle>
|
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="v-pane"
|
<div class="v-pane"
|
||||||
@@ -84,6 +82,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Block>
|
</Block>
|
||||||
</div>
|
</div>
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
import { LGraph } from "@litegraph-ts/core";
|
import { LGraph } from "@litegraph-ts/core";
|
||||||
import LightboxModal from "./LightboxModal.svelte";
|
import LightboxModal from "./LightboxModal.svelte";
|
||||||
import ComfyQueue from "./ComfyQueue.svelte";
|
import ComfyQueue from "./ComfyQueue.svelte";
|
||||||
|
import ComfyProperties from "./ComfyProperties.svelte";
|
||||||
import queueState from "$lib/stores/queueState";
|
import queueState from "$lib/stores/queueState";
|
||||||
|
|
||||||
export let app: ComfyApp = undefined;
|
export let app: ComfyApp = undefined;
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
let queue: ComfyQueue = undefined;
|
let queue: ComfyQueue = undefined;
|
||||||
let mainElem: HTMLDivElement;
|
let mainElem: HTMLDivElement;
|
||||||
let uiPane: ComfyUIPane = undefined;
|
let uiPane: ComfyUIPane = undefined;
|
||||||
|
let props: ComfyProperties = undefined;
|
||||||
let containerElem: HTMLDivElement;
|
let containerElem: HTMLDivElement;
|
||||||
let resizeTimeout: NodeJS.Timeout | null;
|
let resizeTimeout: NodeJS.Timeout | null;
|
||||||
let hasShownUIHelpToast: boolean = false;
|
let hasShownUIHelpToast: boolean = false;
|
||||||
@@ -64,15 +66,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sidebarSize = 20;
|
let propsSidebarSize = 15;
|
||||||
|
|
||||||
function toggleSidebar() {
|
function toggleProps() {
|
||||||
if (sidebarSize == 0) {
|
if (propsSidebarSize == 0) {
|
||||||
sidebarSize = 20;
|
propsSidebarSize = 15;
|
||||||
app.resizeCanvas();
|
app.resizeCanvas();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sidebarSize = 0;
|
propsSidebarSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let queueSidebarSize = 15;
|
||||||
|
|
||||||
|
function toggleQueue() {
|
||||||
|
if (queueSidebarSize == 0) {
|
||||||
|
queueSidebarSize = 15;
|
||||||
|
app.resizeCanvas();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queueSidebarSize = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +167,11 @@
|
|||||||
<div id="dropzone" class="dropzone"></div>
|
<div id="dropzone" class="dropzone"></div>
|
||||||
<div id="container" bind:this={containerElem}>
|
<div id="container" bind:this={containerElem}>
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||||
|
<Pane bind:size={propsSidebarSize}>
|
||||||
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
|
<ComfyProperties bind:this={props} />
|
||||||
|
</div>
|
||||||
|
</Pane>
|
||||||
<Pane>
|
<Pane>
|
||||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||||
<Pane>
|
<Pane>
|
||||||
@@ -165,7 +184,7 @@
|
|||||||
</Pane>
|
</Pane>
|
||||||
</Splitpanes>
|
</Splitpanes>
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane bind:size={sidebarSize}>
|
<Pane bind:size={queueSidebarSize}>
|
||||||
<div class="sidebar-wrapper pane-wrapper">
|
<div class="sidebar-wrapper pane-wrapper">
|
||||||
<ComfyQueue bind:this={queue} />
|
<ComfyQueue bind:this={queue} />
|
||||||
</div>
|
</div>
|
||||||
@@ -179,8 +198,11 @@
|
|||||||
<Button variant="secondary" on:click={toggleGraph}>
|
<Button variant="secondary" on:click={toggleGraph}>
|
||||||
Toggle Graph
|
Toggle Graph
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" on:click={toggleSidebar}>
|
<Button variant="secondary" on:click={toggleProps}>
|
||||||
Toggle Sidebar
|
Toggle Props
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" on:click={toggleQueue}>
|
||||||
|
Toggle Queue
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" on:click={doSave}>
|
<Button variant="secondary" on:click={doSave}>
|
||||||
Save
|
Save
|
||||||
|
|||||||
166
src/lib/components/ComfyProperties.svelte
Normal file
166
src/lib/components/ComfyProperties.svelte
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Block, BlockTitle } from "@gradio/atoms";
|
||||||
|
import { TextBox, Checkbox } from "@gradio/form";
|
||||||
|
import layoutState, { ALL_ATTRIBUTES } from "$lib/stores/layoutState"
|
||||||
|
import { get } from "svelte/store"
|
||||||
|
|
||||||
|
let target: IDragItem | null = null;
|
||||||
|
let node: LGraphNode | null = null;
|
||||||
|
|
||||||
|
$: if ($layoutState.currentSelection.length > 0) {
|
||||||
|
const targetId = $layoutState.currentSelection.slice(-1)
|
||||||
|
target = $layoutState.allItems[targetId].dragItem
|
||||||
|
if (target.type === "widget") {
|
||||||
|
node = target.node
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target = null
|
||||||
|
node = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _entries = [
|
||||||
|
{ name: "title" },
|
||||||
|
{ name: "showTitle" },
|
||||||
|
{ name: "direction" },
|
||||||
|
{ name: "classes" },
|
||||||
|
]
|
||||||
|
|
||||||
|
function getTargetType(): string {
|
||||||
|
if (node)
|
||||||
|
return "Node"
|
||||||
|
else if (target?.type === "container")
|
||||||
|
"Group"
|
||||||
|
return "???"
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAttribute(entry: any, value: any) {
|
||||||
|
if (target) {
|
||||||
|
const name = entry.name
|
||||||
|
console.warn("updateAttribute", name, value)
|
||||||
|
|
||||||
|
target.attrs[name] = value
|
||||||
|
target.attrsChanged.set(!get(target.attrsChanged))
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node.propsChanged.set(!get(node.propsChanged))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="props">
|
||||||
|
{#if target}
|
||||||
|
<div class="top">
|
||||||
|
<div class="target-name">
|
||||||
|
<span>
|
||||||
|
<span class="title">{target.attrs.title}</span>
|
||||||
|
<span class="type">({getTargetType()})</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="props-entries">
|
||||||
|
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
||||||
|
<div class="category-name">
|
||||||
|
<span>
|
||||||
|
<span class="title">{target.attrs.title}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{#each category.specs as spec(spec.name)}
|
||||||
|
{@const has = spec.name in target.attrs}
|
||||||
|
{#if has}
|
||||||
|
<div class="props-entry">
|
||||||
|
{#if spec.type === "string"}
|
||||||
|
<TextBox
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
max_lines={1}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "boolean"}
|
||||||
|
<Checkbox
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||||
|
label={spec.name}
|
||||||
|
/>
|
||||||
|
{:else if spec.type === "enum"}
|
||||||
|
<label class="select-wrapper">
|
||||||
|
<BlockTitle>{spec.name}</BlockTitle>
|
||||||
|
<div class="select">
|
||||||
|
<select
|
||||||
|
value={target.attrs[spec.name]}
|
||||||
|
on:change={(e) => updateAttribute(spec, e.currentTarget.options[e.currentTarget.selectedIndex].value)}>
|
||||||
|
{#each spec.values as value}
|
||||||
|
<option value={value}>
|
||||||
|
{value}
|
||||||
|
</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.props-entry {
|
||||||
|
padding: 0.5rem 0.5rem 0 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-name {
|
||||||
|
border-color: var(--neutral-400);
|
||||||
|
background: var(--neutral-300);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
border-color: var(--neutral-300);
|
||||||
|
background: var(--neutral-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.target-name, .category-name {
|
||||||
|
border-width: var(--block-border-width);
|
||||||
|
padding: 0.4rem 1.0rem;
|
||||||
|
|
||||||
|
.type {
|
||||||
|
color: var(--neutral-500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.select {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-title {
|
||||||
|
padding: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
/* width: 100%;
|
||||||
|
height: auto;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0.5em; */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -5,11 +5,13 @@
|
|||||||
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState";
|
||||||
import { startDrag, stopDrag } from "$lib/utils"
|
import { startDrag, stopDrag } from "$lib/utils"
|
||||||
import BlockContainer from "./BlockContainer.svelte"
|
import BlockContainer from "./BlockContainer.svelte"
|
||||||
|
import { type Writable } from "svelte/store"
|
||||||
|
|
||||||
export let dragItem: IDragItem | null = null;
|
export let dragItem: IDragItem | null = null;
|
||||||
export let zIndex: number = 0;
|
export let zIndex: number = 0;
|
||||||
export let classes: string[] = [];
|
export let classes: string[] = [];
|
||||||
let container: ContainerLayout | null = null;
|
let container: ContainerLayout | null = null;
|
||||||
|
let attrsChanged: Writable<boolean> | null = null;
|
||||||
let widget: WidgetLayout | null = null;
|
let widget: WidgetLayout | null = null;
|
||||||
let showHandles: boolean = false;
|
let showHandles: boolean = false;
|
||||||
|
|
||||||
@@ -17,13 +19,16 @@
|
|||||||
dragItem = null;
|
dragItem = null;
|
||||||
container = null;
|
container = null;
|
||||||
widget = null;
|
widget = null;
|
||||||
|
attrsChanged = null;
|
||||||
}
|
}
|
||||||
else if (dragItem.type === "container") {
|
else if (dragItem.type === "container") {
|
||||||
container = dragItem as ContainerLayout;
|
container = dragItem as ContainerLayout;
|
||||||
|
attrsChanged = container.attrsChanged;
|
||||||
widget = null;
|
widget = null;
|
||||||
}
|
}
|
||||||
else if (dragItem.type === "widget") {
|
else if (dragItem.type === "widget") {
|
||||||
widget = dragItem as WidgetLayout;
|
widget = dragItem as WidgetLayout;
|
||||||
|
attrsChanged = widget.attrsChanged;
|
||||||
container = null;
|
container = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +40,22 @@
|
|||||||
$: if ($queueState && widget && widget.node) {
|
$: if ($queueState && widget && widget.node) {
|
||||||
dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
|
dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWidgetClass() {
|
||||||
|
const title = widget.node.type.replace("/", "-").replace(".", "-")
|
||||||
|
return `widget--${title}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
{#if container}
|
{#if container}
|
||||||
|
{#key $attrsChanged}
|
||||||
<BlockContainer {container} {classes} {zIndex} {showHandles} />
|
<BlockContainer {container} {classes} {zIndex} {showHandles} />
|
||||||
|
{/key}
|
||||||
{:else if widget && widget.node}
|
{:else if widget && widget.node}
|
||||||
<div class="widget" class:widget-edit-outline={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
{#key $attrsChanged}
|
||||||
|
<div class="widget {widget.attrs.classes} {getWidgetClass()}"
|
||||||
|
class:widget-edit-outline={$uiState.uiEditMode === "widgets" && zIndex > 1}
|
||||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(widget.id)}
|
||||||
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
class:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
||||||
>
|
>
|
||||||
@@ -50,6 +64,7 @@
|
|||||||
{#if showHandles}
|
{#if showHandles}
|
||||||
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
<div class="handle handle-widget" style="z-index: {zIndex+100}" data-drag-item-id={widget.id} on:mousedown={startDrag} on:touchstart={startDrag} on:mouseup={stopDrag} on:touchend={stopDrag}/>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export type LayoutState = {
|
|||||||
export type AttributesSpec = {
|
export type AttributesSpec = {
|
||||||
name: string,
|
name: string,
|
||||||
type: string,
|
type: string,
|
||||||
editable: boolean
|
editable: boolean,
|
||||||
|
values?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttributesCategorySpec = {
|
export type AttributesCategorySpec = {
|
||||||
@@ -50,8 +51,9 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "direction",
|
name: "direction",
|
||||||
type: "string",
|
type: "enum",
|
||||||
editable: true,
|
editable: true,
|
||||||
|
values: ["horizontal", "vertical"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "classes",
|
name: "classes",
|
||||||
@@ -74,7 +76,8 @@ export interface IDragItem {
|
|||||||
type: string,
|
type: string,
|
||||||
id: DragItemID,
|
id: DragItemID,
|
||||||
isNodeExecuting?: boolean,
|
isNodeExecuting?: boolean,
|
||||||
attrs: Attributes
|
attrs: Attributes,
|
||||||
|
attrsChanged: Writable<boolean>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContainerLayout extends IDragItem {
|
export interface ContainerLayout extends IDragItem {
|
||||||
@@ -141,6 +144,7 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial<Attributes>
|
|||||||
const dragItem: ContainerLayout = {
|
const dragItem: ContainerLayout = {
|
||||||
type: "container",
|
type: "container",
|
||||||
id: `${state.currentId++}`,
|
id: `${state.currentId++}`,
|
||||||
|
attrsChanged: writable(false),
|
||||||
attrs: {
|
attrs: {
|
||||||
title: "Container",
|
title: "Container",
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
@@ -166,6 +170,7 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia
|
|||||||
type: "widget",
|
type: "widget",
|
||||||
id: `${state.currentId++}`,
|
id: `${state.currentId++}`,
|
||||||
node: node,
|
node: node,
|
||||||
|
attrsChanged: writable(false),
|
||||||
attrs: {
|
attrs: {
|
||||||
title: widgetName,
|
title: widgetName,
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
@@ -436,7 +441,8 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
|||||||
const dragItem: IDragItem = {
|
const dragItem: IDragItem = {
|
||||||
type: entry.dragItem.type,
|
type: entry.dragItem.type,
|
||||||
id: entry.dragItem.id,
|
id: entry.dragItem.id,
|
||||||
attrs: entry.dragItem.attrs
|
attrs: entry.dragItem.attrs,
|
||||||
|
attrsChanged: writable(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
const dragEntry: DragItemEntry = {
|
const dragEntry: DragItemEntry = {
|
||||||
|
|||||||
@@ -67,7 +67,9 @@
|
|||||||
{#key $propsChanged}
|
{#key $propsChanged}
|
||||||
{#if node !== null && nodeValue !== null}
|
{#if node !== null && nodeValue !== null}
|
||||||
<label>
|
<label>
|
||||||
<BlockTitle show_label={true}>{widget.attrs.title}</BlockTitle>
|
{#if widget.attrs.showTitle}
|
||||||
|
<BlockTitle show_label={widget.attrs.showTitle}>{widget.attrs.title}</BlockTitle>
|
||||||
|
{/if}
|
||||||
<Select
|
<Select
|
||||||
bind:value={option}
|
bind:value={option}
|
||||||
bind:items={node.properties.values}
|
bind:items={node.properties.values}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
@import "gradio"
|
@import "gradio";
|
||||||
|
|||||||
Reference in New Issue
Block a user