969 lines
31 KiB
JavaScript
969 lines
31 KiB
JavaScript
/**
|
|
* 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') {
|
|
// Newer frontends create their own widget (type 'color') for COLOR inputs;
|
|
// match by name only so we never add a duplicate alongside it.
|
|
const hasWidget = (node.widgets || []).some((w) => w.name === name)
|
|
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)
|