Start of properties panel
This commit is contained in:
@@ -16,14 +16,17 @@
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
export let showHandles: boolean = false;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
const flipDurationMs = 100;
|
||||
|
||||
$: if (container) {
|
||||
children = $layoutState.allItems[container.id].children;
|
||||
attrsChanged = container.attrsChanged
|
||||
}
|
||||
else {
|
||||
children = null;
|
||||
attrsChanged = null
|
||||
}
|
||||
|
||||
function handleConsider(evt: any) {
|
||||
@@ -38,6 +41,7 @@
|
||||
</script>
|
||||
|
||||
{#if container && children}
|
||||
{#key $attrsChanged}
|
||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')}"
|
||||
class:selected={$uiState.uiEditMode !== "disabled" && $layoutState.currentSelection.includes(container.id)}
|
||||
class:root-container={zIndex === 0}
|
||||
@@ -46,13 +50,7 @@
|
||||
<Block>
|
||||
{#if container.attrs.showTitle}
|
||||
<label for={String(container.id)} class={$uiState.uiEditMode === "widgets" ? "edit-title-label" : ""}>
|
||||
<BlockTitle>
|
||||
{#if $uiState.uiEditMode === "widgets"}
|
||||
<input class="edit-title" bind:value={container.attrs.title} type="text" minlength="1" />
|
||||
{:else}
|
||||
{container.attrs.title}
|
||||
{/if}
|
||||
</BlockTitle>
|
||||
<BlockTitle>{container.attrs.title}</BlockTitle>
|
||||
</label>
|
||||
{/if}
|
||||
<div class="v-pane"
|
||||
@@ -84,6 +82,7 @@
|
||||
{/if}
|
||||
</Block>
|
||||
</div>
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import { LGraph } from "@litegraph-ts/core";
|
||||
import LightboxModal from "./LightboxModal.svelte";
|
||||
import ComfyQueue from "./ComfyQueue.svelte";
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
|
||||
export let app: ComfyApp = undefined;
|
||||
@@ -23,6 +24,7 @@
|
||||
let queue: ComfyQueue = undefined;
|
||||
let mainElem: HTMLDivElement;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
let props: ComfyProperties = undefined;
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
let hasShownUIHelpToast: boolean = false;
|
||||
@@ -64,15 +66,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
let sidebarSize = 20;
|
||||
let propsSidebarSize = 15;
|
||||
|
||||
function toggleSidebar() {
|
||||
if (sidebarSize == 0) {
|
||||
sidebarSize = 20;
|
||||
function toggleProps() {
|
||||
if (propsSidebarSize == 0) {
|
||||
propsSidebarSize = 15;
|
||||
app.resizeCanvas();
|
||||
}
|
||||
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="container" bind:this={containerElem}>
|
||||
<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>
|
||||
@@ -165,7 +184,7 @@
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</Pane>
|
||||
<Pane bind:size={sidebarSize}>
|
||||
<Pane bind:size={queueSidebarSize}>
|
||||
<div class="sidebar-wrapper pane-wrapper">
|
||||
<ComfyQueue bind:this={queue} />
|
||||
</div>
|
||||
@@ -179,8 +198,11 @@
|
||||
<Button variant="secondary" on:click={toggleGraph}>
|
||||
Toggle Graph
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleSidebar}>
|
||||
Toggle Sidebar
|
||||
<Button variant="secondary" on:click={toggleProps}>
|
||||
Toggle Props
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleQueue}>
|
||||
Toggle Queue
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doSave}>
|
||||
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 { startDrag, stopDrag } from "$lib/utils"
|
||||
import BlockContainer from "./BlockContainer.svelte"
|
||||
import { type Writable } from "svelte/store"
|
||||
|
||||
export let dragItem: IDragItem | null = null;
|
||||
export let zIndex: number = 0;
|
||||
export let classes: string[] = [];
|
||||
let container: ContainerLayout | null = null;
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let widget: WidgetLayout | null = null;
|
||||
let showHandles: boolean = false;
|
||||
|
||||
@@ -17,13 +19,16 @@
|
||||
dragItem = null;
|
||||
container = null;
|
||||
widget = null;
|
||||
attrsChanged = null;
|
||||
}
|
||||
else if (dragItem.type === "container") {
|
||||
container = dragItem as ContainerLayout;
|
||||
attrsChanged = container.attrsChanged;
|
||||
widget = null;
|
||||
}
|
||||
else if (dragItem.type === "widget") {
|
||||
widget = dragItem as WidgetLayout;
|
||||
attrsChanged = widget.attrsChanged;
|
||||
container = null;
|
||||
}
|
||||
|
||||
@@ -35,13 +40,22 @@
|
||||
$: if ($queueState && widget && widget.node) {
|
||||
dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
|
||||
}
|
||||
|
||||
function getWidgetClass() {
|
||||
const title = widget.node.type.replace("/", "-").replace(".", "-")
|
||||
return `widget--${title}`
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
{#if container}
|
||||
{#key $attrsChanged}
|
||||
<BlockContainer {container} {classes} {zIndex} {showHandles} />
|
||||
{/key}
|
||||
{: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:is-executing={$queueState.runningNodeId && $queueState.runningNodeId == widget.node.id}
|
||||
>
|
||||
@@ -50,6 +64,7 @@
|
||||
{#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}/>
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -24,7 +24,8 @@ export type LayoutState = {
|
||||
export type AttributesSpec = {
|
||||
name: string,
|
||||
type: string,
|
||||
editable: boolean
|
||||
editable: boolean,
|
||||
values?: string[]
|
||||
}
|
||||
|
||||
export type AttributesCategorySpec = {
|
||||
@@ -50,8 +51,9 @@ const ALL_ATTRIBUTES: AttributesSpecList = [
|
||||
},
|
||||
{
|
||||
name: "direction",
|
||||
type: "string",
|
||||
type: "enum",
|
||||
editable: true,
|
||||
values: ["horizontal", "vertical"]
|
||||
},
|
||||
{
|
||||
name: "classes",
|
||||
@@ -74,7 +76,8 @@ export interface IDragItem {
|
||||
type: string,
|
||||
id: DragItemID,
|
||||
isNodeExecuting?: boolean,
|
||||
attrs: Attributes
|
||||
attrs: Attributes,
|
||||
attrsChanged: Writable<boolean>
|
||||
}
|
||||
|
||||
export interface ContainerLayout extends IDragItem {
|
||||
@@ -141,6 +144,7 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial<Attributes>
|
||||
const dragItem: ContainerLayout = {
|
||||
type: "container",
|
||||
id: `${state.currentId++}`,
|
||||
attrsChanged: writable(false),
|
||||
attrs: {
|
||||
title: "Container",
|
||||
showTitle: true,
|
||||
@@ -166,6 +170,7 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia
|
||||
type: "widget",
|
||||
id: `${state.currentId++}`,
|
||||
node: node,
|
||||
attrsChanged: writable(false),
|
||||
attrs: {
|
||||
title: widgetName,
|
||||
showTitle: true,
|
||||
@@ -436,7 +441,8 @@ function deserialize(data: SerializedLayoutState, graph: LGraph) {
|
||||
const dragItem: IDragItem = {
|
||||
type: entry.dragItem.type,
|
||||
id: entry.dragItem.id,
|
||||
attrs: entry.dragItem.attrs
|
||||
attrs: entry.dragItem.attrs,
|
||||
attrsChanged: writable(false)
|
||||
};
|
||||
|
||||
const dragEntry: DragItemEntry = {
|
||||
|
||||
@@ -67,7 +67,9 @@
|
||||
{#key $propsChanged}
|
||||
{#if node !== null && nodeValue !== null}
|
||||
<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
|
||||
bind:value={option}
|
||||
bind:items={node.properties.values}
|
||||
|
||||
@@ -1 +1 @@
|
||||
@import "gradio"
|
||||
@import "gradio";
|
||||
|
||||
Reference in New Issue
Block a user