From 25b614885e25fea984855bac7dc6758c369c1e91 Mon Sep 17 00:00:00 2001 From: flybirdxx <1119577418@qq.com> Date: Thu, 11 Dec 2025 18:02:00 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A9=E5=8F=96=E8=89=B2=E5=99=A8=E6=9A=82?= =?UTF-8?q?=E6=97=B6=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现在暂时让取舍器恢复可用了,但是那个按钮不知道咋改回去,尽力了 --- js/dz_mtb_widgets.js | 1874 ++++++++++++++++++++++-------------------- 1 file changed, 966 insertions(+), 908 deletions(-) diff --git a/js/dz_mtb_widgets.js b/js/dz_mtb_widgets.js index e44133b..a32f506 100644 --- a/js/dz_mtb_widgets.js +++ b/js/dz_mtb_widgets.js @@ -1,908 +1,966 @@ -/** - * File: DZ_widgets.js - * Project: comfy_DZ - * Author: Mel Massadian - * - * Copyright (c) 2023 Mel Massadian - * - */ - -import { app } from '../../scripts/app.js' -import { api } from '../../scripts/api.js' - -import parseCss from './dz_parse-css.js' -import * as shared from './dz_comfy_shared.js' -import { log } from './dz_comfy_shared.js' - -const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX'] - -const withFont = (ctx, font, cb) => { - 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 } -} - -export const DZWidgets = { - 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 - - 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 - - if (hide) return - - 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.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 - - 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 - ) - } - } - - 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); - // } - } - - 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' - - 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 - - picker.style.position = 'absolute' - picker.style.left = '999999px' //(window.innerWidth / 2) + "px"; - picker.style.top = '999999px' //(window.innerHeight / 2) + "px"; - - document.body.appendChild(picker) - - picker.addEventListener('change', () => { - this.value = picker.value - node.graph._version++ - node.setDirtyCanvas(true, true) - picker.remove() - }) - - picker.click() - } - } - } - } - widget.computeSize = function (width) { - return [width, 32] - } - - 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() -// } -// }, -// } -// -// 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()) -// }, -// } -// -// w.inputEl = document.createElement('p') -// w.inputEl.style = ` -// text-align: center; -// font-size: ${fontSize}px; -// color: var(--input-text); -// line-height: 0; -// font-family: monospace; -// ` -// w.value = val -// document.body.appendChild(w.inputEl) -// -// return w -// }, -} - -/** - * @returns {import("./types/comfy").ComfyExtension} extension - */ -const DZ_widgets = { - name: 'DZ.widgets', - - init: async () => { - log('Registering DZ.widgets') -// try { -// const res = await api.fetchApi('/DZ/debug') -// const msg = await res.json() -// if (!window.DZ) { -// window.DZ = {} -// } -// window.DZ.DEBUG = msg.enabled -// } catch (e) { -// console.error('Error:', error) -// } - }, - - setup: () => { -// app.ui.settings.addSetting({ -// id: 'DZ.Debug.enabled', -// name: '[DZ] 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 (value) { -// console.log('Enabled DEBUG mode') -// } -// if (!window.DZ) { -// window.DZ = {} -// } -// window.DZ.DEBUG = value -// await api -// .fetchApi('/DZ/debug', { -// method: 'POST', -// body: JSON.stringify({ -// enabled: value, -// }), -// }) -// .then((response) => {}) -// .catch((error) => { -// console.error('Error:', error) -// }) -// }, -// }) - }, - - getCustomWidgets: function () { - return { - BOOL: (node, inputName, inputData, app) => { - console.debug('Registering bool') - - return { - widget: node.addCustomWidget( - DZWidgets.BOOL(inputName, inputData[1]?.default || false) - ), - minWidth: 150, - minHeight: 30, - } - }, - - COLOR: (node, inputName, inputData, app) => { - console.debug('Registering color') - return { - widget: node.addCustomWidget( - DZWidgets.COLOR(inputName, inputData[1]?.default || '#ff0000') - ), - minWidth: 150, - minHeight: 30, - } - }, - // BBOX: (node, inputName, inputData, app) => { - // console.debug("Registering bbox") - // return { - // widget: node.addCustomWidget(DZWidgets.BBOX(inputName, inputData[1]?.default || [0, 0, 0, 0])), - // minWidth: 150, - // minHeight: 30, - // } - - // } - } - }, - /** - * @param {import("./types/comfy").NodeType} nodeType - * @param {import("./types/comfy").NodeDef} 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] - - 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 - } - - //- 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) - } - - if (toWidget.length) { - options.push(...toWidget, null) - } - } - - return r - } - } - - //- Extending Python Nodes - switch (nodeData.name) { - case 'Psd Save (DZ)': { - 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) - return r - } - break - } - //TODO: remove this non sense - case 'Get Batch From History (DZ)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : 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 - } - - const onExecuted = nodeType.prototype.onExecuted - nodeType.prototype.onExecuted = function (message) { - const r = onExecuted ? onExecuted.apply(this, message) : undefined - return r - } - - break - } - case 'Save Gif (DZ)': - case 'Save Animated Image (DZ)': { - 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 - } - - 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( -// DZWidgets.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 (DZ)': { - const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? onNodeCreated.apply(this, arguments) - : undefined - - 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 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) - - raw_iteration._value = 0 - -// const value_preview = this.addCustomWidget( -// DZWidgets['DEBUG_STRING']('value_preview', 'Idle') -// ) -// value_preview.parent = this - -// const loop_preview = this.addCustomWidget( -// DZWidgets['DEBUG_STRING']('loop_preview', 'Iteration: Idle') -// ) -// loop_preview.parent = this - - const onReset = () => { - raw_iteration.value = 0 - raw_loop.value = 0 - - value_preview.value = 'Idle' - loop_preview.value = 'Iteration: Idle' - - app.canvas.setDirty(true) - } - - const reset_button = this.addWidget( - 'button', - `Reset`, - 'reset', - onReset - ) - - const 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.DZ?.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) - } - - 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}` - - 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 - } - - break - } - case 'Text Encore Frames (DZ)': { - 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) - return r - } - break - } - case 'Interpolate Clip Sequential (DZ)': { - 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 (DZ)': { - 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('/DZ/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.DZ?.notify?.( - `Extracted positive from ${this.widgets[0].value}` - ) - const tn = LiteGraph.createNode('Text box') - app.graph.add(tn) - tn.title = `${this.widgets[0].value} (Positive)` - tn.widgets[0].value = style[0] - } else { - window.DZ?.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.DZ?.notify?.( - `Extracted negative from ${this.widgets[0].value}` - ) - const tn = LiteGraph.createNode('Text box') - app.graph.add(tn) - tn.title = `${this.widgets[0].value} (Negative)` - tn.widgets[0].value = style[1] - } else { - window.DZ.notify( - `No negative to extract for ${this.widgets[0].value}` - ) - } - } - }, - }, - ] - options.push(...extracters) - } - - break - } - case 'Save Tensors (DZ)': { - 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); - - return r - } - break - } - default: { - break - } - } - }, -} - -app.registerExtension(DZ_widgets) +/** + * File: DZ_widgets.js + * Project: comfy_DZ + * Author: Mel Massadian + * + * Copyright (c) 2023 Mel Massadian + * + */ + +import { app } from '../../scripts/app.js' +import { api } from '../../scripts/api.js' + +import parseCss from './dz_parse-css.js' +import * as shared from './dz_comfy_shared.js' +import { log } from './dz_comfy_shared.js' + +const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX'] + +// Ensure COLOR widgets exist (for legacy frontends and loaded graphs). +const ensureColorWidgets = (node, nodeData) => { + if (!node || !nodeData) return + const required = nodeData?.input?.required || {} + for (const [name, def] of Object.entries(required)) { + const t = def?.[0] + if (t === 'COLOR') { + const hasWidget = (node.widgets || []).some((w) => w.name === name && w.type === 'COLOR') + if (!hasWidget && typeof node.addCustomWidget === 'function') { + const defaultVal = def?.[1]?.default || '#ff0000' + node.addCustomWidget(DZWidgets.COLOR(name, defaultVal)) + } + } + } +} + +const withFont = (ctx, font, cb) => { + 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 } +} + +export const DZWidgets = { + 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 + + 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 + + if (hide) return + + 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.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 + + 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 + ) + } + } + + 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); + // } + } + + 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) { + // Cache last draw position for hit-testing in mouse handler + this.last_y = widgetY + this.last_h = height + this.last_w = widgetWidth + + 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) { + // Support legacy frontends that dispatch mousedown/click instead of pointerdown. + if (e.type === 'pointerdown' || e.type === 'mousedown' || e.type === 'click') { + const widgets = node.widgets.filter((w) => w.type === 'COLOR') + + for (const w of widgets) { + // color picker + const y0 = w.last_y ?? 0 + const y1 = y0 + (w.last_h ?? 32) + if (pos[1] > y0 && pos[1] < y1) { + const picker = document.createElement('input') + picker.type = 'color' + picker.value = this.value + + // Position the invisible picker near the click to avoid browser pop-up blockers. + picker.style.position = 'fixed' + picker.style.left = `${e.clientX}px` + picker.style.top = `${e.clientY}px` + picker.style.opacity = '0' + picker.style.width = '1px' + picker.style.height = '1px' + picker.style.pointerEvents = 'none' + picker.style.zIndex = '9999' + picker.setAttribute('aria-hidden', 'true') + + document.body.appendChild(picker) + + picker.addEventListener('change', () => { + this.value = picker.value + node.graph._version++ + node.setDirtyCanvas(true, true) + picker.remove() + }) + + picker.addEventListener('blur', () => { + // If user cancels without change, clean up. + picker.remove() + }) + + // Prevent graph from interpreting this click as drag + e.stopPropagation?.() + e.preventDefault?.() + // Prefer showPicker when available (Chrome 111+) + if (typeof picker.showPicker === 'function') { + try { + picker.showPicker() + } catch (err) { + picker.click() + } + } else { + picker.click() + } + } + } + } + } + widget.computeSize = function (width) { + return [width, 32] + } + + 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() +// } +// }, +// } +// +// 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()) +// }, +// } +// +// w.inputEl = document.createElement('p') +// w.inputEl.style = ` +// text-align: center; +// font-size: ${fontSize}px; +// color: var(--input-text); +// line-height: 0; +// font-family: monospace; +// ` +// w.value = val +// document.body.appendChild(w.inputEl) +// +// return w +// }, +} + +/** + * @returns {import("./types/comfy").ComfyExtension} extension + */ +const DZ_widgets = { + name: 'DZ.widgets', + + init: async () => { + log('Registering DZ.widgets') +// try { +// const res = await api.fetchApi('/DZ/debug') +// const msg = await res.json() +// if (!window.DZ) { +// window.DZ = {} +// } +// window.DZ.DEBUG = msg.enabled +// } catch (e) { +// console.error('Error:', error) +// } + }, + + setup: () => { +// app.ui.settings.addSetting({ +// id: 'DZ.Debug.enabled', +// name: '[DZ] 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 (value) { +// console.log('Enabled DEBUG mode') +// } +// if (!window.DZ) { +// window.DZ = {} +// } +// window.DZ.DEBUG = value +// await api +// .fetchApi('/DZ/debug', { +// method: 'POST', +// body: JSON.stringify({ +// enabled: value, +// }), +// }) +// .then((response) => {}) +// .catch((error) => { +// console.error('Error:', error) +// }) +// }, +// }) + }, + + getCustomWidgets: function () { + return { + BOOL: (node, inputName, inputData, app) => { + console.debug('Registering bool') + + return { + widget: node.addCustomWidget( + DZWidgets.BOOL(inputName, inputData[1]?.default || false) + ), + minWidth: 150, + minHeight: 30, + } + }, + + COLOR: (node, inputName, inputData, app) => { + console.debug('Registering color') + return { + widget: node.addCustomWidget( + DZWidgets.COLOR(inputName, inputData[1]?.default || '#ff0000') + ), + minWidth: 150, + minHeight: 30, + } + }, + // BBOX: (node, inputName, inputData, app) => { + // console.debug("Registering bbox") + // return { + // widget: node.addCustomWidget(DZWidgets.BBOX(inputName, inputData[1]?.default || [0, 0, 0, 0])), + // minWidth: 150, + // minHeight: 30, + // } + + // } + } + }, + /** + * @param {import("./types/comfy").NodeType} nodeType + * @param {import("./types/comfy").NodeDef} 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] + + 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()) + + // Ensure custom widgets are present even on legacy frontends or old saved graphs. + ensureColorWidgets(this, nodeData) + + this.onRemoved = function () { + // When removing this node we need to remove the input from the DOM + shared.cleanupNode(this) + } + return r + } + + // Also patch configure (called when loading saved workflows) to reattach widgets. + const origConfigure = nodeType.prototype.configure + nodeType.prototype.configure = function () { + const r = origConfigure ? origConfigure.apply(this, arguments) : undefined + ensureColorWidgets(this, nodeData) + 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 || {}] + + 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) + } + } + + return r + } + } + + //- Extending Python Nodes + switch (nodeData.name) { + case 'Psd Save (DZ)': { + 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) + return r + } + break + } + //TODO: remove this non sense + case 'Get Batch From History (DZ)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : 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 + } + + const onExecuted = nodeType.prototype.onExecuted + nodeType.prototype.onExecuted = function (message) { + const r = onExecuted ? onExecuted.apply(this, message) : undefined + return r + } + + break + } + case 'Save Gif (DZ)': + case 'Save Animated Image (DZ)': { + 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 + } + + 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( +// DZWidgets.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 (DZ)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined + + 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 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) + + raw_iteration._value = 0 + +// const value_preview = this.addCustomWidget( +// DZWidgets['DEBUG_STRING']('value_preview', 'Idle') +// ) +// value_preview.parent = this + +// const loop_preview = this.addCustomWidget( +// DZWidgets['DEBUG_STRING']('loop_preview', 'Iteration: Idle') +// ) +// loop_preview.parent = this + + const onReset = () => { + raw_iteration.value = 0 + raw_loop.value = 0 + + value_preview.value = 'Idle' + loop_preview.value = 'Iteration: Idle' + + app.canvas.setDirty(true) + } + + const reset_button = this.addWidget( + 'button', + `Reset`, + 'reset', + onReset + ) + + const 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.DZ?.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) + } + + 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}` + + 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 + } + + break + } + case 'Text Encore Frames (DZ)': { + 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) + return r + } + break + } + case 'Interpolate Clip Sequential (DZ)': { + 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 (DZ)': { + 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('/DZ/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.DZ?.notify?.( + `Extracted positive from ${this.widgets[0].value}` + ) + const tn = LiteGraph.createNode('Text box') + app.graph.add(tn) + tn.title = `${this.widgets[0].value} (Positive)` + tn.widgets[0].value = style[0] + } else { + window.DZ?.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.DZ?.notify?.( + `Extracted negative from ${this.widgets[0].value}` + ) + const tn = LiteGraph.createNode('Text box') + app.graph.add(tn) + tn.title = `${this.widgets[0].value} (Negative)` + tn.widgets[0].value = style[1] + } else { + window.DZ.notify( + `No negative to extract for ${this.widgets[0].value}` + ) + } + } + }, + }, + ] + options.push(...extracters) + } + + break + } + case 'Save Tensors (DZ)': { + 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); + + return r + } + break + } + default: { + break + } + } + }, +} + +app.registerExtension(DZ_widgets)