From 4e593bb30be561e39f1790e3514f60bb39e5a261 Mon Sep 17 00:00:00 2001 From: Mel Massadian Date: Mon, 18 Nov 2024 01:32:29 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20use=20the=20new=20parser=20?= =?UTF-8?q?for=20documentations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - might also fix #210 --- biome.json | 47 +- web/comfy_shared.js | 243 +++-- web/mtb_widgets.js | 2192 +++++++++++++++++++++---------------------- 3 files changed, 1280 insertions(+), 1202 deletions(-) diff --git a/biome.json b/biome.json index 7bfd11c..9a3c928 100644 --- a/biome.json +++ b/biome.json @@ -1,22 +1,31 @@ { - "$schema": "https://biomejs.dev/schemas/1.6.1/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "formatter": { - "lineEnding": "lf" - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "semicolons": "asNeeded", - "indentWidth": 2 - } + "$schema": "https://biomejs.dev/schemas/1.6.1/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noConsoleLog": "warn" + }, + "style": { + "noParameterAssign": "off", + "noShoutyConstants": "warn", + "useNamingConvention": "off" + } } + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf" + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded" + } + } } diff --git a/web/comfy_shared.js b/web/comfy_shared.js index 4f1b01f..ca54474 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -11,6 +11,7 @@ /// import { app } from '../../scripts/app.js' +import { api } from '../../scripts/api.js' // #region base utils @@ -18,7 +19,7 @@ import { app } from '../../scripts/app.js' export function makeUUID() { let dt = new Date().getTime() const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - const r = (dt + Math.random() * 16) % 16 | 0 + const r = ((dt + Math.random() * 16) % 16) | 0 dt = Math.floor(dt / 16) return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16) }) @@ -358,15 +359,17 @@ export function getWidgetType(config) { // #region dynamic connections /** - * @param {NodeType} nodeType - * @param {str} prefix - * @param {str | [str]} inputType + * @param {NodeType} nodeType The nodetype to attach the documentation to + * @param {str} prefix A prefix added to each dynamic inputs + * @param {str | [str]} inputType The datatype(s) of those dynamic inputs * @param {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}?} opts * @returns */ - export const setupDynamicConnections = (nodeType, prefix, inputType, opts) => { - infoLogger('Setting up dynamic connections for', nodeType) + infoLogger( + 'Setting up dynamic connections for', + Object.getOwnPropertyDescriptors(nodeType).title.value, + ) /** @type {{link?:LLink, ioSlot?:INodeInputSlot | INodeOutputSlot}} */ const options = opts || {} @@ -736,10 +739,84 @@ const create_documentation_stylesheet = () => { document.head.appendChild(styleTag) } } -let documentationConverter +let parserPromise +const callbackQueue = [] + +function runQueuedCallbacks() { + while (callbackQueue.length) { + const cb = callbackQueue.shift() + cb(window.MTB.mdParser) + } +} + +function loadParser(shiki) { + if (!parserPromise) { + parserPromise = import( + shiki + ? '/mtb_async/mtb_markdown_plus.umd.js' + : '/mtb_async/mtb_markdown.umd.js' + ) + .then((_module) => + shiki ? MTBMarkdownPlus.getParser() : MTBMarkdown.getParser(), + ) + .then((instance) => { + window.MTB.mdParser = instance + runQueuedCallbacks() + return instance + }) + .catch((error) => { + console.error('Error loading the parser:', error) + }) + } + return parserPromise +} + +export const ensureMarkdownParser = async (callback) => { + infoLogger('Ensuring md parser') + let use_shiki = false + try { + use_shiki = await api.getSetting('mtb.Use Shiki') + } catch (e) { + console.warn('Option not available yet', e) + } + + if (window.MTB?.mdParser) { + infoLogger('Markdown parser found') + callback?.(window.MTB.mdParser) + return window.MTB.mdParser + } + + if (!parserPromise) { + infoLogger('Running promise to fetch parser') + + try { + loadParser(use_shiki) //.then(() => { + // callback?.(window.MTB.mdParser) + // }) + } catch (error) { + console.error('Error loading the parser:', error) + } + } else { + infoLogger('A similar promise is already running, waiting for it to finish') + } + if (callback) { + callbackQueue.push(callback) + } + + await parserPromise + await parserPromise + + return window.MTB.mdParser +} /** - * Add documentation widget to the selected node + * Add documentation widget to the given node. + * + * This method will add a `docCtrl` property to the node + * that contains the AbortController that manages all the events + * defined inside it (global and instance ones) without explicit + * cleanup method for each. + * * @param {NodeData} nodeData * @param {NodeType} nodeType * @param {DocumentationOptions} opts @@ -756,25 +833,10 @@ export const addDocumentation = ( return } - if (!documentationConverter) { - infoLogger('Initializing our mardown converter') - documentationConverter = new showdown.Converter({ - tables: true, - strikethrough: true, - emoji: true, - ghCodeBlocks: true, - tasklists: true, - ghMentions: true, - smoothLivePreview: true, - simplifiedAutoLink: true, - parseImgDimensions: true, - openLinksInNewWindow: true, - }) - } - const options = opts || {} const iconSize = options.icon_size || 14 const iconMargin = options.icon_margin || 4 + let docElement = null let wrapper = null @@ -820,80 +882,87 @@ export const addDocumentation = ( wrapper = document.createElement('div') wrapper.classList.add('documentation-wrapper') - wrapper.innerHTML = documentationConverter.makeHtml(nodeData.description) docElement.appendChild(wrapper) - // resize handle - resizeHandle = document.createElement('div') - resizeHandle.style.width = '0' - resizeHandle.style.height = '0' - resizeHandle.style.position = 'absolute' - resizeHandle.style.bottom = '0' - resizeHandle.style.right = '0' + // wrapper.innerHTML = documentationConverter.makeHtml(nodeData.description) - resizeHandle.style.cursor = 'se-resize' - resizeHandle.style.userSelect = 'none' + ensureMarkdownParser().then(() => { + MTB.mdParser.parse(nodeData.description).then((e) => { + wrapper.innerHTML = e + // resize handle + resizeHandle = document.createElement('div') + resizeHandle.classList.add('doc-resize-handle') + resizeHandle.style.width = '0' + resizeHandle.style.height = '0' + resizeHandle.style.position = 'absolute' + resizeHandle.style.bottom = '0' + resizeHandle.style.right = '0' - resizeHandle.style.borderWidth = '15px' - resizeHandle.style.borderStyle = 'solid' + resizeHandle.style.cursor = 'se-resize' + resizeHandle.style.userSelect = 'none' - resizeHandle.style.borderColor = - 'transparent var(--border-color) var(--border-color) transparent' + resizeHandle.style.borderWidth = '15px' + resizeHandle.style.borderStyle = 'solid' - wrapper.appendChild(resizeHandle) - let isResizing = false + resizeHandle.style.borderColor = + 'transparent var(--border-color) var(--border-color) transparent' - let startX - let startY - let startWidth - let startHeight + wrapper.appendChild(resizeHandle) + let isResizing = false - resizeHandle.addEventListener( - 'mousedown', - (e) => { - e.stopPropagation() - isResizing = true - startX = e.clientX - startY = e.clientY - startWidth = Number.parseInt( - document.defaultView.getComputedStyle(docElement).width, - 10, + let startX + let startY + let startWidth + let startHeight + + resizeHandle.addEventListener( + 'mousedown', + (e) => { + e.stopPropagation() + isResizing = true + startX = e.clientX + startY = e.clientY + startWidth = Number.parseInt( + document.defaultView.getComputedStyle(docElement).width, + 10, + ) + startHeight = Number.parseInt( + document.defaultView.getComputedStyle(docElement).height, + 10, + ) + }, + + { signal: this.docCtrl.signal }, ) - startHeight = Number.parseInt( - document.defaultView.getComputedStyle(docElement).height, - 10, + + document.addEventListener( + 'mousemove', + (e) => { + if (!isResizing) return + const scale = app.canvas.ds.scale + const newWidth = startWidth + (e.clientX - startX) / scale + const newHeight = startHeight + (e.clientY - startY) / scale + + docElement.style.width = `${newWidth}px` + docElement.style.height = `${newHeight}px` + + this.docPos = { + width: `${newWidth}px`, + height: `${newHeight}px`, + } + }, + { signal: this.docCtrl.signal }, ) - }, - { signal: this.docCtrl.signal }, - ) - - document.addEventListener( - 'mousemove', - (e) => { - if (!isResizing) return - const scale = app.canvas.ds.scale - const newWidth = startWidth + (e.clientX - startX) / scale - const newHeight = startHeight + (e.clientY - startY) / scale - - docElement.style.width = `${newWidth}px` - docElement.style.height = `${newHeight}px` - - this.docPos = { - width: `${newWidth}px`, - height: `${newHeight}px`, - } - }, - { signal: this.docCtrl.signal }, - ) - - document.addEventListener( - 'mouseup', - () => { - isResizing = false - }, - { signal: this.docCtrl.signal }, - ) + document.addEventListener( + 'mouseup', + () => { + isResizing = false + }, + { signal: this.docCtrl.signal }, + ) + }) + }) } else if (!this.show_doc && docElement !== null) { docElement.remove() docElement = null diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index 2ee4284..2f1e6d1 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -20,1235 +20,1235 @@ import { infoLogger } from './comfy_shared.js' import { NumberInputWidget } from './numberInput.js' // NOTE: new widget types registered by MTB Widgets -const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX'] +const newTypes = [/*'BOOL'*/ , 'COLOR', 'BBOX'] const deprecated_nodes = { - // 'Animation Builder': - // 'Kept to avoid breaking older script but replaced by TimeEngine', + // 'Animation Builder': + // 'Kept to avoid breaking older script but replaced by TimeEngine', } const withFont = (ctx, font, cb) => { - const oldFont = ctx.font - ctx.font = font - cb() - ctx.font = oldFont + const oldFont = ctx.font + ctx.font = font + cb() + ctx.font = oldFont } const calculateTextDimensions = (ctx, value, width, fontSize = 16) => { - const words = value.split(' ') - const lines = [] - let currentLine = '' - for (const word of words) { - const testLine = currentLine.length === 0 ? word : `${currentLine} ${word}` - const testWidth = ctx.measureText(testLine).width - if (testWidth > width) { - lines.push(currentLine) - currentLine = word - } else { - currentLine = testLine - } - } - if (lines.length === 0) lines.push(value) - const textHeight = (lines.length + 1) * fontSize - const maxLineWidth = lines.reduce( - (maxWidth, line) => Math.max(maxWidth, ctx.measureText(line).width), - 0, - ) - return { textHeight, maxLineWidth } + const words = value.split(' ') + const lines = [] + let currentLine = '' + for (const word of words) { + const testLine = currentLine.length === 0 ? word : `${currentLine} ${word}` + const testWidth = ctx.measureText(testLine).width + if (testWidth > width) { + lines.push(currentLine) + currentLine = word + } else { + currentLine = testLine + } + } + if (lines.length === 0) lines.push(value) + const textHeight = (lines.length + 1) * fontSize + const maxLineWidth = lines.reduce( + (maxWidth, line) => Math.max(maxWidth, ctx.measureText(line).width), + 0, + ) + return { textHeight, maxLineWidth } } export function addMultilineWidget(node, name, opts, callback) { - const inputEl = document.createElement('textarea') - inputEl.className = 'comfy-multiline-input' - inputEl.value = opts.defaultVal - inputEl.placeholder = opts.placeholder || name + const inputEl = document.createElement('textarea') + inputEl.className = 'comfy-multiline-input' + inputEl.value = opts.defaultVal + inputEl.placeholder = opts.placeholder || name - const widget = node.addDOMWidget(name, 'textmultiline', inputEl, { - getValue() { - return inputEl.value - }, - setValue(v) { - inputEl.value = v - }, - }) - widget.inputEl = inputEl + const widget = node.addDOMWidget(name, 'textmultiline', inputEl, { + getValue() { + return inputEl.value + }, + setValue(v) { + inputEl.value = v + }, + }) + widget.inputEl = inputEl - inputEl.addEventListener('input', () => { - callback?.(widget.value) - widget.callback?.(widget.value) - }) - widget.onRemove = () => { - inputEl.remove() - } + inputEl.addEventListener('input', () => { + callback?.(widget.value) + widget.callback?.(widget.value) + }) + widget.onRemove = () => { + inputEl.remove() + } - return { minWidth: 400, minHeight: 200, widget } + return { minWidth: 400, minHeight: 200, widget } } export const VECTOR_AXIS = { - 0: 'x', - 1: 'y', - 2: 'z', - 3: 'w', + 0: 'x', + 1: 'y', + 2: 'z', + 3: 'w', } export function addVectorWidgetW( - node, - name, - value, - vector_size, - callback, - app, + node, + name, + value, + vector_size, + callback, + app, ) { - // const inputEl = document.createElement('div') - // const vecEl = document.createElement('div') - // - // inputEl.style.background = 'red' - // - // inputEl.className = 'comfy-vector-container' - // vecEl.className = 'comfy-vector-input' - // - // vecEl.style.display = 'flex' - // inputEl.appendChild(vecEl) - const inputs = [] + // const inputEl = document.createElement('div') + // const vecEl = document.createElement('div') + // + // inputEl.style.background = 'red' + // + // inputEl.className = 'comfy-vector-container' + // vecEl.className = 'comfy-vector-input' + // + // vecEl.style.display = 'flex' + // inputEl.appendChild(vecEl) + const inputs = [] - for (let i = 0; i < vector_size; i++) { - // const input = document.createElement('input') - // input.type = 'number' - // input.value = value[VECTOR_AXIS[i]] - const input = node.addWidget( - 'number', - `${name}_${VECTOR_AXIS[i]}`, - value[VECTOR_AXIS[i]], - (val) => {}, - ) + for (let i = 0; i < vector_size; i++) { + // const input = document.createElement('input') + // input.type = 'number' + // input.value = value[VECTOR_AXIS[i]] + const input = node.addWidget( + 'number', + `${name}_${VECTOR_AXIS[i]}`, + value[VECTOR_AXIS[i]], + (val) => {}, + ) - inputs.push(input) - // vecEl.appendChild(input) - } - // - // const widget = node.addDOMWidget(name, 'vector', inputEl, { - // getValue() { - // return JSON.stringify(widget._value) - // }, - // setValue(v) { - // widget._value = v - // }, - // afterResize(node, widget) { - // console.log('After resize', { that: this, node, widget }) - // }, - // }) - // - // console.log('prev callback', widget.callback) - // widget.callback = callback - // widget._value = value - // - // for (let i = 0; i < vector_size; i++) { - // const input = inputs[i] - // input.addEventListener('change', (event) => { - // widget._value[VECTOR_AXIS[i]] = Number.parseFloat(event.target.value) - // widget.callback?.(widget._value) - // node.graph._version++ - // node.setDirtyCanvas(true, true) - // }) - // } - // // document.body.append(inputEl) - // - // widget.inputEl = inputEl - // widget.vecEl = vecEl - // - // inputEl.addEventListener('input', () => { - // widget.callback?.(widget.value) - // }) - // - return { minWidth: 400, minHeight: 200, widget } + inputs.push(input) + // vecEl.appendChild(input) + } + // + // const widget = node.addDOMWidget(name, 'vector', inputEl, { + // getValue() { + // return JSON.stringify(widget._value) + // }, + // setValue(v) { + // widget._value = v + // }, + // afterResize(node, widget) { + // console.log('After resize', { that: this, node, widget }) + // }, + // }) + // + // console.log('prev callback', widget.callback) + // widget.callback = callback + // widget._value = value + // + // for (let i = 0; i < vector_size; i++) { + // const input = inputs[i] + // input.addEventListener('change', (event) => { + // widget._value[VECTOR_AXIS[i]] = Number.parseFloat(event.target.value) + // widget.callback?.(widget._value) + // node.graph._version++ + // node.setDirtyCanvas(true, true) + // }) + // } + // // document.body.append(inputEl) + // + // widget.inputEl = inputEl + // widget.vecEl = vecEl + // + // inputEl.addEventListener('input', () => { + // widget.callback?.(widget.value) + // }) + // + return { minWidth: 400, minHeight: 200, widget } } export function addVectorWidget(node, name, value, vector_size, callback, app) { - const inputEl = document.createElement('div') - const vecEl = document.createElement('div') + const inputEl = document.createElement('div') + const vecEl = document.createElement('div') - inputEl.className = 'comfy-vector-container' - vecEl.className = 'comfy-vector-input' - vecEl.id = 'vecEl' + inputEl.className = 'comfy-vector-container' + vecEl.className = 'comfy-vector-input' + vecEl.id = 'vecEl' - vecEl.style.display = 'flex' - vecEl.style.flexDirection = 'column' - inputEl.appendChild(vecEl) - const inputs = [] + vecEl.style.display = 'flex' + vecEl.style.flexDirection = 'column' + inputEl.appendChild(vecEl) + const inputs = [] - // - // for (let i = 0; i < vector_size; i++) { - // const input = document.createElement('input') - // input.type = 'number' - // input.value = value[VECTOR_AXIS[i]] - // inputs.push(input) - // vecEl.appendChild(input) - // } + // + // for (let i = 0; i < vector_size; i++) { + // const input = document.createElement('input') + // input.type = 'number' + // input.value = value[VECTOR_AXIS[i]] + // inputs.push(input) + // vecEl.appendChild(input) + // } - const widget = node.addDOMWidget(name, 'vector', inputEl, { - getValue() { - return JSON.stringify(widget._value) - }, - setValue(v) { - widget._value = v - }, - }) - const vec = new NumberInputWidget('vecEl', vector_size, true) - vec.setValue(...Object.values(value)) - vec.onChange = (value) => { - for (let i = 0; i < value.length; i++) { - const val = value[i] - widget._value[VECTOR_AXIS[i]] = Number.parseFloat(val) - } + const widget = node.addDOMWidget(name, 'vector', inputEl, { + getValue() { + return JSON.stringify(widget._value) + }, + setValue(v) { + widget._value = v + }, + }) + const vec = new NumberInputWidget('vecEl', vector_size, true) + vec.setValue(...Object.values(value)) + vec.onChange = (value) => { + for (let i = 0; i < value.length; i++) { + const val = value[i] + widget._value[VECTOR_AXIS[i]] = Number.parseFloat(val) + } - widget.callback?.(widget._value) - // widget._value[VECTOR_AXIS[index]] = Number.parseFloat(value) - } + widget.callback?.(widget._value) + // widget._value[VECTOR_AXIS[index]] = Number.parseFloat(value) + } - console.log('prev callback', widget.callback) - widget.callback = callback - widget._value = value + console.log('prev callback', widget.callback) + widget.callback = callback + widget._value = value - // for (let i = 0; i < vector_size; i++) { - // const input = inputs[i] - // input.addEventListener('change', (event) => { - // widget._value[VECTOR_AXIS[i]] = Number.parseFloat(event.target.value) - // widget.callback?.(widget._value) - // node.graph._version++ - // node.setDirtyCanvas(true, true) - // }) - // } + // for (let i = 0; i < vector_size; i++) { + // const input = inputs[i] + // input.addEventListener('change', (event) => { + // widget._value[VECTOR_AXIS[i]] = Number.parseFloat(event.target.value) + // widget.callback?.(widget._value) + // node.graph._version++ + // node.setDirtyCanvas(true, true) + // }) + // } - widget.inputEl = inputEl - widget.vecEl = vecEl - widget.vec = vec + widget.inputEl = inputEl + widget.vecEl = vecEl + widget.vec = vec - return { minWidth: 400, minHeight: 200 * vector_size, widget } + return { minWidth: 400, minHeight: 200 * vector_size, widget } } export const MtbWidgets = { - //TODO: complete this properly + //TODO: complete this properly - /** - * Creates a vector widget. - * @param {string} key - The key for the widget. - * @param {number[]} [val] - The initial value for the widget. - * @param {number} size - The size of the vector. - * @returns {VectorWidget} The vector widget. - */ - VECTOR: (key, val, size) => { - shared.infoLogger('Adding VECTOR widget', { key, val, size }) - /** @type {VectorWidget} */ - const widget = { - name: key, - type: `vector${size}`, - y: 0, - options: { default: Array.from({ length: size }, () => 0.0) }, - _value: val || Array.from({ length: size }, () => 0.0), - draw: function (ctx, node, width, widgetY, height) { - ctx.textAlign = 'left' - ctx.strokeStyle = outline_color - ctx.fillStyle = background_color - ctx.beginPath() - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) - else ctx.rect(margin, y, widget_width - margin * 2, H) - ctx.fill() - if (show_text) { - if (!w.disabled) ctx.stroke() - ctx.fillStyle = text_color - if (!w.disabled) { - ctx.beginPath() - ctx.moveTo(margin + 16, y + 5) - ctx.lineTo(margin + 6, y + H * 0.5) - ctx.lineTo(margin + 16, y + H - 5) - ctx.fill() - ctx.beginPath() - ctx.moveTo(widget_width - margin - 16, y + 5) - ctx.lineTo(widget_width - margin - 6, y + H * 0.5) - ctx.lineTo(widget_width - margin - 16, y + H - 5) - ctx.fill() - } - ctx.fillStyle = secondary_text_color - ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7) - ctx.fillStyle = text_color - ctx.textAlign = 'right' - if (w.type === 'number') { - ctx.fillText( - Number(w.value).toFixed( - w.options.precision !== undefined ? w.options.precision : 3, - ), - widget_width - margin * 2 - 20, - y + H * 0.7, - ) - } else { - let v = w.value - if (w.options.values) { - let values = w.options.values - if (values.constructor === Function) values = values() - if (values && values.constructor !== Array) v = values[w.value] - } - ctx.fillText(v, widget_width - margin * 2 - 20, y + H * 0.7) - } - } - }, - get value() { - return this._value - }, - set value(val) { - this._value = val - this.callback?.(this._value) - }, - } + /** + * Creates a vector widget. + * @param {string} key - The key for the widget. + * @param {number[]} [val] - The initial value for the widget. + * @param {number} size - The size of the vector. + * @returns {VectorWidget} The vector widget. + */ + VECTOR: (key, val, size) => { + shared.infoLogger('Adding VECTOR widget', { key, val, size }) + /** @type {VectorWidget} */ + const widget = { + name: key, + type: `vector${size}`, + y: 0, + options: { default: Array.from({ length: size }, () => 0.0) }, + _value: val || Array.from({ length: size }, () => 0.0), + draw: function (ctx, node, width, widgetY, height) { + ctx.textAlign = 'left' + ctx.strokeStyle = outline_color + ctx.fillStyle = background_color + ctx.beginPath() + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) + else ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.fill() + if (show_text) { + if (!w.disabled) ctx.stroke() + ctx.fillStyle = text_color + if (!w.disabled) { + ctx.beginPath() + ctx.moveTo(margin + 16, y + 5) + ctx.lineTo(margin + 6, y + H * 0.5) + ctx.lineTo(margin + 16, y + H - 5) + ctx.fill() + ctx.beginPath() + ctx.moveTo(widget_width - margin - 16, y + 5) + ctx.lineTo(widget_width - margin - 6, y + H * 0.5) + ctx.lineTo(widget_width - margin - 16, y + H - 5) + ctx.fill() + } + ctx.fillStyle = secondary_text_color + ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7) + ctx.fillStyle = text_color + ctx.textAlign = 'right' + if (w.type === 'number') { + ctx.fillText( + Number(w.value).toFixed( + w.options.precision !== undefined ? w.options.precision : 3, + ), + widget_width - margin * 2 - 20, + y + H * 0.7, + ) + } else { + let v = w.value + if (w.options.values) { + let values = w.options.values + if (values.constructor === Function) values = values() + if (values && values.constructor !== Array) v = values[w.value] + } + ctx.fillText(v, widget_width - margin * 2 - 20, y + H * 0.7) + } + } + }, + get value() { + return this._value + }, + set value(val) { + this._value = val + this.callback?.(this._value) + }, + } - return widget - }, - BBOX: (key, val) => { - /** @type {import("./types/litegraph").IWidget} */ - const widget = { - name: key, - type: 'BBOX', - // options: val, - y: 0, - value: val?.default || [0, 0, 0, 0], - options: {}, + return widget + }, + BBOX: (key, val) => { + /** @type {import("./types/litegraph").IWidget} */ + const widget = { + name: key, + type: 'BBOX', + // options: val, + y: 0, + value: val?.default || [0, 0, 0, 0], + options: {}, - draw: function (ctx, node, widget_width, widgetY, height) { - const hide = this.type !== 'BBOX' && app.canvas.ds.scale > 0.5 + draw: function (ctx, node, widget_width, widgetY, height) { + const hide = this.type !== 'BBOX' && app.canvas.ds.scale > 0.5 - const show_text = true - const outline_color = LiteGraph.WIDGET_OUTLINE_COLOR - const background_color = LiteGraph.WIDGET_BGCOLOR - const text_color = LiteGraph.WIDGET_TEXT_COLOR - const secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR - const H = LiteGraph.NODE_WIDGET_HEIGHT + const show_text = true + const outline_color = LiteGraph.WIDGET_OUTLINE_COLOR + const background_color = LiteGraph.WIDGET_BGCOLOR + const text_color = LiteGraph.WIDGET_TEXT_COLOR + const secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR + const H = LiteGraph.NODE_WIDGET_HEIGHT - let margin = 15 - let numWidgets = 4 // Number of stacked widgets + let margin = 15 + let numWidgets = 4 // Number of stacked widgets - if (hide) return + if (hide) return - for (let i = 0; i < numWidgets; i++) { - let currentY = widgetY + i * (H + margin) // Adjust Y position for each widget + for (let i = 0; i < numWidgets; i++) { + let currentY = widgetY + i * (H + margin) // Adjust Y position for each widget - ctx.textAlign = 'left' - ctx.strokeStyle = outline_color - ctx.fillStyle = background_color - ctx.beginPath() - if (show_text) - ctx.roundRect(margin, currentY, widget_width - margin * 2, H, [ - H * 0.5, - ]) - else ctx.rect(margin, currentY, widget_width - margin * 2, H) - ctx.fill() - if (show_text) { - if (!this.disabled) ctx.stroke() - ctx.fillStyle = text_color - if (!this.disabled) { - ctx.beginPath() - ctx.moveTo(margin + 16, currentY + 5) - ctx.lineTo(margin + 6, currentY + H * 0.5) - ctx.lineTo(margin + 16, currentY + H - 5) - ctx.fill() - ctx.beginPath() - ctx.moveTo(widget_width - margin - 16, currentY + 5) - ctx.lineTo(widget_width - margin - 6, currentY + H * 0.5) - ctx.lineTo(widget_width - margin - 16, currentY + H - 5) - ctx.fill() - } - ctx.fillStyle = secondary_text_color - ctx.fillText( - this.label || this.name, - margin * 2 + 5, - currentY + H * 0.7, - ) - ctx.fillStyle = text_color - ctx.textAlign = 'right' + ctx.textAlign = 'left' + ctx.strokeStyle = outline_color + ctx.fillStyle = background_color + ctx.beginPath() + if (show_text) + ctx.roundRect(margin, currentY, widget_width - margin * 2, H, [ + H * 0.5, + ]) + else ctx.rect(margin, currentY, widget_width - margin * 2, H) + ctx.fill() + if (show_text) { + if (!this.disabled) ctx.stroke() + ctx.fillStyle = text_color + if (!this.disabled) { + ctx.beginPath() + ctx.moveTo(margin + 16, currentY + 5) + ctx.lineTo(margin + 6, currentY + H * 0.5) + ctx.lineTo(margin + 16, currentY + H - 5) + ctx.fill() + ctx.beginPath() + ctx.moveTo(widget_width - margin - 16, currentY + 5) + ctx.lineTo(widget_width - margin - 6, currentY + H * 0.5) + ctx.lineTo(widget_width - margin - 16, currentY + H - 5) + ctx.fill() + } + ctx.fillStyle = secondary_text_color + ctx.fillText( + this.label || this.name, + margin * 2 + 5, + currentY + H * 0.7, + ) + ctx.fillStyle = text_color + ctx.textAlign = 'right' - ctx.fillText( - Number(this.value).toFixed( - this.options?.precision !== undefined - ? this.options.precision - : 3, - ), - widget_width - margin * 2 - 20, - currentY + H * 0.7, - ) - } - } - }, - mouse: function (event, pos, node) { - let old_value = this.value - let x = pos[0] - node.pos[0] - let y = pos[1] - node.pos[1] - let width = node.size[0] - let H = LiteGraph.NODE_WIDGET_HEIGHT - let margin = 5 - let numWidgets = 4 // Number of stacked widgets + ctx.fillText( + Number(this.value).toFixed( + this.options?.precision !== undefined + ? this.options.precision + : 3, + ), + widget_width - margin * 2 - 20, + currentY + H * 0.7, + ) + } + } + }, + mouse: function (event, pos, node) { + let old_value = this.value + let x = pos[0] - node.pos[0] + let y = pos[1] - node.pos[1] + let width = node.size[0] + let H = LiteGraph.NODE_WIDGET_HEIGHT + let margin = 5 + let numWidgets = 4 // Number of stacked widgets - for (let i = 0; i < numWidgets; i++) { - let currentY = y + i * (H + margin) // Adjust Y position for each widget + for (let i = 0; i < numWidgets; i++) { + let currentY = y + i * (H + margin) // Adjust Y position for each widget - if ( - event.type == LiteGraph.pointerevents_method + 'move' && - this.type == 'BBOX' - ) { - if (event.deltaX) - this.value += event.deltaX * 0.1 * (this.options?.step || 1) - if (this.options.min != null && this.value < this.options.min) { - this.value = this.options.min - } - if (this.options.max != null && this.value > this.options.max) { - this.value = this.options.max - } - } else if (event.type == LiteGraph.pointerevents_method + 'down') { - let values = this.options?.values - if (values && values.constructor === Function) { - values = this.options.values(w, node) - } - let values_list = null + if ( + event.type == LiteGraph.pointerevents_method + 'move' && + this.type == 'BBOX' + ) { + if (event.deltaX) + this.value += event.deltaX * 0.1 * (this.options?.step || 1) + if (this.options.min != null && this.value < this.options.min) { + this.value = this.options.min + } + if (this.options.max != null && this.value > this.options.max) { + this.value = this.options.max + } + } else if (event.type == LiteGraph.pointerevents_method + 'down') { + let values = this.options?.values + if (values && values.constructor === Function) { + values = this.options.values(w, node) + } + let values_list = null - let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 - if (this.type == 'BBOX') { - this.value += delta * 0.1 * (this.options.step || 1) - if (this.options.min != null && this.value < this.options.min) { - this.value = this.options.min - } - if (this.options.max != null && this.value > this.options.max) { - this.value = this.options.max - } - } else if (delta) { - //clicked in arrow, used for combos - let index = -1 - this.last_mouseclick = 0 //avoids dobl click event - if (values.constructor === Object) - index = values_list.indexOf(String(this.value)) + delta - else index = values_list.indexOf(this.value) + delta - if (index >= values_list.length) { - index = values_list.length - 1 - } - if (index < 0) { - index = 0 - } - if (values.constructor === Array) this.value = values[index] - else this.value = index - } - } //end mousedown - else if ( - event.type == LiteGraph.pointerevents_method + 'up' && - this.type == 'BBOX' - ) { - let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 - if (event.click_time < 200 && delta == 0) { - this.prompt( - 'Value', - this.value, - function (v) { - // check if v is a valid equation or a number - if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { - try { - //solve the equation if possible - v = eval(v) - } catch (e) {} - } - this.value = Number(v) - shared.inner_value_change(this, this.value, event) - }.bind(w), - event, - ) - } - } + let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 + if (this.type == 'BBOX') { + this.value += delta * 0.1 * (this.options.step || 1) + if (this.options.min != null && this.value < this.options.min) { + this.value = this.options.min + } + if (this.options.max != null && this.value > this.options.max) { + this.value = this.options.max + } + } else if (delta) { + //clicked in arrow, used for combos + let index = -1 + this.last_mouseclick = 0 //avoids dobl click event + if (values.constructor === Object) + index = values_list.indexOf(String(this.value)) + delta + else index = values_list.indexOf(this.value) + delta + if (index >= values_list.length) { + index = values_list.length - 1 + } + if (index < 0) { + index = 0 + } + if (values.constructor === Array) this.value = values[index] + else this.value = index + } + } //end mousedown + else if ( + event.type == LiteGraph.pointerevents_method + 'up' && + this.type == 'BBOX' + ) { + let delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0 + if (event.click_time < 200 && delta == 0) { + this.prompt( + 'Value', + this.value, + function (v) { + // check if v is a valid equation or a number + if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { + try { + //solve the equation if possible + v = eval(v) + } catch (e) {} + } + this.value = Number(v) + shared.inner_value_change(this, this.value, event) + }.bind(w), + event, + ) + } + } - if (old_value != this.value) - setTimeout( - function () { - shared.inner_value_change(this, this.value, event) - }.bind(this), - 20, - ) + if (old_value != this.value) + setTimeout( + function () { + shared.inner_value_change(this, this.value, event) + }.bind(this), + 20, + ) - app.canvas.setDirty(true) - } - }, - computeSize: function (width) { - return [width, LiteGraph.NODE_WIDGET_HEIGHT * 4] - }, - // onDrawBackground: function (ctx) { - // if (!this.flags.collapsed) return; - // this.inputEl.style.display = "block"; - // this.inputEl.style.top = this.graphcanvas.offsetTop + this.pos[1] + "px"; - // this.inputEl.style.left = this.graphcanvas.offsetLeft + this.pos[0] + "px"; - // }, - // onInputChange: function (e) { - // const property = e.target.dataset.property; - // const bbox = this.getInputData(0); - // if (!bbox) return; - // bbox[property] = parseFloat(e.target.value); - // this.setOutputData(0, bbox); - // } - } + app.canvas.setDirty(true) + } + }, + computeSize: function (width) { + return [width, LiteGraph.NODE_WIDGET_HEIGHT * 4] + }, + // onDrawBackground: function (ctx) { + // if (!this.flags.collapsed) return; + // this.inputEl.style.display = "block"; + // this.inputEl.style.top = this.graphcanvas.offsetTop + this.pos[1] + "px"; + // this.inputEl.style.left = this.graphcanvas.offsetLeft + this.pos[0] + "px"; + // }, + // onInputChange: function (e) { + // const property = e.target.dataset.property; + // const bbox = this.getInputData(0); + // if (!bbox) return; + // bbox[property] = parseFloat(e.target.value); + // this.setOutputData(0, bbox); + // } + } - widget.desc = 'Represents a Bounding Box with x, y, width, and height.' - return widget - }, + widget.desc = 'Represents a Bounding Box with x, y, width, and height.' + return widget + }, - COLOR: (key, val, compute = false) => { - /** @type {import("/types/litegraph").IWidget} */ - const widget = {} - widget.y = 0 - widget.name = key - widget.type = 'COLOR' - widget.options = { default: '#ff0000' } - widget.value = val || '#ff0000' - widget.draw = function (ctx, node, widgetWidth, widgetY, height) { - const hide = this.type !== 'COLOR' && app.canvas.ds.scale > 0.5 - if (hide) { - return - } - const border = 3 - ctx.fillStyle = '#000' - ctx.fillRect(0, widgetY, widgetWidth, height) - ctx.fillStyle = this.value - ctx.fillRect( - border, - widgetY + border, - widgetWidth - border * 2, - height - border * 2, - ) - const color = parseCss(this.value.default || this.value) - if (!color) { - return - } - ctx.fillStyle = shared.isColorBright(color.values, 125) ? '#000' : '#fff' + COLOR: (key, val, compute = false) => { + /** @type {import("/types/litegraph").IWidget} */ + const widget = {} + widget.y = 0 + widget.name = key + widget.type = 'COLOR' + widget.options = { default: '#ff0000' } + widget.value = val || '#ff0000' + widget.draw = function (ctx, node, widgetWidth, widgetY, height) { + const hide = this.type !== 'COLOR' && app.canvas.ds.scale > 0.5 + if (hide) { + return + } + const border = 3 + ctx.fillStyle = '#000' + ctx.fillRect(0, widgetY, widgetWidth, height) + ctx.fillStyle = this.value + ctx.fillRect( + border, + widgetY + border, + widgetWidth - border * 2, + height - border * 2, + ) + const color = parseCss(this.value.default || this.value) + if (!color) { + return + } + ctx.fillStyle = shared.isColorBright(color.values, 125) ? '#000' : '#fff' - ctx.font = '14px Arial' - ctx.textAlign = 'center' - ctx.fillText(this.name, widgetWidth * 0.5, widgetY + 14) - } - widget.mouse = function (e, pos, node) { - if (e.type === 'pointerdown') { - const widgets = node.widgets.filter((w) => w.type === 'COLOR') + ctx.font = '14px Arial' + ctx.textAlign = 'center' + ctx.fillText(this.name, widgetWidth * 0.5, widgetY + 14) + } + widget.mouse = function (e, pos, node) { + if (e.type === 'pointerdown') { + const widgets = node.widgets.filter((w) => w.type === 'COLOR') - for (const w of widgets) { - // color picker - const rect = [w.last_y, w.last_y + 32] - if (pos[1] > rect[0] && pos[1] < rect[1]) { - const picker = document.createElement('input') - picker.type = 'color' - picker.value = this.value + for (const w of widgets) { + // color picker + const rect = [w.last_y, w.last_y + 32] + if (pos[1] > rect[0] && pos[1] < rect[1]) { + const picker = document.createElement('input') + picker.type = 'color' + picker.value = this.value - picker.style.position = 'absolute' - picker.style.left = '999999px' //(window.innerWidth / 2) + "px"; - picker.style.top = '999999px' //(window.innerHeight / 2) + "px"; + picker.style.position = 'absolute' + picker.style.left = '999999px' //(window.innerWidth / 2) + "px"; + picker.style.top = '999999px' //(window.innerHeight / 2) + "px"; - document.body.appendChild(picker) + document.body.appendChild(picker) - picker.addEventListener('change', () => { - this.value = picker.value - this.callback?.(this.value) - node.graph._version++ - node.setDirtyCanvas(true, true) - picker.remove() - }) + picker.addEventListener('change', () => { + this.value = picker.value + this.callback?.(this.value) + node.graph._version++ + node.setDirtyCanvas(true, true) + picker.remove() + }) - picker.click() - } - } - } - } - widget.computeSize = function (width) { - return [width, 32] - } + picker.click() + } + } + } + } + widget.computeSize = function (width) { + return [width, 32] + } - return widget - }, + return widget + }, - DEBUG_IMG: (name, val) => { - const w = { - name, - type: 'image', - value: val, - draw: function (ctx, node, widgetWidth, widgetY, height) { - const [cw, ch] = this.computeSize(widgetWidth) - shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch) - }, - computeSize: function (width) { - const ratio = this.inputRatio || 1 - if (width) { - return [width, width / ratio + 4] - } - return [128, 128] - }, - onRemoved: function () { - if (this.inputEl) { - this.inputEl.remove() - } - }, - } + DEBUG_IMG: (name, val) => { + const w = { + name, + type: 'image', + value: val, + draw: function (ctx, node, widgetWidth, widgetY, height) { + const [cw, ch] = this.computeSize(widgetWidth) + shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch) + }, + computeSize: function (width) { + const ratio = this.inputRatio || 1 + if (width) { + return [width, width / ratio + 4] + } + return [128, 128] + }, + onRemoved: function () { + if (this.inputEl) { + this.inputEl.remove() + } + }, + } - w.inputEl = document.createElement('img') - w.inputEl.src = w.value - w.inputEl.onload = function () { - w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight - } - document.body.appendChild(w.inputEl) - return w - }, - DEBUG_STRING: (name, val) => { - const fontSize = 16 - const w = { - name, - type: 'debug_text', + w.inputEl = document.createElement('img') + w.inputEl.src = w.value + w.inputEl.onload = function () { + w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight + } + document.body.appendChild(w.inputEl) + return w + }, + DEBUG_STRING: (name, val) => { + const fontSize = 16 + const w = { + name, + type: 'debug_text', - draw: function (ctx, node, widgetWidth, widgetY, height) { - // const [cw, ch] = this.computeSize(widgetWidth) - shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, height) - }, - computeSize(width) { - if (!this.value) { - return [32, 32] - } - if (!width) { - console.debug(`No width ${this.parent.size}`) - } - let dimensions - withFont(app.ctx, `${fontSize}px monospace`, () => { - dimensions = calculateTextDimensions(app.ctx, this.value, width) - }) - const widgetWidth = Math.max( - width || this.width || 32, - dimensions.maxLineWidth, - ) - const widgetHeight = dimensions.textHeight * 1.5 - return [widgetWidth, widgetHeight] - }, - onRemoved: function () { - if (this.inputEl) { - this.inputEl.remove() - } - }, - get value() { - return this.inputEl.innerHTML - }, - set value(val) { - this.inputEl.innerHTML = val - this.parent?.setSize?.(this.parent?.computeSize()) - }, - } + draw: function (ctx, node, widgetWidth, widgetY, height) { + // const [cw, ch] = this.computeSize(widgetWidth) + shared.offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, height) + }, + computeSize(width) { + if (!this.value) { + return [32, 32] + } + if (!width) { + console.debug(`No width ${this.parent.size}`) + } + let dimensions + withFont(app.ctx, `${fontSize}px monospace`, () => { + dimensions = calculateTextDimensions(app.ctx, this.value, width) + }) + const widgetWidth = Math.max( + width || this.width || 32, + dimensions.maxLineWidth, + ) + const widgetHeight = dimensions.textHeight * 1.5 + return [widgetWidth, widgetHeight] + }, + onRemoved: function () { + if (this.inputEl) { + this.inputEl.remove() + } + }, + get value() { + return this.inputEl.innerHTML + }, + set value(val) { + this.inputEl.innerHTML = val + this.parent?.setSize?.(this.parent?.computeSize()) + }, + } - w.inputEl = document.createElement('p') - w.inputEl.style = ` + w.inputEl = document.createElement('p') + w.inputEl.style = ` text-align: center; font-size: ${fontSize}px; color: var(--input-text); line-height: 1em; font-family: monospace; ` - w.value = val - document.body.appendChild(w.inputEl) + w.value = val + document.body.appendChild(w.inputEl) - return w - }, + return w + }, } /** * @returns {import("./types/comfy").ComfyExtension} extension */ const mtb_widgets = { - name: 'mtb.widgets', + name: 'mtb.widgets', - init: async () => { - infoLogger('Registering mtb.widgets') - try { - const res = await api.fetchApi('/mtb/debug') - const msg = await res.json() - if (!window.MTB) { - window.MTB = {} - } - window.MTB.DEBUG = msg.enabled - } catch (e) { - console.error('Error:', e) - } - }, + init: async () => { + infoLogger('Registering mtb.widgets') + try { + const res = await api.fetchApi('/mtb/debug') + const msg = await res.json() + if (!window.MTB) { + window.MTB = {} + } + window.MTB.DEBUG = msg.enabled + } catch (e) { + console.error('Error:', e) + } + }, - setup: () => { - app.ui.settings.addSetting({ - id: 'mtb.Debug.enabled', - name: '[⚡mtb] Enable Debug (py and js)', - type: 'boolean', - defaultValue: false, + setup: () => { + app.ui.settings.addSetting({ + id: 'mtb.Debug.enabled', + name: '[⚡mtb] Enable Debug (py and js)', + type: 'boolean', + defaultValue: false, - tooltip: - 'This will enable debug messages in the console and in the python console respectively', - attrs: { - style: { - fontFamily: 'monospace', - }, - }, - async onChange(value) { - if (!window.MTB) { - window.MTB = {} - } - window.MTB.DEBUG = value - if (value) { - infoLogger('Enabled DEBUG mode') - } + tooltip: + 'This will enable debug messages in the console and in the python console respectively', + attrs: { + style: { + fontFamily: 'monospace', + }, + }, + async onChange(value) { + if (!window.MTB) { + window.MTB = {} + } + window.MTB.DEBUG = value + if (value) { + infoLogger('Enabled DEBUG mode') + } - await api - .fetchApi('/mtb/debug', { - method: 'POST', - body: JSON.stringify({ - enabled: value, - }), - }) - .then((_response) => {}) - .catch((error) => { - console.error('Error:', error) - }) - }, - }) - }, + await api + .fetchApi('/mtb/debug', { + method: 'POST', + body: JSON.stringify({ + enabled: value, + }), + }) + .then((_response) => {}) + .catch((error) => { + console.error('Error:', error) + }) + }, + }) + }, - getCustomWidgets: () => { - return { - // BOOL: (node, inputName, inputData, _app) => { - // console.debug('Registering bool') - // - // return { - // widget: node.addCustomWidget( - // MtbWidgets.BOOL(inputName, inputData[1]?.default || false), - // ), - // minWidth: 150, - // minHeight: 30, - // } - // }, + getCustomWidgets: () => { + return { + // BOOL: (node, inputName, inputData, _app) => { + // console.debug('Registering bool') + // + // return { + // widget: node.addCustomWidget( + // MtbWidgets.BOOL(inputName, inputData[1]?.default || false), + // ), + // minWidth: 150, + // minHeight: 30, + // } + // }, - COLOR: (node, inputName, inputData, _app) => { - console.debug('Registering color') - return { - widget: node.addCustomWidget( - MtbWidgets.COLOR(inputName, inputData[1]?.default || '#ff0000'), - ), - minWidth: 150, - minHeight: 30, - } - }, - // BBOX: (node, inputName, inputData, app) => { - // console.debug("Registering bbox") - // return { - // widget: node.addCustomWidget(MtbWidgets.BBOX(inputName, inputData[1]?.default || [0, 0, 0, 0])), - // minWidth: 150, - // minHeight: 30, - // } + COLOR: (node, inputName, inputData, _app) => { + console.debug('Registering color') + return { + widget: node.addCustomWidget( + MtbWidgets.COLOR(inputName, inputData[1]?.default || '#ff0000'), + ), + minWidth: 150, + minHeight: 30, + } + }, + // BBOX: (node, inputName, inputData, app) => { + // console.debug("Registering bbox") + // return { + // widget: node.addCustomWidget(MtbWidgets.BBOX(inputName, inputData[1]?.default || [0, 0, 0, 0])), + // minWidth: 150, + // minHeight: 30, + // } - // } - } - }, - /** - * @param {NodeType} nodeType - * @param {NodeData} nodeData - * @param {import("./types/comfy").App} app - */ - async beforeRegisterNodeDef(nodeType, nodeData, app) { - // const rinputs = nodeData.input?.required + // } + } + }, + /** + * @param {NodeType} nodeType + * @param {NodeData} nodeData + * @param {import("./types/comfy").App} app + */ + async beforeRegisterNodeDef(nodeType, nodeData, app) { + // const rinputs = nodeData.input?.required - let has_custom = false - if (nodeData.input && nodeData.input.required) { - for (const i of Object.keys(nodeData.input.required)) { - const input_type = nodeData.input.required[i][0] + let has_custom = false + if (nodeData.input && nodeData.input.required) { + for (const i of Object.keys(nodeData.input.required)) { + const input_type = nodeData.input.required[i][0] - if (newTypes.includes(input_type)) { - has_custom = true - break - } - } - } - if (has_custom) { - //- Add widgets on node creation - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : undefined - this.serialize_widgets = true - this.setSize?.(this.computeSize()) + if (newTypes.includes(input_type)) { + has_custom = true + break + } + } + } + if (has_custom) { + //- Add widgets on node creation + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined + this.serialize_widgets = true + this.setSize?.(this.computeSize()) - this.onRemoved = function () { - // When removing this node we need to remove the input from the DOM - shared.cleanupNode(this) - } - return r - } + this.onRemoved = function () { + // When removing this node we need to remove the input from the DOM + shared.cleanupNode(this) + } + return r + } - //- Extra menus - const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions - nodeType.prototype.getExtraMenuOptions = function (_, options) { - const r = origGetExtraMenuOptions - ? origGetExtraMenuOptions.apply(this, arguments) - : undefined - if (this.widgets) { - let toInput = [] - let toWidget = [] - for (const w of this.widgets) { - if (w.type === shared.CONVERTED_TYPE) { - //- This is already handled by widgetinputs.js - // toWidget.push({ - // content: `Convert ${w.name} to widget`, - // callback: () => shared.convertToWidget(this, w), - // }); - } else if (newTypes.includes(w.type)) { - const config = nodeData?.input?.required[w.name] || - nodeData?.input?.optional?.[w.name] || [w.type, w.options || {}] + //- Extra menus + const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions + nodeType.prototype.getExtraMenuOptions = function (_, options) { + const r = origGetExtraMenuOptions + ? origGetExtraMenuOptions.apply(this, arguments) + : undefined + if (this.widgets) { + let toInput = [] + let toWidget = [] + for (const w of this.widgets) { + if (w.type === shared.CONVERTED_TYPE) { + //- This is already handled by widgetinputs.js + // toWidget.push({ + // content: `Convert ${w.name} to widget`, + // callback: () => shared.convertToWidget(this, w), + // }); + } else if (newTypes.includes(w.type)) { + const config = nodeData?.input?.required[w.name] || + nodeData?.input?.optional?.[w.name] || [w.type, w.options || {}] - toInput.push({ - content: `Convert ${w.name} to input`, - callback: () => shared.convertToInput(this, w, config), - }) - } - } - if (toInput.length) { - options.push(...toInput, null) - } + toInput.push({ + content: `Convert ${w.name} to input`, + callback: () => shared.convertToInput(this, w, config), + }) + } + } + if (toInput.length) { + options.push(...toInput, null) + } - if (toWidget.length) { - options.push(...toWidget, null) - } - } + if (toWidget.length) { + options.push(...toWidget, null) + } + } - return r - } - } + return r + } + } - if (!nodeData.name.endsWith('(mtb)')) { - return - } - // console.log('MTB Node', { description: nodeData.description, nodeType }) + if (!nodeData.name.endsWith('(mtb)')) { + return + } + // console.log('MTB Node', { description: nodeData.description, nodeType }) - shared.addDocumentation(nodeData, nodeType) + shared.addDocumentation(nodeData, nodeType) - const deprecation = deprecated_nodes[nodeData.name.replace(' (mtb)', '')] + const deprecation = deprecated_nodes[nodeData.name.replace(' (mtb)', '')] - if (deprecation) { - shared.addDeprecation(nodeType, deprecation) - } - //- Extending Python Nodes - switch (nodeData.name) { - //TODO: remove this non sense - case 'Get Batch From History (mtb)': - case 'Get Batch From History V2 (mtb)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated ? onNodeCreated.apply(this, []) : undefined - const internal_count = this.widgets.find( - (w) => w.name === 'internal_count', - ) - shared.hideWidgetForGood(this, internal_count) - internal_count.afterQueued = function () { - this.value++ - } + if (deprecation) { + shared.addDeprecation(nodeType, deprecation) + } + //- Extending Python Nodes + switch (nodeData.name) { + //TODO: remove this non sense + case 'Get Batch From History (mtb)': + case 'Get Batch From History V2 (mtb)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated ? onNodeCreated.apply(this, []) : undefined + const internal_count = this.widgets.find( + (w) => w.name === 'internal_count', + ) + shared.hideWidgetForGood(this, internal_count) + internal_count.afterQueued = function () { + this.value++ + } - return r - } + return r + } - const onExecuted = nodeType.prototype.onExecuted - nodeType.prototype.onExecuted = function (message) { - const r = onExecuted ? onExecuted.apply(this, message) : undefined - return r - } + const onExecuted = nodeType.prototype.onExecuted + nodeType.prototype.onExecuted = function (message) { + const r = onExecuted ? onExecuted.apply(this, message) : undefined + return r + } - break - } - case 'Save Gif (mtb)': - case 'Save Animated Image (mtb)': { - const onExecuted = nodeType.prototype.onExecuted - nodeType.prototype.onExecuted = function (message) { - const prefix = 'anything_' - const r = onExecuted ? onExecuted.apply(this, message) : undefined + break + } + case 'Save Gif (mtb)': + case 'Save Animated Image (mtb)': { + const onExecuted = nodeType.prototype.onExecuted + nodeType.prototype.onExecuted = function (message) { + const prefix = 'anything_' + const r = onExecuted ? onExecuted.apply(this, message) : undefined - if (this.widgets) { - const pos = this.widgets.findIndex((w) => w.name === `${prefix}_0`) - if (pos !== -1) { - for (let i = pos; i < this.widgets.length; i++) { - this.widgets[i].onRemoved?.() - } - this.widgets.length = pos - } + if (this.widgets) { + const pos = this.widgets.findIndex((w) => w.name === `${prefix}_0`) + if (pos !== -1) { + for (let i = pos; i < this.widgets.length; i++) { + this.widgets[i].onRemoved?.() + } + this.widgets.length = pos + } - let imgURLs = [] - if (message) { - if (message.gif) { - imgURLs = imgURLs.concat( - message.gif.map((params) => { - return api.apiURL( - '/view?' + new URLSearchParams(params).toString(), - ) - }), - ) - } - if (message.apng) { - imgURLs = imgURLs.concat( - message.apng.map((params) => { - return api.apiURL( - '/view?' + new URLSearchParams(params).toString(), - ) - }), - ) - } - let i = 0 - for (const img of imgURLs) { - const w = this.addCustomWidget( - MtbWidgets.DEBUG_IMG(`${prefix}_${i}`, img), - ) - w.parent = this - i++ - } - } - const onRemoved = this.onRemoved - this.onRemoved = () => { - shared.cleanupNode(this) - return onRemoved?.() - } - } - this.setSize?.(this.computeSize()) - return r - } + let imgURLs = [] + if (message) { + if (message.gif) { + imgURLs = imgURLs.concat( + message.gif.map((params) => { + return api.apiURL( + '/view?' + new URLSearchParams(params).toString(), + ) + }), + ) + } + if (message.apng) { + imgURLs = imgURLs.concat( + message.apng.map((params) => { + return api.apiURL( + '/view?' + new URLSearchParams(params).toString(), + ) + }), + ) + } + let i = 0 + for (const img of imgURLs) { + const w = this.addCustomWidget( + MtbWidgets.DEBUG_IMG(`${prefix}_${i}`, img), + ) + w.parent = this + i++ + } + } + const onRemoved = this.onRemoved + this.onRemoved = () => { + shared.cleanupNode(this) + return onRemoved?.() + } + } + this.setSize?.(this.computeSize()) + return r + } - break - } - case 'Animation Builder (mtb)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : undefined + break + } + case 'Animation Builder (mtb)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined - this.changeMode(LiteGraph.ALWAYS) + this.changeMode(LiteGraph.ALWAYS) - const raw_iteration = this.widgets.find( - (w) => w.name === 'raw_iteration', - ) - const raw_loop = this.widgets.find((w) => w.name === 'raw_loop') + const raw_iteration = this.widgets.find( + (w) => w.name === 'raw_iteration', + ) + const raw_loop = this.widgets.find((w) => w.name === 'raw_loop') - const total_frames = this.widgets.find( - (w) => w.name === 'total_frames', - ) - const loop_count = this.widgets.find((w) => w.name === 'loop_count') + const total_frames = this.widgets.find( + (w) => w.name === 'total_frames', + ) + const loop_count = this.widgets.find((w) => w.name === 'loop_count') - shared.hideWidgetForGood(this, raw_iteration) - shared.hideWidgetForGood(this, raw_loop) + shared.hideWidgetForGood(this, raw_iteration) + shared.hideWidgetForGood(this, raw_loop) - raw_iteration._value = 0 + raw_iteration._value = 0 - const value_preview = this.addCustomWidget( - MtbWidgets['DEBUG_STRING']('value_preview', 'Idle'), - ) - value_preview.parent = this + const value_preview = this.addCustomWidget( + MtbWidgets['DEBUG_STRING']('value_preview', 'Idle'), + ) + value_preview.parent = this - const loop_preview = this.addCustomWidget( - MtbWidgets['DEBUG_STRING']('loop_preview', 'Iteration: Idle'), - ) - loop_preview.parent = this + const loop_preview = this.addCustomWidget( + MtbWidgets['DEBUG_STRING']('loop_preview', 'Iteration: Idle'), + ) + loop_preview.parent = this - const onReset = () => { - raw_iteration.value = 0 - raw_loop.value = 0 + const onReset = () => { + raw_iteration.value = 0 + raw_loop.value = 0 - value_preview.value = 'Idle' - loop_preview.value = 'Iteration: Idle' + value_preview.value = 'Idle' + loop_preview.value = 'Iteration: Idle' - app.canvas.setDirty(true) - } + app.canvas.setDirty(true) + } - // reset button - this.addWidget('button', `Reset`, 'reset', onReset) + // reset button + this.addWidget('button', `Reset`, 'reset', onReset) - // run button - this.addWidget('button', `Queue`, 'queue', () => { - onReset() // this could maybe be a setting or checkbox - app.queuePrompt(0, total_frames.value * loop_count.value) - window.MTB?.notify?.( - `Started a queue of ${total_frames.value} frames (for ${ - loop_count.value - } loop, so ${total_frames.value * loop_count.value})`, - 5000, - ) - }) + // run button + this.addWidget('button', `Queue`, 'queue', () => { + onReset() // this could maybe be a setting or checkbox + app.queuePrompt(0, total_frames.value * loop_count.value) + window.MTB?.notify?.( + `Started a queue of ${total_frames.value} frames (for ${ + loop_count.value + } loop, so ${total_frames.value * loop_count.value})`, + 5000, + ) + }) - this.onRemoved = () => { - shared.cleanupNode(this) - app.canvas.setDirty(true) - } + this.onRemoved = () => { + shared.cleanupNode(this) + app.canvas.setDirty(true) + } - raw_iteration.afterQueued = function () { - this.value++ - raw_loop.value = Math.floor(this.value / total_frames.value) + raw_iteration.afterQueued = function () { + this.value++ + raw_loop.value = Math.floor(this.value / total_frames.value) - value_preview.value = `frame: ${ - raw_iteration.value % total_frames.value - } / ${total_frames.value - 1}` + value_preview.value = `frame: ${ + raw_iteration.value % total_frames.value + } / ${total_frames.value - 1}` - if (raw_loop.value + 1 > loop_count.value) { - loop_preview.value = 'Done 😎!' - } else { - loop_preview.value = `current loop: ${raw_loop.value + 1}/${ - loop_count.value - }` - } - } + if (raw_loop.value + 1 > loop_count.value) { + loop_preview.value = 'Done 😎!' + } else { + loop_preview.value = `current loop: ${raw_loop.value + 1}/${ + loop_count.value + }` + } + } - return r - } + return r + } - break - } - case 'Interpolate Clip Sequential (mtb)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : undefined - const addReplacement = () => { - const input = this.addInput( - `replacement_${this.widgets.length}`, - 'STRING', - '', - ) - console.log(input) - this.addWidget('STRING', `replacement_${this.widgets.length}`, '') - } - //- add - this.addWidget('button', '+', 'add', function (value, widget, node) { - console.log('Button clicked', value, widget, node) - addReplacement() - }) - //- remove - this.addWidget( - 'button', - '-', - 'remove', - function (value, widget, node) { - console.log(`Button clicked: ${value}`, widget, node) - }, - ) + break + } + case 'Interpolate Clip Sequential (mtb)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined + const addReplacement = () => { + const input = this.addInput( + `replacement_${this.widgets.length}`, + 'STRING', + '', + ) + console.log(input) + this.addWidget('STRING', `replacement_${this.widgets.length}`, '') + } + //- add + this.addWidget('button', '+', 'add', function (value, widget, node) { + console.log('Button clicked', value, widget, node) + addReplacement() + }) + //- remove + this.addWidget( + 'button', + '-', + 'remove', + function (value, widget, node) { + console.log(`Button clicked: ${value}`, widget, node) + }, + ) - return r - } - break - } - case 'Styles Loader (mtb)': { - const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions - nodeType.prototype.getExtraMenuOptions = function (_, options) { - const r = origGetExtraMenuOptions - ? origGetExtraMenuOptions.apply(this, arguments) - : undefined + return r + } + break + } + case 'Styles Loader (mtb)': { + const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions + nodeType.prototype.getExtraMenuOptions = function (_, options) { + const r = origGetExtraMenuOptions + ? origGetExtraMenuOptions.apply(this, arguments) + : undefined - const getStyle = async (node) => { - try { - const getStyles = await api.fetchApi('/mtb/actions', { - method: 'POST', - body: JSON.stringify({ - name: 'getStyles', - args: - node.widgets && node.widgets[0].value - ? node.widgets[0].value - : '', - }), - }) + const getStyle = async (node) => { + try { + const getStyles = await api.fetchApi('/mtb/actions', { + method: 'POST', + body: JSON.stringify({ + name: 'getStyles', + args: + node.widgets && node.widgets[0].value + ? node.widgets[0].value + : '', + }), + }) - const output = await getStyles.json() - return output?.result - } catch (e) { - console.error(e) - } - } - const extracters = [ - { - content: 'Extract Positive to Text node', - callback: async () => { - const style = await getStyle(this) - if (style && style.length >= 1) { - if (style[0]) { - window.MTB?.notify?.( - `Extracted positive from ${this.widgets[0].value}`, - ) - // const tn = LiteGraph.createNode('Text box') - const tn = LiteGraph.createNode('CLIPTextEncode') - app.graph.add(tn) - tn.title = `${this.widgets[0].value} (Positive)` - tn.widgets[0].value = style[0] - } else { - window.MTB?.notify?.( - `No positive to extract for ${this.widgets[0].value}`, - ) - } - } - }, - }, - { - content: 'Extract Negative to Text node', - callback: async () => { - const style = await getStyle(this) - if (style && style.length >= 2) { - if (style[1]) { - window.MTB?.notify?.( - `Extracted negative from ${this.widgets[0].value}`, - ) - const tn = LiteGraph.createNode('CLIPTextEncode') - app.graph.add(tn) - tn.title = `${this.widgets[0].value} (Negative)` - tn.widgets[0].value = style[1] - } else { - window.MTB.notify( - `No negative to extract for ${this.widgets[0].value}`, - ) - } - } - }, - }, - ] - options.push(...extracters) - } + const output = await getStyles.json() + return output?.result + } catch (e) { + console.error(e) + } + } + const extracters = [ + { + content: 'Extract Positive to Text node', + callback: async () => { + const style = await getStyle(this) + if (style && style.length >= 1) { + if (style[0]) { + window.MTB?.notify?.( + `Extracted positive from ${this.widgets[0].value}`, + ) + // const tn = LiteGraph.createNode('Text box') + const tn = LiteGraph.createNode('CLIPTextEncode') + app.graph.add(tn) + tn.title = `${this.widgets[0].value} (Positive)` + tn.widgets[0].value = style[0] + } else { + window.MTB?.notify?.( + `No positive to extract for ${this.widgets[0].value}`, + ) + } + } + }, + }, + { + content: 'Extract Negative to Text node', + callback: async () => { + const style = await getStyle(this) + if (style && style.length >= 2) { + if (style[1]) { + window.MTB?.notify?.( + `Extracted negative from ${this.widgets[0].value}`, + ) + const tn = LiteGraph.createNode('CLIPTextEncode') + app.graph.add(tn) + tn.title = `${this.widgets[0].value} (Negative)` + tn.widgets[0].value = style[1] + } else { + window.MTB.notify( + `No negative to extract for ${this.widgets[0].value}`, + ) + } + } + }, + }, + ] + options.push(...extracters) + } - break - } + break + } - //NOTE: dynamic nodes - case 'Apply Text Template (mtb)': { - shared.setupDynamicConnections(nodeType, 'var', '*') - break - } - case 'Save Data Bundle (mtb)': { - shared.setupDynamicConnections(nodeType, 'data', '*') // [MASK,IMAGE] - break - } - case 'Add To Playlist (mtb)': { - shared.setupDynamicConnections(nodeType, 'video', 'VIDEO') - break - } - case 'Psd Save (mtb)': { - shared.setupDynamicConnections(nodeType, 'input_', 'PSDLAYER') - break - } - // case 'Text Encode Frames (mtb)' : { - // shared.setupDynamicConnections(nodeType, 'input_', 'IMAGE') - // break - // } - case 'Stack Images (mtb)': - case 'Concat Images (mtb)': { - shared.setupDynamicConnections(nodeType, 'image', 'IMAGE') - break - } - case 'Audio Sequence (mtb)': - case 'Audio Stack (mtb)': { - shared.setupDynamicConnections(nodeType, 'audio', 'AUDIO') - break - } - case 'Batch Float Assemble (mtb)': - case 'Batch Float Math (mtb)': - case 'Plot Batch Float (mtb)': { - shared.setupDynamicConnections(nodeType, 'floats', 'FLOATS') - break - } - case 'Batch Merge (mtb)': { - shared.setupDynamicConnections(nodeType, 'batches', 'IMAGE') + //NOTE: dynamic nodes + case 'Apply Text Template (mtb)': { + shared.setupDynamicConnections(nodeType, 'var', '*') + break + } + case 'Save Data Bundle (mtb)': { + shared.setupDynamicConnections(nodeType, 'data', '*') // [MASK,IMAGE] + break + } + case 'Add To Playlist (mtb)': { + shared.setupDynamicConnections(nodeType, 'video', 'VIDEO') + break + } + case 'Psd Save (mtb)': { + shared.setupDynamicConnections(nodeType, 'input_', 'PSDLAYER') + break + } + // case 'Text Encode Frames (mtb)' : { + // shared.setupDynamicConnections(nodeType, 'input_', 'IMAGE') + // break + // } + case 'Stack Images (mtb)': + case 'Concat Images (mtb)': { + shared.setupDynamicConnections(nodeType, 'image', 'IMAGE') + break + } + case 'Audio Sequence (mtb)': + case 'Audio Stack (mtb)': { + shared.setupDynamicConnections(nodeType, 'audio', 'AUDIO') + break + } + case 'Batch Float Assemble (mtb)': + case 'Batch Float Math (mtb)': + case 'Plot Batch Float (mtb)': { + shared.setupDynamicConnections(nodeType, 'floats', 'FLOATS') + break + } + case 'Batch Merge (mtb)': { + shared.setupDynamicConnections(nodeType, 'batches', 'IMAGE') - break - } - // TODO: remove this, recommend pythongoss's version that is much better - case 'Math Expression (mtb)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : undefined - this.addInput(`x`, '*') - return r - } + break + } + // TODO: remove this, recommend pythongoss's version that is much better + case 'Math Expression (mtb)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined + this.addInput(`x`, '*') + return r + } - const onConnectionsChange = nodeType.prototype.onConnectionsChange - nodeType.prototype.onConnectionsChange = function ( - type, - index, - connected, - link_info, - ) { - const r = onConnectionsChange - ? onConnectionsChange.apply(this, arguments) - : undefined - shared.dynamic_connection(this, index, connected, 'var_', '*', { - nameArray: ['x', 'y', 'z'], - }) + const onConnectionsChange = nodeType.prototype.onConnectionsChange + nodeType.prototype.onConnectionsChange = function ( + type, + index, + connected, + link_info, + ) { + const r = onConnectionsChange + ? onConnectionsChange.apply(this, arguments) + : undefined + shared.dynamic_connection(this, index, connected, 'var_', '*', { + nameArray: ['x', 'y', 'z'], + }) - //- infer type - if (link_info) { - const fromNode = this.graph._nodes.find( - (otherNode) => otherNode.id == link_info.origin_id, - ) - const type = fromNode.outputs[link_info.origin_slot].type - this.inputs[index].type = type - // this.inputs[index].label = type.toLowerCase() - } - //- restore dynamic input - if (!connected) { - this.inputs[index].type = '*' - this.inputs[index].label = `number_${index + 1}` - } - } + //- infer type + if (link_info) { + const fromNode = this.graph._nodes.find( + (otherNode) => otherNode.id == link_info.origin_id, + ) + const type = fromNode.outputs[link_info.origin_slot].type + this.inputs[index].type = type + // this.inputs[index].label = type.toLowerCase() + } + //- restore dynamic input + if (!connected) { + this.inputs[index].type = '*' + this.inputs[index].label = `number_${index + 1}` + } + } - break - } + break + } - case 'Batch Shape (mtb)': - case 'Mask To Image (mtb)': - case 'Text To Image (mtb)': { - shared.addMenuHandler(nodeType, function (_app, options) { - /** @type {ContextMenuItem} */ - const item = { - content: 'swap colors', - title: 'Swap BG/FG Color ⚡', - callback: (_menuItem) => { - const color_w = this.widgets.find((w) => w.name === 'color') - const bg_w = this.widgets.find( - (w) => w.name === 'background' || w.name === 'bg_color', - ) + case 'Batch Shape (mtb)': + case 'Mask To Image (mtb)': + case 'Text To Image (mtb)': { + shared.addMenuHandler(nodeType, function (_app, options) { + /** @type {ContextMenuItem} */ + const item = { + content: 'swap colors', + title: 'Swap BG/FG Color ⚡', + callback: (_menuItem) => { + const color_w = this.widgets.find((w) => w.name === 'color') + const bg_w = this.widgets.find( + (w) => w.name === 'background' || w.name === 'bg_color', + ) - const color = color_w.value - const bg = bg_w.value + const color = color_w.value + const bg = bg_w.value - color_w.value = bg - bg_w.value = color - }, - } + color_w.value = bg + bg_w.value = color + }, + } - options.push(item) - return [item] - }) - break - } - case 'Save Tensors (mtb)': { - const onDrawBackground = nodeType.prototype.onDrawBackground - nodeType.prototype.onDrawBackground = function (ctx, canvas) { - const r = onDrawBackground - ? onDrawBackground.apply(this, arguments) - : undefined - // // draw a circle on the top right of the node, with text inside - // ctx.fillStyle = "#fff"; - // ctx.beginPath(); - // ctx.arc(this.size[0] - this.node_width * 0.5, this.size[1] - this.node_height * 0.5, this.node_width * 0.5, 0, Math.PI * 2); - // ctx.fill(); + options.push(item) + return [item] + }) + break + } + case 'Save Tensors (mtb)': { + const onDrawBackground = nodeType.prototype.onDrawBackground + nodeType.prototype.onDrawBackground = function (ctx, canvas) { + const r = onDrawBackground + ? onDrawBackground.apply(this, arguments) + : undefined + // // draw a circle on the top right of the node, with text inside + // ctx.fillStyle = "#fff"; + // ctx.beginPath(); + // ctx.arc(this.size[0] - this.node_width * 0.5, this.size[1] - this.node_height * 0.5, this.node_width * 0.5, 0, Math.PI * 2); + // ctx.fill(); - // ctx.fillStyle = "#000"; - // ctx.textAlign = "center"; - // ctx.font = "bold 12px Arial"; - // ctx.fillText("Save Tensors", this.size[0] - this.node_width * 0.5, this.size[1] - this.node_height * 0.5); + // ctx.fillStyle = "#000"; + // ctx.textAlign = "center"; + // ctx.font = "bold 12px Arial"; + // ctx.fillText("Save Tensors", this.size[0] - this.node_width * 0.5, this.size[1] - this.node_height * 0.5); - return r - } - break - } - default: { - break - } - } - }, + return r + } + break + } + default: { + break + } + } + }, } app.registerExtension(mtb_widgets)