From b295218afe31a4581d8953e9bd8bf88df82fc418 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 6 May 2023 12:56:54 -0500 Subject: [PATCH] Tabs container --- package.json | 1 + pnpm-lock.yaml | 3 + src/lib/components/BlockContainer.svelte | 111 ++-- src/lib/components/ComfyProperties.svelte | 742 ++++++++++++---------- src/lib/components/Container.svelte | 41 ++ src/lib/components/TabsContainer.svelte | 213 +++++++ src/lib/components/WidgetContainer.svelte | 4 +- src/lib/stores/layoutState.ts | 85 ++- src/lib/stores/uiState.ts | 2 +- vite.config.ts | 6 +- 10 files changed, 772 insertions(+), 436 deletions(-) create mode 100644 src/lib/components/Container.svelte create mode 100644 src/lib/components/TabsContainer.svelte diff --git a/package.json b/package.json index 21713bd..a6e5b0d 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@gradio/form": "workspace:*", "@gradio/gallery": "workspace:*", "@gradio/icons": "workspace:*", + "@gradio/tabs": "workspace:*", "@gradio/theme": "workspace:*", "@gradio/upload": "workspace:*", "@gradio/utils": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cdbb2a..2edccfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,9 @@ importers: '@gradio/icons': specifier: workspace:* version: link:gradio/js/icons + '@gradio/tabs': + specifier: workspace:* + version: link:gradio/js/tabs '@gradio/theme': specifier: workspace:* version: link:gradio/js/theme diff --git a/src/lib/components/BlockContainer.svelte b/src/lib/components/BlockContainer.svelte index 19aafdf..f3c0a6d 100644 --- a/src/lib/components/BlockContainer.svelte +++ b/src/lib/components/BlockContainer.svelte @@ -11,11 +11,15 @@ import { flip } from 'svelte/animate'; import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState"; import { startDrag, stopDrag } from "$lib/utils" + import type { Writable } from "svelte/store"; export let container: ContainerLayout | null = null; export let zIndex: number = 0; export let classes: string[] = []; export let showHandles: boolean = false; + export let edit: boolean = false; + export let dragDisabled: boolean = false; + let attrsChanged: Writable | null = null; let children: IDragItem[] | null = null; const flipDurationMs = 100; @@ -41,57 +45,54 @@ {#if container && children} - {@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1} - {#key $attrsChanged} -
- - {#if container.attrs.title !== ""} - - {/if} -
+ + {#if container.attrs.title && container.attrs.title !== ""} + + {/if} +
+ {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} + {@const hidden = item?.attrs?.hidden} +
- {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item(item.id)} - {@const hidden = item?.attrs?.hidden} -
- - {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} -
- {/if} -
- {/each} -
- {#if container.attrs.hidden && edit} -
- {/if} - {#if showHandles} -
- {/if} - -
- {/key} + + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} +
+ {/if} +
+ {/each} +
+ {#if container.attrs.hidden && edit} +
+ {/if} + {#if showHandles} +
+ {/if} + +
{/if} + + +
+
+
+ + {target?.attrs?.title || node?.title || "Workflow"} + {#if targetType !== ""} + ({targetType}) + {/if} + + +
+
+
+ {#key $refreshPanel} + {#each ALL_ATTRIBUTES as category(category.categoryName)} +
+ + {category.categoryName} + +
+ {#each category.specs as spec(spec.id)} + {#if spec.location === "widget" && validWidgetAttribute(spec, target)} +
+ {#if spec.type === "string"} + updateAttribute(spec, target, e.detail)} + on:input={(e) => updateAttribute(spec, target, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateAttribute(spec, target, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateAttribute(spec, target, e.detail)} + /> + {:else if spec.type === "enum"} + updateAttribute(spec, target, e.detail)} + /> + {/if} +
+ {:else if node} + {#if spec.location === "nodeProps" && validNodeProperty(spec, node)} +
+ {#if spec.type === "string"} + updateProperty(spec, e.detail)} + on:input={(e) => updateProperty(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "number"} + updateProperty(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateProperty(spec, e.detail)} + /> + {/if} +
+ {:else if spec.location === "nodeVars" && spec.name in node} +
+ {#if spec.type === "string"} + updateVar(spec, e.detail)} + on:input={(e) => updateVar(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateVar(spec, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateVar(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateVar(spec, e.detail)} + /> + {/if} +
+ {/if} + {:else if spec.location === "workflow" && spec.name in $layoutState.attrs} +
+ {#if spec.type === "string"} + updateWorkflowAttribute(spec, e.detail)} + on:input={(e) => updateWorkflowAttribute(spec, e.detail)} + label={spec.name} + disabled={!$uiState.uiUnlocked || !spec.editable} + max_lines={1} + /> + {:else if spec.type === "boolean"} + updateWorkflowAttribute(spec, e.detail)} + disabled={!$uiState.uiUnlocked || !spec.editable} + label={spec.name} + /> + {:else if spec.type === "number"} + updateWorkflowAttribute(spec, e.detail)} + /> + {:else if spec.type === "enum"} + updateWorkflowAttribute(spec, e.detail)} + /> + {/if} +
+ {/if} + {/each} + {/each} + {/key} +
+
+ + diff --git a/src/lib/components/Container.svelte b/src/lib/components/Container.svelte new file mode 100644 index 0000000..bb756af --- /dev/null +++ b/src/lib/components/Container.svelte @@ -0,0 +1,41 @@ + + +{#if container} + {@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1} + {@const dragDisabled = zIndex === 0 || $layoutState.currentSelection.length > 2 || !$uiState.uiUnlocked} + {#key $attrsChanged} + {#if container.attrs.variant === "tabs"} + + {:else} + + {/if} + {/key} +{/if} diff --git a/src/lib/components/TabsContainer.svelte b/src/lib/components/TabsContainer.svelte new file mode 100644 index 0000000..b8344c5 --- /dev/null +++ b/src/lib/components/TabsContainer.svelte @@ -0,0 +1,213 @@ + + +{#if container && children} +
+ {#if edit} + + {#if container.attrs.title && container.attrs.title !== ""} + {container.attrs.title} + {/if} +
+ {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)} + {@const hidden = item?.attrs?.hidden} + {@const tabName = getTabName(container, i)} +
+ + + + {#if item[SHADOW_ITEM_MARKER_PROPERTY_NAME]} +
+ {/if} + +
+ {/each} +
+ {#if container.attrs.hidden && edit} +
+ {/if} + {#if showHandles} +
+ {/if} + + {:else} + + {#each children.filter(item => item.id !== SHADOW_PLACEHOLDER_ITEM_ID) as item, i(item.id)} + {@const tabName = getTabName(container, i)} + console.log("tab " + i)}> + + + {/each} + + {/if} +
+{/if} + + diff --git a/src/lib/components/WidgetContainer.svelte b/src/lib/components/WidgetContainer.svelte index 8b3478e..f0e2e34 100644 --- a/src/lib/components/WidgetContainer.svelte +++ b/src/lib/components/WidgetContainer.svelte @@ -4,7 +4,7 @@ import layoutState, { type ContainerLayout, type WidgetLayout, type IDragItem } from "$lib/stores/layoutState"; import { startDrag, stopDrag } from "$lib/utils" - import BlockContainer from "./BlockContainer.svelte" + import Container from "./Container.svelte" import { type Writable } from "svelte/store" import type { ComfyWidgetNode } from "$lib/nodes"; @@ -59,7 +59,7 @@ {#if container} {#key $attrsChanged} - + {/key} {:else if widget && widget.node} {@const edit = $uiState.uiUnlocked && $uiState.uiEditMode === "widgets" && zIndex > 1} diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 58660f1..d987715 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -35,7 +35,12 @@ export type Attributes = { blockVariant?: "block" | "hidden", hidden?: boolean, disabled?: boolean, - flexGrow?: number + flexGrow?: number, + + /** Display variant for widgets/containers (e.g. number widget can act as slider/knob/dial) */ + variant?: string, + + tabNames?: string[] } export type AttributesSpec = { @@ -44,8 +49,8 @@ export type AttributesSpec = { type: string, location: "widget" | "nodeProps" | "nodeVars" | "workflow" editable: boolean, + defaultValue: any, - defaultValue?: any, values?: string[], hidden?: boolean, validNodeTypes?: string[], @@ -53,6 +58,7 @@ export type AttributesSpec = { canShow?: (arg: IDragItem | LGraphNode) => boolean, serialize?: (arg: any) => string, deserialize?: (arg: string) => any, + refreshPanelOnChange?: boolean } export type AttributesCategorySpec = { @@ -62,6 +68,13 @@ export type AttributesCategorySpec = { export type AttributesSpecList = AttributesCategorySpec[] +const serializeStringArray = (arg: string[]) => arg.join(",") +const deserializeStringArray = (arg: string) => { + if (arg === "") + return [] + return arg.split(",").map(s => s.trim()) +} + const ALL_ATTRIBUTES: AttributesSpecList = [ { categoryName: "appearance", @@ -70,18 +83,21 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ name: "title", type: "string", location: "widget", + defaultValue: "", editable: true, }, { name: "hidden", type: "boolean", location: "widget", + defaultValue: false, editable: true }, { name: "disabled", type: "boolean", location: "widget", + defaultValue: false, editable: true }, { @@ -107,15 +123,6 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ defaultValue: "", editable: true, }, - { - name: "blockVariant", - type: "enum", - location: "widget", - editable: true, - values: ["block", "hidden"], - defaultValue: "block", - canShow: (di: IDragItem) => di.type === "container" - }, // Container variants { @@ -125,6 +132,28 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ editable: true, values: ["block", "accordion", "tabs"], defaultValue: "block", + canShow: (di: IDragItem) => di.type === "container", + refreshPanelOnChange: true + }, + + { + name: "tabNames", + type: "string", + location: "widget", + editable: true, + defaultValue: ["Tab 1", "Tab 2", "Tab 3"], + canShow: (di: IDragItem) => di.type === "container" && di.attrs.variant === "tabs", + serialize: serializeStringArray, + deserialize: deserializeStringArray + }, + + { + name: "blockVariant", + type: "enum", + location: "widget", + editable: true, + values: ["block", "hidden"], + defaultValue: "block", canShow: (di: IDragItem) => di.type === "container" }, ] @@ -138,12 +167,9 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ type: "string", location: "nodeVars", editable: true, - serialize: (arg: string[]) => arg.join(","), - deserialize: (arg: string) => { - if (arg === "") - return [] - return arg.split(",").map(s => s.trim()) - } + defaultValue: [], + serialize: serializeStringArray, + deserialize: deserializeStringArray }, // Range @@ -152,6 +178,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ type: "number", location: "nodeProps", editable: true, + defaultValue: 0, validNodeTypes: ["ui/slider"], }, { @@ -159,6 +186,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ type: "number", location: "nodeProps", editable: true, + defaultValue: 10, validNodeTypes: ["ui/slider"], }, { @@ -166,6 +194,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ type: "number", location: "nodeProps", editable: true, + defaultValue: 1, validNodeTypes: ["ui/slider"], }, @@ -221,8 +250,8 @@ export interface WidgetLayout extends IDragItem { type DragItemID = string; type LayoutStateOps = { - addContainer: (parent: ContainerLayout | null, attrs: Partial, index: number) => ContainerLayout, - addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial, index: number) => WidgetLayout, + addContainer: (parent: ContainerLayout | null, attrs: Partial, index?: number) => ContainerLayout, + addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial, index?: number) => WidgetLayout, findDefaultContainerForInsertion: () => ContainerLayout | null, updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[], nodeAdded: (node: LGraphNode) => void, @@ -272,7 +301,7 @@ function findDefaultContainerForInsertion(): ContainerLayout | null { return null } -function addContainer(parent: ContainerLayout | null, attrs: Partial = {}, index: number = -1): ContainerLayout { +function addContainer(parent: ContainerLayout | null, attrs: Partial = {}, index?: number): ContainerLayout { const state = get(store); const dragItem: ContainerLayout = { type: "container", @@ -291,14 +320,14 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial const entry: DragItemEntry = { dragItem, children: [], parent: null }; state.allItems[dragItem.id] = entry; if (parent) { - moveItem(dragItem, parent) + moveItem(dragItem, parent, index) } console.debug("[layoutState] addContainer", state) store.set(state) return dragItem; } -function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial = {}, index: number = -1): WidgetLayout { +function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial = {}, index?: number): WidgetLayout { const state = get(store); const widgetName = "Widget" const dragItem: WidgetLayout = { @@ -320,7 +349,7 @@ function addWidget(parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partia state.allItems[dragItem.id] = entry; state.allItemsByNode[node.id] = entry; console.debug("[layoutState] addWidget", state) - moveItem(dragItem, parent) + moveItem(dragItem, parent, index) return dragItem; } @@ -398,7 +427,7 @@ function nodeRemoved(node: LGraphNode) { store.set(state) } -function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) { +function moveItem(target: IDragItem, to: ContainerLayout, index?: number) { const state = get(store) const entry = state.allItems[target.id] if (entry.parent && entry.parent.id === to.id) @@ -406,9 +435,9 @@ function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) { if (entry.parent) { const parentEntry = state.allItems[entry.parent.id]; - const index = parentEntry.children.findIndex(c => c.id === target.id) - if (index !== -1) { - parentEntry.children.splice(index, 1) + const parentIndex = parentEntry.children.findIndex(c => c.id === target.id) + if (parentIndex !== -1) { + parentEntry.children.splice(parentIndex, 1) } else { console.error(parentEntry) @@ -418,7 +447,7 @@ function moveItem(target: IDragItem, to: ContainerLayout, index: number = -1) { } const toEntry = state.allItems[to.id]; - if (index !== -1) + if (index != null && index >= 0) toEntry.children.splice(index, 0, target) else toEntry.children.push(target) diff --git a/src/lib/stores/uiState.ts b/src/lib/stores/uiState.ts index a7c3cc6..76b15fa 100644 --- a/src/lib/stores/uiState.ts +++ b/src/lib/stores/uiState.ts @@ -20,7 +20,7 @@ const store: WritableUIStateStore = writable( graphLocked: false, nodesLocked: false, autoAddUI: true, - uiUnlocked: false, + uiUnlocked: true, uiEditMode: "widgets" }) diff --git a/vite.config.ts b/vite.config.ts index c640040..4ed4baf 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,7 +8,11 @@ import { viteStaticCopy } from 'vite-plugin-static-copy' export default defineConfig({ clearScreen: false, plugins: [ - FullReload(["src/**/*.{js,ts,scss,svelte}"]), + FullReload([ + // "src/**/*.{js,ts,scss,svelte}" + "src/**/*.{scss}", + "src/**/ComfyApp.{ts,svelte}" + ]), svelte(), , viteStaticCopy({ targets: [