Start of properties panel

This commit is contained in:
space-nuko
2023-05-05 00:49:34 -05:00
parent 85d676b0f9
commit 7f64b743a7
7 changed files with 278 additions and 68 deletions

View File

@@ -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,7 +41,8 @@
</script> </script>
{#if container && children} {#if container && children}
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}" {#key $attrsChanged}
<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}
class:is-executing={container.isNodeExecuting} class:is-executing={container.isNodeExecuting}
@@ -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"
@@ -83,7 +81,8 @@
<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} {/if}
</Block> </Block>
</div> </div>
{/key}
{/if} {/if}
<style lang="scss"> <style lang="scss">

View File

@@ -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

View 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>

View File

@@ -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">

View File

@@ -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 = {

View File

@@ -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}

View File

@@ -1 +1 @@
@import "gradio" @import "gradio";