diff --git a/src/composables/widgets/useStringWidget.ts b/src/composables/widgets/useStringWidget.ts new file mode 100644 index 000000000..b340ff69a --- /dev/null +++ b/src/composables/widgets/useStringWidget.ts @@ -0,0 +1,90 @@ +import type { IWidget, LGraphNode } from '@comfyorg/litegraph' + +import { useSettingStore } from '@/stores/settingStore' +import type { ComfyApp } from '@/types' +import type { InputSpec } from '@/types/apiTypes' + +function addMultilineWidget( + node: LGraphNode, + name: string, + opts: { defaultVal: string; placeholder?: string }, + app: ComfyApp +) { + const inputEl = document.createElement('textarea') + inputEl.className = 'comfy-multiline-input' + inputEl.value = opts.defaultVal + inputEl.placeholder = opts.placeholder || name + if (app.vueAppReady) { + inputEl.spellcheck = useSettingStore().get( + 'Comfy.TextareaWidget.Spellcheck' + ) + } + + const widget = node.addDOMWidget(name, 'customtext', inputEl, { + getValue(): string { + return inputEl.value + }, + setValue(v: string) { + inputEl.value = v + } + }) + + widget.inputEl = inputEl + + inputEl.addEventListener('input', () => { + widget.callback?.(widget.value) + }) + + inputEl.addEventListener('pointerdown', (event: PointerEvent) => { + if (event.button === 1) { + app.canvas.processMouseDown(event) + } + }) + + inputEl.addEventListener('pointermove', (event: PointerEvent) => { + if ((event.buttons & 4) === 4) { + app.canvas.processMouseMove(event) + } + }) + + inputEl.addEventListener('pointerup', (event: PointerEvent) => { + if (event.button === 1) { + app.canvas.processMouseUp(event) + } + }) + + return { minWidth: 400, minHeight: 200, widget } +} + +export const useStringWidget = () => { + const widgetConstructor = ( + node: LGraphNode, + inputName: string, + inputData: InputSpec, + app: ComfyApp + ) => { + const defaultVal = inputData[1].default || '' + const multiline = !!inputData[1].multiline + + let res: { widget: IWidget } + if (multiline) { + res = addMultilineWidget( + node, + inputName, + { defaultVal, ...inputData[1] }, + app + ) + } else { + res = { + widget: node.addWidget('text', inputName, defaultVal, () => {}, {}) + } + } + + if (inputData[1].dynamicPrompts != undefined) + res.widget.dynamicPrompts = inputData[1].dynamicPrompts + + return res + } + + return widgetConstructor +} diff --git a/src/extensions/core/dynamicPrompts.ts b/src/extensions/core/dynamicPrompts.ts index b06b57599..e006edad1 100644 --- a/src/extensions/core/dynamicPrompts.ts +++ b/src/extensions/core/dynamicPrompts.ts @@ -10,8 +10,7 @@ useExtensionService().registerExtension({ if (node.widgets) { // Locate dynamic prompt text widgets // Include any widgets with dynamicPrompts set to true, and customtext - // @ts-expect-error dynamicPrompts is not typed - const widgets = node.widgets.filter((n) => n.dynamicPrompts) + const widgets = node.widgets.filter((w) => w.dynamicPrompts) for (const widget of widgets) { // Override the serialization of the value to resolve dynamic prompts for all widgets supporting it in this node // @ts-expect-error hacky override diff --git a/src/scripts/widgets.ts b/src/scripts/widgets.ts index 170353273..a1f92c8f8 100644 --- a/src/scripts/widgets.ts +++ b/src/scripts/widgets.ts @@ -15,6 +15,7 @@ import TiptapStarterKit from '@tiptap/starter-kit' import { Markdown as TiptapMarkdown } from 'tiptap-markdown' import { useRemoteWidget } from '@/composables/widgets/useRemoteWidget' +import { useStringWidget } from '@/composables/widgets/useStringWidget' import { useSettingStore } from '@/stores/settingStore' import { useToastStore } from '@/stores/toastStore' import { useWidgetStore } from '@/stores/widgetStore' @@ -335,52 +336,6 @@ function createIntWidget( } } -function addMultilineWidget(node, name: string, opts, app: ComfyApp) { - const inputEl = document.createElement('textarea') - inputEl.className = 'comfy-multiline-input' - inputEl.value = opts.defaultVal - inputEl.placeholder = opts.placeholder || name - if (app.vueAppReady) { - inputEl.spellcheck = useSettingStore().get( - 'Comfy.TextareaWidget.Spellcheck' - ) - } - - const widget = node.addDOMWidget(name, 'customtext', inputEl, { - getValue() { - return inputEl.value - }, - setValue(v) { - inputEl.value = v - } - }) - widget.inputEl = inputEl - - inputEl.addEventListener('input', () => { - widget.callback?.(widget.value) - }) - - inputEl.addEventListener('pointerdown', (event: PointerEvent) => { - if (event.button === 1) { - app.canvas.processMouseDown(event) - } - }) - - inputEl.addEventListener('pointermove', (event: PointerEvent) => { - if ((event.buttons & 4) === 4) { - app.canvas.processMouseMove(event) - } - }) - - inputEl.addEventListener('pointerup', (event: PointerEvent) => { - if (event.button === 1) { - app.canvas.processMouseUp(event) - } - }) - - return { minWidth: 400, minHeight: 200, widget } -} - function addMarkdownWidget(node, name: string, opts, app: ComfyApp) { TiptapMarkdown.configure({ html: false, @@ -528,29 +483,7 @@ export const ComfyWidgets: Record = { widget: node.addWidget('toggle', inputName, defaultVal, () => {}, options) } }, - STRING(node, inputName, inputData: InputSpec, app) { - const defaultVal = inputData[1].default || '' - const multiline = !!inputData[1].multiline - - let res - if (multiline) { - res = addMultilineWidget( - node, - inputName, - { defaultVal, ...inputData[1] }, - app - ) - } else { - res = { - widget: node.addWidget('text', inputName, defaultVal, () => {}, {}) - } - } - - if (inputData[1].dynamicPrompts != undefined) - res.widget.dynamicPrompts = inputData[1].dynamicPrompts - - return res - }, + STRING: useStringWidget(), MARKDOWN(node, inputName, inputData: InputSpec, app) { const defaultVal = inputData[1].default || '' diff --git a/src/types/litegraph-augmentation.d.ts b/src/types/litegraph-augmentation.d.ts index 7b758986e..657ef7318 100644 --- a/src/types/litegraph-augmentation.d.ts +++ b/src/types/litegraph-augmentation.d.ts @@ -17,6 +17,12 @@ declare module '@comfyorg/litegraph/dist/types/widgets' { onRemove?: () => void beforeQueued?: () => unknown serializeValue?: (node: LGraphNode, index: number) => Promise + + /** + * If the widget supports dynamic prompts, this will be set to true. + * See extensions/core/dynamicPrompts.ts + */ + dynamicPrompts?: boolean } }