From daff339035653a67aa7de67f63fd4059c8d7445e Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Sat, 6 May 2023 14:15:46 -0500 Subject: [PATCH] Refactoring drag items --- src/lib/components/AccordionContainer.svelte | 2 +- src/lib/components/BlockContainer.svelte | 2 +- src/lib/components/TabsContainer.svelte | 2 +- src/lib/components/WidgetContainer.svelte | 8 +- src/lib/defaultGraph.ts | 36 +-- src/lib/nodes/ComfyWidgetNodes.ts | 3 +- src/lib/stores/layoutState.ts | 242 +++++++++++++++++-- src/lib/widgets/ButtonWidget.svelte | 5 + src/lib/widgets/TextWidget.svelte | 6 +- vite.config.ts | 12 +- 10 files changed, 265 insertions(+), 53 deletions(-) diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index 8a0027c..e57dd08 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -49,7 +49,7 @@ {#if container && children}
- .widget.selected { - background: var(--color-yellow-200); + .widget { + height: 100%; + + &.selected { + background: var(--color-yellow-200); + } } .container.selected { background: var(--color-yellow-400); diff --git a/src/lib/defaultGraph.ts b/src/lib/defaultGraph.ts index 50aa856..c17c3f6 100644 --- a/src/lib/defaultGraph.ts +++ b/src/lib/defaultGraph.ts @@ -4560,7 +4560,7 @@ const defaultGraph: SerializedAppState = { showTitle: false, direction: "horizontal", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: false, flexGrow: 100, disabled: false @@ -4580,7 +4580,7 @@ const defaultGraph: SerializedAppState = { showTitle: false, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: false, flexGrow: 100, disabled: false @@ -4605,7 +4605,7 @@ const defaultGraph: SerializedAppState = { showTitle: false, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: false, flexGrow: 100, disabled: false @@ -4736,7 +4736,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: false, flexGrow: 100, disabled: false @@ -4776,7 +4776,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", hidden: false, flexGrow: 100, disabled: false @@ -4869,7 +4869,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: false, flexGrow: 100, disabled: false @@ -4908,7 +4908,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "block", + containerVariant: "block", hidden: true, flexGrow: 100, disabled: false @@ -4962,7 +4962,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", hidden: false, flexGrow: 100, disabled: false @@ -4984,7 +4984,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", hidden: false, flexGrow: 100, disabled: false @@ -5023,7 +5023,7 @@ const defaultGraph: SerializedAppState = { showTitle: false, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", hidden: false, flexGrow: 100, disabled: false @@ -5044,7 +5044,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", hidden: false, flexGrow: 100, disabled: false @@ -5116,7 +5116,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", flexGrow: 100, disabled: false } @@ -5136,7 +5136,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "block", + containerVariant: "block", flexGrow: 100, disabled: false } @@ -5241,7 +5241,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", flexGrow: 100, disabled: false } @@ -5261,7 +5261,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", flexGrow: 100, disabled: false } @@ -5331,7 +5331,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", flexGrow: 100 } }, @@ -5353,7 +5353,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", flexGrow: 100 } }, @@ -5372,7 +5372,7 @@ const defaultGraph: SerializedAppState = { showTitle: true, direction: "horizontal", classes: "", - blockVariant: "hidden", + containerVariant: "hidden", flexGrow: 100 } }, diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index eee5593..782efd7 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -489,7 +489,8 @@ LiteGraph.registerNodeType({ }) export interface ComfyButtonProperties extends ComfyWidgetProperties { - message: string + message: string, + variant: string } export class ComfyButtonNode extends ComfyWidgetNode { diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index 723c419..c0f09b8 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -6,61 +6,233 @@ import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action'; import type { ComfyWidgetNode } from '$lib/nodes'; type DragItemEntry = { + /* + * Drag item. + */ dragItem: IDragItem, + + /* + * Children of this drag item. + * Only applies if the drag item's type is "container" + */ children: IDragItem[] | null, + + /* + * Parent of this drag item. + */ parent: IDragItem | null } +/* + * Global workflow attributes + */ export type LayoutAttributes = { + /* + * Default subgraph to run when the "Queue Prompt" button in the bottom bar + * is pressed. + * + * If it's an empty string, all backend nodes will be included in the prompt + * instead. + */ defaultSubgraph: string } +/* + * Keeps track of the tree of UI components - widgets and the containers that + * group them together. + */ export type LayoutState = { + /* + * Root of the UI tree + */ root: IDragItem | null, + + /* + * All items indexed by their own ID + */ allItems: Record, + + /* + * Items indexed by the litegraph node they're bound to + * Only contains drag items of type "widget" + */ allItemsByNode: Record, + + /* + * Next ID to use for instantiating a new drag item + */ currentId: number, + + /* + * Selected drag items. + */ currentSelection: DragItemID[], + + /* + * Selected LGraphNodes inside the litegraph canvas. + */ currentSelectionNodes: LGraphNode[], + + /* + * If true, a saved workflow is being deserialized, so ignore any + * nodeAdded/nodeRemoved events. + * + * TODO: instead use LGraphAddNodeOptions.addedByDeserialize + */ isConfiguring: boolean, + + /* + * If true, the right-click context menu is open + */ isMenuOpen: boolean, + + /* + * Global workflow attributes + */ attrs: LayoutAttributes } +/** + * Attributes for both containers and nodes, or containers only. + * If the attribute can be applicable to both, then it should go here. + * If it only applies to a container it should go here too. + * If it only applies to a node it should be placed in its LGraphNode.properties (nodeProps) instead. + **/ export type Attributes = { + /* + * Flex direction for containers. + */ direction: "horizontal" | "vertical", + + /* + * Display name of this item. + */ title: string, + + /* + * If false, hide the title. + */ showTitle: boolean, + + /* + * List of classes to apply to the component. + */ classes: string, - blockVariant?: "block" | "hidden", + + /* + * Variant for containers. "hidden" hides margin/borders. + */ + containerVariant?: "block" | "hidden", + + /* + * If true, don't show this component in the UI + */ hidden?: boolean, + + /* + * If true, grey out this component in the UI + */ disabled?: boolean, + + /* + * CSS Flex grow + */ flexGrow?: number, - /** Display variant for widgets/containers (e.g. number widget can act as slider/knob/dial) */ + /** + * Display variant for widgets/containers (e.g. number widget can act as slider/knob/dial) + * Valid values depend on the widget in question. + */ variant?: string, + /*************************************/ + /* Special attributes for containers */ + /*************************************/ + + // Accordion openOnStartup?: boolean } export type AttributesSpec = { - id?: number, // for svelte keyed each + /* + * ID necessary for svelte's keyed each, autoset at the top level in this source file. + */ + id?: number, + + /* + * Attribute name. Corresponds to the name of the instance variable in the + * hashmap/class instance, which depends on `location`. + */ name: string, - type: string, + + /* + * Type of this attribute. + * If you want to support a custom type, use "string" combined with + * `serialize` and `deserialize`. + */ + type: "string" | "enum" | "number" | "boolean", + + /* + * Location of this attribute. + * - "widget": inside IDragNode.attrs + * - "nodeProps": inside LGraphNode.properties + * - "nodeVars": an instance variable directly on an LGraphNode + * - "workflow": inside $layoutState.attrs + */ location: "widget" | "nodeProps" | "nodeVars" | "workflow" + + /* + * Can this attribute be edited in the properties pane. + */ editable: boolean, + + /* + * Default value to supply to this attribute if it is null when the properties pane is opened. + * NOTE: This means that any attribute can't have a default null value! + */ defaultValue: any, + /* + * If `type` is "enum", the valid values for the combo widget. + */ values?: string[], - hidden?: boolean, + + /* + * Valid `LGraphNode.type`s this property applies to if it's located in a node. + * These are like "ui/button", "ui/slider". + */ validNodeTypes?: string[], + /* + * Callback: if false, don't show the property in the pane. + * Useful if you need to show the property based on another property. + * Example: If the IDragItem is a container (not a widget), show its flex `direction`. + */ canShow?: (arg: IDragItem | LGraphNode) => boolean, + + /* + * If the type of this spec is "string", but the underlying type is something else, + * convert the value to a string here so it can be edited in the textbox. + */ serialize?: (arg: any) => string, + + /* + * If the type of this spec is "string", but the underlying type is something else, + * convert the textbox value into the underlying value. + */ deserialize?: (arg: string) => any, + + /* + * If true, when this property is changed the properties pane will be rebuilt. + * This should be used if there's a canShow dependent on this property so + * the pane can be updated with the new list of valid properties. + */ refreshPanelOnChange?: boolean } +/* + * A list of `AttributesSpec`s grouped under a category. + */ export type AttributesCategorySpec = { categoryName: string, specs: AttributesSpec[] @@ -75,6 +247,10 @@ const deserializeStringArray = (arg: string) => { return arg.split(",").map(s => s.trim()) } +/* + * Attributes that will show up in the properties panel. + * Their order in the list is the order they'll appear in the panel. + */ const ALL_ATTRIBUTES: AttributesSpecList = [ { categoryName: "appearance", @@ -136,7 +312,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ refreshPanelOnChange: true }, { - name: "blockVariant", + name: "containerVariant", type: "enum", location: "widget", editable: true, @@ -218,6 +394,7 @@ const ALL_ATTRIBUTES: AttributesSpecList = [ } ]; +// This is needed so the specs can be iterated with svelte's keyed #each. let i = 0; for (const cat of Object.values(ALL_ATTRIBUTES)) { for (const val of Object.values(cat.specs)) { @@ -228,20 +405,54 @@ for (const cat of Object.values(ALL_ATTRIBUTES)) { export { ALL_ATTRIBUTES }; +/* + * Something that can be dragged around in the frontend - a widget or a container. + */ export interface IDragItem { - type: string, + /* + * Type of the item. + */ + type: "container" | "widget", + + /* + * Unique ID of the item. + */ id: DragItemID, + + /* + * If true, the node associated with this drag item is executing. + * Used to show an indicator on the widget/container. + */ isNodeExecuting?: boolean, + + /* + * Attributes for this drag item. + */ attrs: Attributes, + + /* + * Hackish thing to indicate to Svelte that an attribute changed. + * TODO Use Writeable instead! + */ attrsChanged: Writable } +/* + * A container (block, accordion, tabs). Has child drag items. + */ export interface ContainerLayout extends IDragItem { type: "container", } +/* + * A widget (slider, dropdown, textbox...) + */ export interface WidgetLayout extends IDragItem { type: "widget", + + /* + * litegraph node this widget is bound to. + */ node: ComfyWidgetNode } @@ -310,7 +521,7 @@ function addContainer(parent: ContainerLayout | null, attrs: Partial showTitle: true, direction: "vertical", classes: "", - blockVariant: "block", + containerVariant: "block", flexGrow: 100, ...attrs } @@ -371,23 +582,10 @@ function nodeAdded(node: LGraphNode) { const parent = findDefaultContainerForInsertion(); - // Two cases where we want to add nodes: - // 1. User adds a new UI node, so we should instantiate its widget in the frontend. - // 2. User adds a node with inputs that can be filled by frontend widgets. - // Depending on config, this means we should instantiate default UI nodes connected to those inputs. - - console.debug(node) + console.debug("[layoutState] nodeAdded", node) if ("svelteComponentType" in node) { addWidget(parent, node as ComfyWidgetNode); } - - // Add default node panel with all widgets autoinstantiated - // if (node.widgets && node.widgets.length > 0) { - // const container = addContainer(parent.id, { title: node.title, direction: "vertical", associatedNode: node.id }); - // for (const widget of node.widgets) { - // addWidget(container.id, node, widget, { associatedNode: node.id }); - // } - // } } function removeEntry(state: LayoutState, id: DragItemID) { diff --git a/src/lib/widgets/ButtonWidget.svelte b/src/lib/widgets/ButtonWidget.svelte index a0c72ed..d1c6c6d 100644 --- a/src/lib/widgets/ButtonWidget.svelte +++ b/src/lib/widgets/ButtonWidget.svelte @@ -44,5 +44,10 @@ .wrapper { padding: 2px; width: 100%; + height: 100%; + + :global(> button) { + height: 100%; + } } diff --git a/src/lib/widgets/TextWidget.svelte b/src/lib/widgets/TextWidget.svelte index 61704a4..67d8565 100644 --- a/src/lib/widgets/TextWidget.svelte +++ b/src/lib/widgets/TextWidget.svelte @@ -35,7 +35,7 @@ disabled={widget.attrs.disabled} lines={node.properties.multiline ? 5 : 1} max_lines={node.properties.multiline ? 5 : 1} - show_label={true} + show_label={widget.attrs.title !== ""} on:change on:submit on:blur @@ -49,4 +49,8 @@ padding: 2px; width: 100%; } + + :global(span.hide) { + display: none; + } diff --git a/vite.config.ts b/vite.config.ts index ac36164..5288058 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,12 +8,12 @@ import { viteStaticCopy } from 'vite-plugin-static-copy' export default defineConfig({ clearScreen: false, plugins: [ - FullReload([ - // "src/**/*.{js,ts,scss,svelte}" - "src/**/*.{scss}", - "src/lib/stores/*.*", - "src/**/ComfyApp.{ts,svelte}" - ]), + // FullReload([ + // // "src/**/*.{js,ts,scss,svelte}" + // "src/**/*.{scss}", + // "src/lib/stores/*.*", + // "src/**/ComfyApp.{ts,svelte}" + // ]), svelte(), viteStaticCopy({ targets: [