469 lines
19 KiB
Svelte
469 lines
19 KiB
Svelte
<script lang="ts">
|
|
import { Block, BlockTitle } from "@gradio/atoms";
|
|
import { TextBox, Checkbox } from "@gradio/form";
|
|
import { LGraphNode } from "@litegraph-ts/core"
|
|
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
|
import uiState from "$lib/stores/uiState"
|
|
import { get, type Writable, writable } from "svelte/store"
|
|
import type { ComfyWidgetNode } from "$lib/nodes";
|
|
import ComfyNumberProperty from "./ComfyNumberProperty.svelte";
|
|
import ComfyComboProperty from "./ComfyComboProperty.svelte";
|
|
|
|
let target: IDragItem | null = null;
|
|
let node: LGraphNode | null = null;
|
|
let attrsChanged: Writable<boolean> | null = null;
|
|
let refreshPanel: Writable<number> = writable(0);
|
|
|
|
$: if ($layoutState.currentSelection.length > 0) {
|
|
const targetId = $layoutState.currentSelection.slice(-1)[0]
|
|
target = $layoutState.allItems[targetId].dragItem
|
|
attrsChanged = target.attrsChanged;
|
|
if (target.type === "widget") {
|
|
node = (target as WidgetLayout).node
|
|
}
|
|
else {
|
|
node = null;
|
|
}
|
|
}
|
|
else if ($layoutState.currentSelectionNodes.length > 0) {
|
|
target = null;
|
|
node = $layoutState.currentSelectionNodes[0]
|
|
attrsChanged = null;
|
|
}
|
|
else {
|
|
target = null
|
|
node = null;
|
|
attrsChanged = null;
|
|
}
|
|
|
|
$: if (target) {
|
|
for (const cat of Object.values(ALL_ATTRIBUTES)) {
|
|
for (const spec of Object.values(cat.specs)) {
|
|
if (spec.location === "widget" && target.attrs[spec.name] == null) {
|
|
if (!spec.editable)
|
|
continue;
|
|
|
|
if (spec.canShow && !spec.canShow(target))
|
|
continue;
|
|
|
|
console.warn("Set default widget attr", spec.name, spec.defaultValue, target)
|
|
let value = spec.defaultValue;
|
|
target.attrs[spec.name] = value;
|
|
if (spec.refreshPanelOnChange)
|
|
$refreshPanel += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let targetType: string = "???"
|
|
|
|
$: {
|
|
if (node != null)
|
|
targetType = node.type || "Node"
|
|
else if (target)
|
|
targetType = "Group"
|
|
else
|
|
targetType = ""
|
|
}
|
|
|
|
function validNodeProperty(spec: AttributesSpec, node: LGraphNode | null): boolean {
|
|
if (node == null || spec.location !== "nodeProps")
|
|
return false;
|
|
|
|
if (spec.canShow && !spec.canShow(node))
|
|
return false;
|
|
|
|
if (spec.validNodeTypes) {
|
|
return spec.validNodeTypes.indexOf(node.type) !== -1;
|
|
}
|
|
return spec.name in node.properties
|
|
}
|
|
|
|
function validNodeVar(spec: AttributesSpec, node: LGraphNode | null): boolean {
|
|
if (node == null || spec.location !== "nodeVars")
|
|
return false;
|
|
|
|
if (spec.canShow && !spec.canShow(node))
|
|
return false;
|
|
|
|
if (spec.validNodeTypes) {
|
|
return spec.validNodeTypes.indexOf(node.type) !== -1;
|
|
}
|
|
return spec.name in node
|
|
}
|
|
|
|
function validWidgetAttribute(spec: AttributesSpec, widget: IDragItem | null): boolean {
|
|
if (widget == null || spec.location !== "widget")
|
|
return false;
|
|
if (spec.canShow)
|
|
return spec.canShow(widget);
|
|
|
|
if (spec.validNodeTypes) {
|
|
if (widget.type === "widget") {
|
|
const node = (widget as WidgetLayout).node
|
|
if (!node)
|
|
return false;
|
|
return spec.validNodeTypes.indexOf(node.type) !== -1;
|
|
}
|
|
else if (widget.type === "container") {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return spec.name in widget.attrs
|
|
}
|
|
|
|
function validWorkflowAttribute(spec: AttributesSpec): boolean {
|
|
if (spec.location !== "workflow")
|
|
return false;
|
|
|
|
return spec.name in $layoutState.attrs
|
|
}
|
|
|
|
function getAttribute(target: IDragItem, spec: AttributesSpec): any {
|
|
let value = target.attrs[spec.name]
|
|
if (value == null)
|
|
value = spec.defaultValue
|
|
else if (spec.serialize)
|
|
value = spec.serialize(value)
|
|
console.debug("[ComfyProperties] getAttribute", spec.name, value, target, spec)
|
|
return value
|
|
}
|
|
|
|
function updateAttribute(spec: AttributesSpec, target: IDragItem | null, value: any) {
|
|
if (target == null || !spec.editable)
|
|
return;
|
|
|
|
const name = spec.name
|
|
|
|
console.debug("[ComfyProperties] updateAttribute", spec, value, name, node)
|
|
if (spec.deserialize)
|
|
value = spec.deserialize(value)
|
|
|
|
target.attrs[name] = value
|
|
target.attrsChanged.set(get(target.attrsChanged) + 1)
|
|
|
|
if (node && "propsChanged" in node) {
|
|
const comfyNode = node as ComfyWidgetNode
|
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
|
}
|
|
|
|
console.warn(spec)
|
|
if (spec.refreshPanelOnChange) {
|
|
doRefreshPanel()
|
|
}
|
|
}
|
|
|
|
function getProperty(node: LGraphNode, spec: AttributesSpec) {
|
|
let value = node.properties[spec.name]
|
|
if (value == null)
|
|
value = spec.defaultValue
|
|
else if (spec.serialize)
|
|
value = spec.serialize(value)
|
|
console.debug("[ComfyProperties] getProperty", spec, value, node)
|
|
return value
|
|
}
|
|
|
|
function updateProperty(spec: AttributesSpec, value: any) {
|
|
if (node == null || !spec.editable)
|
|
return
|
|
|
|
const name = spec.name
|
|
console.warn("[ComfyProperties] updateProperty", name, value)
|
|
|
|
if (spec.deserialize)
|
|
value = spec.deserialize(value)
|
|
|
|
node.properties[name] = value;
|
|
|
|
if ("propsChanged" in node) {
|
|
const comfyNode = node as ComfyWidgetNode
|
|
comfyNode.notifyPropsChanged();
|
|
}
|
|
|
|
if (spec.refreshPanelOnChange)
|
|
doRefreshPanel()
|
|
}
|
|
|
|
function getVar(node: LGraphNode, spec: AttributesSpec) {
|
|
let value = node[spec.name]
|
|
if (value == null)
|
|
value = spec.defaultValue
|
|
else if (spec.serialize)
|
|
value = spec.serialize(value)
|
|
console.debug("[ComfyProperties] getVar", spec, value, node)
|
|
return value
|
|
}
|
|
|
|
function updateVar(spec: AttributesSpec, value: any) {
|
|
if (node == null || !spec.editable)
|
|
return;
|
|
|
|
const name = spec.name
|
|
|
|
console.debug("[ComfyProperties] updateVar", spec, value, name, node)
|
|
if (spec.deserialize)
|
|
value = spec.deserialize(value)
|
|
|
|
node[name] = value;
|
|
|
|
if ("propsChanged" in node) {
|
|
const comfyNode = node as ComfyWidgetNode
|
|
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
|
}
|
|
|
|
if (spec.refreshPanelOnChange) {
|
|
doRefreshPanel()
|
|
}
|
|
}
|
|
|
|
function doRefreshPanel() {
|
|
console.warn("[ComfyProperties] doRefreshPanel")
|
|
$refreshPanel += 1;
|
|
}
|
|
|
|
function updateWorkflowAttribute(spec: AttributesSpec, value: any) {
|
|
if (!spec.editable)
|
|
return;
|
|
|
|
const name = spec.name
|
|
console.warn("updateWorkflowAttribute", name, value)
|
|
|
|
$layoutState.attrs[name] = value
|
|
$layoutState = $layoutState
|
|
|
|
if (spec.refreshPanelOnChange)
|
|
doRefreshPanel()
|
|
}
|
|
</script>
|
|
|
|
<div class="props">
|
|
<div class="top">
|
|
<div class="target-name">
|
|
<span>
|
|
<span class="title">{target?.attrs?.title || node?.title || "Workflow"}<span>
|
|
{#if targetType !== ""}
|
|
<span class="type">({targetType})</span>
|
|
{/if}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="props-entries">
|
|
{#key $refreshPanel}
|
|
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
|
<div class="category-name">
|
|
<span>
|
|
<span class="title">{category.categoryName}</span>
|
|
</span>
|
|
</div>
|
|
{#each category.specs as spec(spec.id)}
|
|
{#if validWidgetAttribute(spec, target)}
|
|
<div class="props-entry">
|
|
{#if spec.type === "string"}
|
|
<TextBox
|
|
value={getAttribute(target, spec)}
|
|
on:change={(e) => 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"}
|
|
<Checkbox
|
|
value={getAttribute(target, spec)}
|
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
label={spec.name}
|
|
/>
|
|
{:else if spec.type === "number"}
|
|
<ComfyNumberProperty
|
|
name={spec.name}
|
|
value={getAttribute(target, spec)}
|
|
step={spec.step || 1}
|
|
min={spec.min || -1024}
|
|
max={spec.max || 1024}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
|
/>
|
|
{:else if spec.type === "enum"}
|
|
<ComfyComboProperty
|
|
name={spec.name}
|
|
value={getAttribute(target, spec)}
|
|
values={spec.values}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateAttribute(spec, target, e.detail)}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{:else if node}
|
|
{#if validNodeProperty(spec, node)}
|
|
<div class="props-entry">
|
|
{#if spec.type === "string"}
|
|
<TextBox
|
|
value={getProperty(node, spec)}
|
|
on:change={(e) => 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"}
|
|
<Checkbox
|
|
value={getProperty(node, spec)}
|
|
label={spec.name}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateProperty(spec, e.detail)}
|
|
/>
|
|
{:else if spec.type === "number"}
|
|
<ComfyNumberProperty
|
|
name={spec.name}
|
|
value={getProperty(node, spec)}
|
|
step={spec.step || 1}
|
|
min={spec.min || -1024}
|
|
max={spec.max || 1024}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateProperty(spec, e.detail)}
|
|
/>
|
|
{:else if spec.type === "enum"}
|
|
<ComfyComboProperty
|
|
name={spec.name}
|
|
value={getProperty(node, spec)}
|
|
values={spec.values}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateProperty(spec, e.detail)}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{:else if validNodeVar(spec, node)}
|
|
<div class="props-entry">
|
|
{#if spec.type === "string"}
|
|
<TextBox
|
|
value={getVar(node, spec)}
|
|
on:change={(e) => 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"}
|
|
<Checkbox
|
|
value={getVar(node, spec)}
|
|
on:change={(e) => updateVar(spec, e.detail)}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
label={spec.name}
|
|
/>
|
|
{:else if spec.type === "number"}
|
|
<ComfyNumberProperty
|
|
name={spec.name}
|
|
value={getVar(node, spec)}
|
|
step={spec.step || 1}
|
|
min={spec.min || -1024}
|
|
max={spec.max || 1024}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateVar(spec, e.detail)}
|
|
/>
|
|
{:else if spec.type === "enum"}
|
|
<ComfyComboProperty
|
|
name={spec.name}
|
|
value={getVar(node, spec)}
|
|
values={spec.values}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateVar(spec, e.detail)}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{:else if !node && !target && validWorkflowAttribute(spec)}
|
|
<div class="props-entry">
|
|
{#if spec.type === "string"}
|
|
<TextBox
|
|
value={$layoutState.attrs[spec.name] || spec.defaultValue}
|
|
on:change={(e) => 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"}
|
|
<Checkbox
|
|
value={$layoutState.attrs[spec.name] || spec.defaultValue}
|
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
label={spec.name}
|
|
/>
|
|
{:else if spec.type === "number"}
|
|
<ComfyNumberProperty
|
|
name={spec.name}
|
|
value={$layoutState.attrs[spec.name] || spec.defaultValue}
|
|
step={spec.step || 1}
|
|
min={spec.min || -1024}
|
|
max={spec.max || 1024}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
|
/>
|
|
{:else if spec.type === "enum"}
|
|
<ComfyComboProperty
|
|
name={spec.name}
|
|
value={$layoutState.attrs[spec.name] || spec.defaultValue}
|
|
values={spec.values}
|
|
disabled={!$uiState.uiUnlocked || !spec.editable}
|
|
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
|
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
{/each}
|
|
{/each}
|
|
{/key}
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.props-entry {
|
|
padding-bottom: 0.5rem;
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
display: flex;
|
|
flex-direction: row;
|
|
}
|
|
|
|
.target-name {
|
|
border-color: var(--neutral-400);
|
|
background: var(--neutral-300);
|
|
padding: 0.8rem 1.0rem;
|
|
|
|
.title {
|
|
font-weight: bold;
|
|
|
|
.type {
|
|
padding-left: 0.25rem;
|
|
font-weight: normal;
|
|
}
|
|
}
|
|
}
|
|
|
|
.category-name {
|
|
padding: 0.4rem 1.0rem;
|
|
border-color: var(--neutral-300);
|
|
background: var(--neutral-200);
|
|
}
|
|
|
|
.target-name, .category-name {
|
|
border-width: var(--block-border-width);
|
|
|
|
.type {
|
|
color: var(--neutral-500);
|
|
}
|
|
}
|
|
|
|
.bottom {
|
|
/* width: 100%;
|
|
height: auto;
|
|
position: absolute;
|
|
bottom: 0;
|
|
padding: 0.5em; */
|
|
}
|
|
</style>
|