diff --git a/web/comfy_shared.js b/web/comfy_shared.js index b3da129..bf1c60c 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -384,6 +384,48 @@ export function getWidgetType(config) { // #endregion +// function to test if input is a dynamic one +const isDynamicInput = (input) => { + infoLogger('Checking if input dynamic', { input }) + // return input.name.startsWith(connectionPrefix) + return input._isDynamic === true +} + +// Add a dynamic input, update node properties and slot colors! +const addDynamicInput = (node, name, kind) => { + const input = node.addInput(name, kind) + input._isDynamic = true + + update_dynamic_properties(node) + set_slot_colors(node, ['cyan', undefined], isDynamicInput) + + return input +} + +const set_slot_colors = (node, colors, condition) => { + if (!condition) { + condition = (_s) => true + } + + for (const slot of node.slots) { + infoLogger('Candidate', { slot, accepted: condition(slot) }) + if (condition(slot)) { + slot.color_off = colors[0] + slot.color_on = colors[1] + } + } +} + +const update_dynamic_properties = (node) => { + const dyn = [] + for (const input of node.inputs) { + if (isDynamicInput(input)) { + dyn.push(input.name) + } + } + node.setProperty('dynamic_connections', dyn) +} + // #region dynamic connections /** * @param {NodeType} nodeType The nodetype to attach the documentation to @@ -411,12 +453,84 @@ export const setupDynamicConnections = ( }, opts || {}, ) + nodeType.prototype.getSlotMenuOptions = function (slot) { + return [ + { + content: 'Rename Input', + callback: () => { + let dialog = app.canvas.createDialog( + "Name", + {}, + ) + let dialogInput = dialog.querySelector('input') + if (dialogInput) { + dialogInput.value = slot.input.label || slot.input.name || '' + } + let inner = () => { + app.graph.beforeChange() + // TODO: check if name exists or other guards + slot.input.label = dialogInput.value + app.graph.afterChange() + + dialog.close() + } + dialog.querySelector('button').addEventListener('click', inner) + dialogInput.addEventListener('keydown', (e) => { + dialog.is_modified = true + if (e.keyCode === 27) { + dialog.close() + } else if (e.keyCode === 13) { + inner() + } else if (e.keyCode !== 13 && e.target?.localName !== 'textarea') { + return + } + e.preventDefault() + e.stopPropagation() + }) + dialogInput.focus() + }, + }, + ] + + } + + const onConfigure = nodeType.prototype.onConfigure + + nodeType.prototype.onConfigure = function (data) { + const r = onConfigure ? onConfigure.apply(this, data) : undefined + + // Set or restore serialized properties, lt seems to auto serialize/deserialize to/from string + if (!('dynamic_connections' in this.properties)) { + // this.addProperty('dynamic_connections', [], 'string') + this.setProperty('dynamic_connections', []) + } else { + const dynamic_connections = this.properties.dynamic_connections + if (typeof dynamic_connections !== 'object') { + return r + } + for (const name of dynamic_connections) { + infoLogger(`Would dynamize: ${name}`) + const input = this.inputs.find((i) => i.name === name) + if (input) { + infoLogger('Input found', { input }) + input._isDynamic = true + } + } + } + // set color + set_slot_colors(this, ['cyan', undefined], isDynamicInput) + + return r + } + const onNodeCreated = nodeType.prototype.onNodeCreated const inputList = typeof inputType === 'object' nodeType.prototype.onNodeCreated = function () { const r = onNodeCreated ? onNodeCreated.apply(this, []) : undefined - this.addInput( + + const input = addDynamicInput( + this, `${prefix}${options.separator}${options.start_index}`, inputList ? '*' : inputType, ) @@ -488,10 +602,7 @@ export const dynamic_connection = ( opts || {}, ) - // function to test if input is a dynamic one - const isDynamicInput = (inputName) => inputName.startsWith(connectionPrefix) - - if (node.inputs.length > 0 && !isDynamicInput(node.inputs[index].name)) { + if (node.inputs.length > 0 && !isDynamicInput(node.inputs[index])) { return } @@ -501,7 +612,7 @@ export const dynamic_connection = ( const nameArray = options.nameArray || [] const clean_inputs = () => { - if (node.id < 0) return + if (node.id < 0) return // being duplicated if (node.inputs.length === 0) return let w_count = node.widgets?.length || 0 @@ -511,7 +622,7 @@ export const dynamic_connection = ( const to_remove = [] for (let n = 1; n < node.inputs.length; n++) { const element = node.inputs[n] - if (!element.link && isDynamicInput(element.name)) { + if (!element.link && isDynamicInput(element)) { if (node.widgets) { const w = node.widgets.find((w) => w.name === element.name) if (w) { @@ -525,9 +636,12 @@ export const dynamic_connection = ( } for (let i = 0; i < to_remove.length; i++) { const id = to_remove[i] - - node.removeInput(id) - i_count -= 1 + try { + node.removeInput(id) + i_count -= 1 + } catch (err) { + errorLogger('Cannot remove input', err) + } } node.inputs.length = i_count @@ -541,7 +655,7 @@ export const dynamic_connection = ( for (let i = 0; i < node.inputs.length; i++) { let name = '' // rename only prefixed inputs - if (isDynamicInput(node.inputs[i].name)) { + if (node.inputs[i].name.startsWith(connectionPrefix)) { // prefixed => rename and increase index name = `${connectionPrefix}${prefixed_idx}` prefixed_idx += 1 @@ -595,9 +709,8 @@ export const dynamic_connection = ( if (node.inputs.length === 0) return // add an extra input if (node.inputs[node.inputs.length - 1].link !== null) { - // count only the prefixed inputs const nextIndex = node.inputs.reduce( - (acc, cur) => (isDynamicInput(cur.name) ? ++acc : acc), + (acc, cur) => (isDynamicInput(cur) ? ++acc : acc), 0, ) @@ -607,7 +720,7 @@ export const dynamic_connection = ( : `${connectionPrefix}${nextIndex + options.start_index}` infoLogger(`Adding input ${nextIndex + 1} (${name})`) - node.addInput(name, conType) + addDynamicInput(node, name, conType) } } }