Workflow properties
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||
import { Checkbox } from "@gradio/form"
|
||||
import { Checkbox, TextBox } from "@gradio/form"
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
@@ -29,6 +29,7 @@
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
let hasShownUIHelpToast: boolean = false;
|
||||
let uiTheme: string = "";
|
||||
|
||||
let debugLayout: boolean = false;
|
||||
|
||||
@@ -46,7 +47,10 @@
|
||||
|
||||
function queuePrompt() {
|
||||
console.log("Queuing!");
|
||||
app.queuePrompt(0, 1);
|
||||
let subworkflow = $uiState.subWorkflow;
|
||||
if (subworkflow === "")
|
||||
subworkflow = null
|
||||
app.queuePrompt(0, 1, subworkflow);
|
||||
}
|
||||
|
||||
$: if (app?.lCanvas) app.lCanvas.allow_dragnodes = !$uiState.nodesLocked;
|
||||
@@ -164,6 +168,12 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if uiTheme === "anapnoe"}
|
||||
<link rel="stylesheet" href="/src/scss/ux.scss">
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<div id="main">
|
||||
<div id="dropzone" class="dropzone"></div>
|
||||
<div id="container" bind:this={containerElem}>
|
||||
@@ -220,16 +230,24 @@
|
||||
<Button variant="secondary" on:click={doRefreshCombos}>
|
||||
🔄
|
||||
</Button>
|
||||
<Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/>
|
||||
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||
<Checkbox label="Disable Interaction" bind:value={$uiState.graphLocked}/> -->
|
||||
<Checkbox label="Auto-Add UI" bind:value={$uiState.autoAddUI}/>
|
||||
<TextBox bind:value={$uiState.subWorkflow} label="Subworkflow" show_label={true} lines={1} max_lines={1}/>
|
||||
<label class="label" for="enable-ui-editing">
|
||||
<BlockTitle>Enable UI Editing</BlockTitle>
|
||||
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="widgets">Widgets</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label" for="ui-theme">
|
||||
<BlockTitle>Theme</BlockTitle>
|
||||
<select id="ui-theme" name="ui-theme" bind:value={uiTheme}>
|
||||
<option value="">None</option>
|
||||
<option value="anapnoe">Anapnoe</option>
|
||||
</select>
|
||||
</label>
|
||||
<select id="enable-ui-editing" name="enable-ui-editing" bind:value={$uiState.uiEditMode}>
|
||||
<option value="disabled">Disabled</option>
|
||||
<option value="widgets">Widgets</option>
|
||||
</select>
|
||||
</div>
|
||||
<LightboxModal />
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@ import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get } from "svelte/store";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { promptToGraphVis, toGraphVis } from "$lib/utils";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -54,9 +55,11 @@ export type SerializedPromptInputs = {
|
||||
class_type: string
|
||||
}
|
||||
|
||||
export type SerializedPromptOutput = Record<string, SerializedPromptInputs>
|
||||
|
||||
export type SerializedPrompt = {
|
||||
workflow: SerializedLGraph,
|
||||
output: Record<string, SerializedPromptInputs>
|
||||
output: SerializedPromptOutput
|
||||
}
|
||||
|
||||
export type Progress = {
|
||||
@@ -420,7 +423,7 @@ export default class ComfyApp {
|
||||
* Converts the current graph workflow for sending to the API
|
||||
* @returns The workflow and node links
|
||||
*/
|
||||
async graphToPrompt(): Promise<SerializedPrompt> {
|
||||
async graphToPrompt(tag: string | null = null): Promise<SerializedPrompt> {
|
||||
// Run frontend-only logic
|
||||
this.lGraph.runStep(1)
|
||||
|
||||
@@ -438,6 +441,11 @@ export default class ComfyApp {
|
||||
|
||||
const node = node_ as ComfyBackendNode;
|
||||
|
||||
if (tag && node.tags.indexOf(tag) === -1) {
|
||||
console.debug("Skipping tagged node", tag, node.tags)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.mode === NodeMode.NEVER) {
|
||||
// Don't serialize muted nodes
|
||||
continue;
|
||||
@@ -451,6 +459,11 @@ export default class ComfyApp {
|
||||
const inp = node.inputs[i];
|
||||
const inputLink = node.getInputLink(i)
|
||||
const inputNode = node.getInputNode(i)
|
||||
|
||||
if (inputNode && tag && "tags" in inputNode && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inputLink || !inputNode) {
|
||||
if ("config" in inp) {
|
||||
const defaultValue = (inp as IComfyInputSlot).config?.defaultValue
|
||||
@@ -489,17 +502,36 @@ export default class ComfyApp {
|
||||
if (parent) {
|
||||
const seen = {}
|
||||
let link = node.getInputLink(i);
|
||||
while (parent && !parent.isBackendNode) {
|
||||
|
||||
const isValidParent = (parent: ComfyGraphNode) => {
|
||||
if (!parent || parent.isBackendNode)
|
||||
return false;
|
||||
if ("tags" in parent && (parent.tags as string[]).indexOf(tag) === -1)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (isValidParent(parent)) {
|
||||
link = parent.getInputLink(link.origin_slot);
|
||||
if (link && !seen[link.id]) {
|
||||
seen[link.id] = true
|
||||
parent = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
||||
const inputNode = parent.getInputNode(link.origin_slot) as ComfyGraphNode;
|
||||
if (inputNode && "tags" in inputNode && tag && (inputNode.tags as string[]).indexOf(tag) === -1) {
|
||||
console.debug("Skipping tagged parent node", tag, node.tags)
|
||||
parent = null;
|
||||
}
|
||||
else {
|
||||
parent = inputNode;
|
||||
}
|
||||
} else {
|
||||
parent = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (link && parent && parent.isBackendNode) {
|
||||
if ("tags" in parent && tag && (parent.tags as string[]).indexOf(tag) === -1)
|
||||
continue;
|
||||
|
||||
const input = node.inputs[i]
|
||||
// TODO can null be a legitimate value in some cases?
|
||||
// Nodes like CLIPLoader will never have a value in the frontend, hence "null".
|
||||
@@ -522,15 +554,19 @@ export default class ComfyApp {
|
||||
if (Array.isArray(output[o].inputs[i])
|
||||
&& output[o].inputs[i].length === 2
|
||||
&& !output[output[o].inputs[i][0]]) {
|
||||
console.debug("Prune removed node link", o, i, output[o].inputs[i])
|
||||
delete output[o].inputs[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.warn({ workflow, output })
|
||||
console.warn(promptToGraphVis({ workflow, output }))
|
||||
|
||||
return { workflow, output };
|
||||
}
|
||||
|
||||
async queuePrompt(num: number, batchCount: number = 1) {
|
||||
async queuePrompt(num: number, batchCount: number = 1, tag: string | null = null) {
|
||||
this.queueItems.push({ num, batchCount });
|
||||
|
||||
// Only have one action process the items so each one gets a unique seed correctly
|
||||
@@ -545,7 +581,7 @@ export default class ComfyApp {
|
||||
console.log(`Queue get! ${num} ${batchCount}`);
|
||||
|
||||
for (let i = 0; i < batchCount; i++) {
|
||||
const p = await this.graphToPrompt();
|
||||
const p = await this.graphToPrompt(tag);
|
||||
|
||||
try {
|
||||
await this.api.queuePrompt(num, p);
|
||||
@@ -626,12 +662,8 @@ export default class ComfyApp {
|
||||
if ("config" in input) {
|
||||
const comfyInput = input as IComfyInputSlot;
|
||||
|
||||
console.warn("RefreshCombo", comfyInput.defaultWidgetNode, comfyInput)
|
||||
|
||||
if (comfyInput.defaultWidgetNode == nodes.ComfyComboNode && def["input"]["required"][comfyInput.name] !== undefined) {
|
||||
comfyInput.config.values = def["input"]["required"][comfyInput.name][0];
|
||||
|
||||
console.warn("RefreshCombo", comfyInput.config.values, def["input"]["required"][comfyInput.name])
|
||||
const inputNode = node.getInputNode(index)
|
||||
|
||||
if (inputNode && "doAutoConfig" in inputNode) {
|
||||
|
||||
54
src/lib/components/ComfyComboProperty.svelte
Normal file
54
src/lib/components/ComfyComboProperty.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let value: string = "";
|
||||
export let values: string[] = [""];
|
||||
export let name: string = "";
|
||||
let value_: string = ""
|
||||
|
||||
$: handleChange(value);
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: string;
|
||||
submit: undefined;
|
||||
blur: undefined;
|
||||
}>();
|
||||
|
||||
function handleChange(val: string) {
|
||||
if (val != value_)
|
||||
dispatch("change", val);
|
||||
value_ = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<label class="select-wrapper">
|
||||
<BlockTitle>{name}</BlockTitle>
|
||||
<div class="select">
|
||||
<select on:blur bind:value>
|
||||
{#each values as value}
|
||||
<option {value}>
|
||||
{value}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<style lang="scss">
|
||||
.select-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
|
||||
select {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-title {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
</style>
|
||||
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
45
src/lib/components/ComfyNumberProperty.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
export let value: number = 0;
|
||||
export let step: number = 1;
|
||||
export let name: string = "";
|
||||
let value_: number = 0;
|
||||
|
||||
$: value;
|
||||
$: handleChange(value);
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: number;
|
||||
submit: undefined;
|
||||
blur: undefined;
|
||||
}>();
|
||||
|
||||
function handleChange(val: number) {
|
||||
if (val != value_)
|
||||
dispatch("change", val);
|
||||
value_ = val
|
||||
}
|
||||
</script>
|
||||
|
||||
<label class="number-wrapper">
|
||||
<BlockTitle>{name}</BlockTitle>
|
||||
<div class="number">
|
||||
<input type="number" bind:value {step}>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<style lang="scss">
|
||||
.number-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.number {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,10 +2,11 @@
|
||||
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 } from "$lib/stores/layoutState"
|
||||
import layoutState, { type IDragItem, type WidgetLayout, ALL_ATTRIBUTES, type AttributesSpec } from "$lib/stores/layoutState"
|
||||
import { get } from "svelte/store"
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import type { ComfyWidgetNode } from "$lib/nodes";
|
||||
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;
|
||||
@@ -20,6 +21,10 @@
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
else if ($layoutState.currentSelectionNodes.length > 0) {
|
||||
target = null;
|
||||
node = $layoutState.currentSelectionNodes[0]
|
||||
}
|
||||
else {
|
||||
target = null
|
||||
node = null;
|
||||
@@ -29,14 +34,14 @@
|
||||
|
||||
$: {
|
||||
if (node != null)
|
||||
targetType = node.type || "Widget"
|
||||
targetType = node.type || "Node"
|
||||
else if (target)
|
||||
targetType = "group"
|
||||
targetType = "Group"
|
||||
else
|
||||
targetType = "???"
|
||||
targetType = ""
|
||||
}
|
||||
|
||||
function updateAttribute(entry: any, value: any) {
|
||||
function updateAttribute(entry: AttributesSpec, value: any) {
|
||||
if (target) {
|
||||
const name = entry.name
|
||||
console.warn("updateAttribute", name, value)
|
||||
@@ -51,7 +56,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function updateProperty(entry: any, value: any) {
|
||||
function updateProperty(entry: AttributesSpec, value: any) {
|
||||
if (node) {
|
||||
const name = entry.name
|
||||
console.warn("updateProperty", name, value)
|
||||
@@ -64,125 +69,195 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getVar(node: LGraphNode, entry: AttributesSpec) {
|
||||
let value = node[entry.name]
|
||||
if (entry.serialize)
|
||||
value = entry.serialize(value)
|
||||
console.debug("[ComfyProperties] getVar", entry, value, node)
|
||||
return value
|
||||
}
|
||||
|
||||
function updateVar(entry: any, value: any) {
|
||||
if (node) {
|
||||
const name = entry.name
|
||||
console.warn("updateProperty", name, value)
|
||||
|
||||
if (entry.deserialize)
|
||||
value = entry.deserialize(value)
|
||||
|
||||
console.debug("[ComfyProperties] updateVar", entry, value, name, node)
|
||||
node[name] = value;
|
||||
|
||||
if ("propsChanged" in node) {
|
||||
const comfyNode = node as ComfyWidgetNode
|
||||
comfyNode.propsChanged.set(get(comfyNode.propsChanged) + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateWorkflowAttribute(entry: AttributesSpec, value: any) {
|
||||
const name = entry.name
|
||||
console.warn("updateWorkflowAttribute", name, value)
|
||||
|
||||
$layoutState.attrs[name] = value
|
||||
$layoutState = $layoutState
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="props">
|
||||
{#if target}
|
||||
<div class="top">
|
||||
<div class="target-name">
|
||||
<span>
|
||||
<span class="title">{target.attrs.title}</span>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="props-entries">
|
||||
{#each ALL_ATTRIBUTES as category(category.categoryName)}
|
||||
<div class="category-name">
|
||||
<span>
|
||||
<span class="title">{category.categoryName}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="props-entries">
|
||||
{#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.name)}
|
||||
{#if spec.location === "widget" && spec.name in target.attrs}
|
||||
{#each category.specs as spec(spec.name)}
|
||||
{#if spec.location === "widget" && target && spec.name in target.attrs}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={target.attrs[spec.name]}
|
||||
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||
on:input={(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 === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={target.attrs[spec.name]}
|
||||
step={1}
|
||||
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={target.attrs[spec.name]}
|
||||
values={spec.values}
|
||||
on:changed={(e) => updateAttribute(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if node}
|
||||
{#if spec.location === "nodeProps" && spec.name in node.properties}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={target.attrs[spec.name]}
|
||||
on:change={(e) => updateAttribute(spec, e.detail)}
|
||||
on:input={(e) => updateAttribute(spec, e.detail)}
|
||||
value={node.properties[spec.name]}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:input={(e) => updateProperty(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)}
|
||||
value={node.properties[spec.name]}
|
||||
label={spec.name}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={node.properties[spec.name]}
|
||||
step={1}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={node.properties[spec.name]}
|
||||
values={spec.values}
|
||||
on:changed={(e) => updateProperty(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if spec.location === "nodeVars" && spec.name in 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}
|
||||
max_lines={1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={getVar(node, spec)}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<label class="number-wrapper">
|
||||
<BlockTitle>{spec.name}</BlockTitle>
|
||||
<div class="number">
|
||||
<input
|
||||
type="number"
|
||||
value={target.attrs[spec.name]}
|
||||
step={1}
|
||||
on:change={(e) => updateAttribute(spec, e.currentTarget.valueAsNumber)}
|
||||
on:input={(e) => updateAttribute(spec, e.currentTarget.valueAsNumber)}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
step={1}
|
||||
on:change={(e) => updateVar(spec, e.detail)}
|
||||
/>
|
||||
{: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>
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={getVar(node, spec)}
|
||||
values={spec.values}
|
||||
on:changed={(e) => updateVar(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if node}
|
||||
{#if spec.location === "nodeProps" && spec.name in node.properties}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={node.properties[spec.name]}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
on:input={(e) => updateProperty(spec, e.detail)}
|
||||
label={spec.name}
|
||||
max_lines={1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={node.properties[spec.name]}
|
||||
on:change={(e) => updateProperty(spec, e.detail)}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<label class="number-wrapper">
|
||||
<BlockTitle>{spec.name}</BlockTitle>
|
||||
<div class="number">
|
||||
<input
|
||||
type="number"
|
||||
value={node.properties[spec.name]}
|
||||
step={1}
|
||||
on:change={(e) => updateProperty(spec, e.currentTarget.valueAsNumber)}
|
||||
on:input={(e) => updateProperty(spec, e.currentTarget.valueAsNumber)}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
{:else if spec.type === "enum"}
|
||||
<label class="select-wrapper">
|
||||
<BlockTitle>{spec.name}</BlockTitle>
|
||||
<div class="select">
|
||||
<select
|
||||
value={node.properties[spec.name]}
|
||||
on:change={(e) => updateProperty(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}
|
||||
{/if}
|
||||
{/each}
|
||||
{:else if spec.location === "workflow" && spec.name in $layoutState.attrs}
|
||||
<div class="props-entry">
|
||||
{#if spec.type === "string"}
|
||||
<TextBox
|
||||
value={$layoutState.attrs[spec.name]}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
on:input={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
label={spec.name}
|
||||
max_lines={1}
|
||||
/>
|
||||
{:else if spec.type === "boolean"}
|
||||
<Checkbox
|
||||
value={$layoutState.attrs[spec.name]}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
label={spec.name}
|
||||
/>
|
||||
{:else if spec.type === "number"}
|
||||
<ComfyNumberProperty
|
||||
name={spec.name}
|
||||
value={$layoutState.attrs[spec.name]}
|
||||
step={1}
|
||||
on:change={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
/>
|
||||
{:else if spec.type === "enum"}
|
||||
<ComfyComboProperty
|
||||
name={spec.name}
|
||||
value={$layoutState.attrs[spec.name]}
|
||||
values={spec.values}
|
||||
on:changed={(e) => updateWorkflowAttribute(spec, e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -218,34 +293,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.number-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.number {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
width: 100%;
|
||||
|
||||
.select {
|
||||
width: 100%;
|
||||
|
||||
select {
|
||||
width: 100%
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-title {
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
/* width: 100%;
|
||||
height: auto;
|
||||
|
||||
Reference in New Issue
Block a user