319 lines
8.7 KiB
JavaScript
319 lines
8.7 KiB
JavaScript
/**
|
|
* File: comfy_shared.js
|
|
* Project: comfy_mtb
|
|
* Author: Mel Massadian
|
|
*
|
|
* Copyright (c) 2023 Mel Massadian
|
|
*
|
|
*/
|
|
|
|
import { app } from '/scripts/app.js'
|
|
|
|
export const log = (...args) => {
|
|
if (window.MTB?.DEBUG) {
|
|
console.debug(...args)
|
|
}
|
|
}
|
|
|
|
//- WIDGET UTILS
|
|
export const CONVERTED_TYPE = 'converted-widget'
|
|
|
|
export function offsetDOMWidget(
|
|
widget,
|
|
ctx,
|
|
node,
|
|
widgetWidth,
|
|
widgetY,
|
|
height
|
|
) {
|
|
const margin = 10
|
|
const elRect = ctx.canvas.getBoundingClientRect()
|
|
const transform = new DOMMatrix()
|
|
.scaleSelf(
|
|
elRect.width / ctx.canvas.width,
|
|
elRect.height / ctx.canvas.height
|
|
)
|
|
.multiplySelf(ctx.getTransform())
|
|
.translateSelf(margin, margin + widgetY)
|
|
|
|
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d)
|
|
Object.assign(widget.inputEl.style, {
|
|
transformOrigin: '0 0',
|
|
transform: scale,
|
|
left: `${transform.a + transform.e}px`,
|
|
top: `${transform.d + transform.f}px`,
|
|
width: `${widgetWidth - margin * 2}px`,
|
|
// height: `${(widget.parent?.inputHeight || 32) - (margin * 2)}px`,
|
|
height: `${(height || widget.parent?.inputHeight || 32) - margin * 2}px`,
|
|
|
|
position: 'absolute',
|
|
background: !node.color ? '' : node.color,
|
|
color: !node.color ? '' : 'white',
|
|
zIndex: 5, //app.graph._nodes.indexOf(node),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Extracts the type and link type from a widget config object.
|
|
* @param {*} config
|
|
* @returns
|
|
*/
|
|
export function getWidgetType(config) {
|
|
// Special handling for COMBO so we restrict links based on the entries
|
|
let type = config?.[0]
|
|
let linkType = type
|
|
if (type instanceof Array) {
|
|
type = 'COMBO'
|
|
linkType = linkType.join(',')
|
|
}
|
|
return { type, linkType }
|
|
}
|
|
|
|
export const dynamic_connection = (
|
|
node,
|
|
index,
|
|
connected,
|
|
connectionPrefix = 'input_',
|
|
connectionType = 'PSDLAYER'
|
|
) => {
|
|
// remove all non connected inputs
|
|
if (!connected && node.inputs.length > 1) {
|
|
log(`Removing input ${index} (${node.inputs[index].name})`)
|
|
if (node.widgets) {
|
|
const w = node.widgets.find((w) => w.name === node.inputs[index].name)
|
|
if (w) {
|
|
w.onRemoved?.()
|
|
node.widgets.length = node.widgets.length - 1
|
|
}
|
|
}
|
|
node.removeInput(index)
|
|
|
|
// make inputs sequential again
|
|
for (let i = 0; i < node.inputs.length; i++) {
|
|
node.inputs[i].label = `${connectionPrefix}${i + 1}`
|
|
}
|
|
}
|
|
|
|
// add an extra input
|
|
if (node.inputs[node.inputs.length - 1].link != undefined) {
|
|
log(
|
|
`Adding input ${node.inputs.length + 1} (${connectionPrefix}${
|
|
node.inputs.length + 1
|
|
})`
|
|
)
|
|
|
|
node.addInput(
|
|
`${connectionPrefix}${node.inputs.length + 1}`,
|
|
connectionType
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Appends a callback to the extra menu options of a given node type.
|
|
* @param {*} nodeType
|
|
* @param {*} cb
|
|
*/
|
|
export function addMenuHandler(nodeType, cb) {
|
|
const getOpts = nodeType.prototype.getExtraMenuOptions
|
|
nodeType.prototype.getExtraMenuOptions = function () {
|
|
const r = getOpts.apply(this, arguments)
|
|
cb.apply(this, arguments)
|
|
return r
|
|
}
|
|
}
|
|
|
|
export function hideWidget(node, widget, suffix = '') {
|
|
widget.origType = widget.type
|
|
widget.hidden = true
|
|
widget.origComputeSize = widget.computeSize
|
|
widget.origSerializeValue = widget.serializeValue
|
|
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
|
|
widget.type = CONVERTED_TYPE + suffix
|
|
widget.serializeValue = () => {
|
|
// Prevent serializing the widget if we have no input linked
|
|
const { link } = node.inputs.find((i) => i.widget?.name === widget.name)
|
|
if (link == null) {
|
|
return undefined
|
|
}
|
|
return widget.origSerializeValue
|
|
? widget.origSerializeValue()
|
|
: widget.value
|
|
}
|
|
|
|
// Hide any linked widgets, e.g. seed+seedControl
|
|
if (widget.linkedWidgets) {
|
|
for (const w of widget.linkedWidgets) {
|
|
hideWidget(node, w, ':' + widget.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function showWidget(widget) {
|
|
widget.type = widget.origType
|
|
widget.computeSize = widget.origComputeSize
|
|
widget.serializeValue = widget.origSerializeValue
|
|
|
|
delete widget.origType
|
|
delete widget.origComputeSize
|
|
delete widget.origSerializeValue
|
|
|
|
// Hide any linked widgets, e.g. seed+seedControl
|
|
if (widget.linkedWidgets) {
|
|
for (const w of widget.linkedWidgets) {
|
|
showWidget(w)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function convertToWidget(node, widget) {
|
|
showWidget(widget)
|
|
const sz = node.size
|
|
node.removeInput(node.inputs.findIndex((i) => i.widget?.name === widget.name))
|
|
|
|
for (const widget of node.widgets) {
|
|
widget.last_y -= LiteGraph.NODE_SLOT_HEIGHT
|
|
}
|
|
|
|
// Restore original size but grow if needed
|
|
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
|
|
}
|
|
|
|
export function convertToInput(node, widget, config) {
|
|
hideWidget(node, widget)
|
|
|
|
const { linkType } = getWidgetType(config)
|
|
|
|
// Add input and store widget config for creating on primitive node
|
|
const sz = node.size
|
|
node.addInput(widget.name, linkType, {
|
|
widget: { name: widget.name, config },
|
|
})
|
|
|
|
for (const widget of node.widgets) {
|
|
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT
|
|
}
|
|
|
|
// Restore original size but grow if needed
|
|
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
|
|
}
|
|
|
|
export function hideWidgetForGood(node, widget, suffix = '') {
|
|
widget.origType = widget.type
|
|
widget.origComputeSize = widget.computeSize
|
|
widget.origSerializeValue = widget.serializeValue
|
|
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
|
|
widget.type = CONVERTED_TYPE + suffix
|
|
// widget.serializeValue = () => {
|
|
// // Prevent serializing the widget if we have no input linked
|
|
// const w = node.inputs?.find((i) => i.widget?.name === widget.name);
|
|
// if (w?.link == null) {
|
|
// return undefined;
|
|
// }
|
|
// return widget.origSerializeValue ? widget.origSerializeValue() : widget.value;
|
|
// };
|
|
|
|
// Hide any linked widgets, e.g. seed+seedControl
|
|
if (widget.linkedWidgets) {
|
|
for (const w of widget.linkedWidgets) {
|
|
hideWidgetForGood(node, w, ':' + widget.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function fixWidgets(node) {
|
|
if (node.inputs) {
|
|
for (const input of node.inputs) {
|
|
log(input)
|
|
if (input.widget || node.widgets) {
|
|
// if (newTypes.includes(input.type)) {
|
|
const matching_widget = node.widgets.find((w) => w.name === input.name)
|
|
if (matching_widget) {
|
|
// if (matching_widget.hidden) {
|
|
// log(`Already hidden skipping ${matching_widget.name}`)
|
|
// continue
|
|
// }
|
|
const w = node.widgets.find((w) => w.name === matching_widget.name)
|
|
if (w && w.type != CONVERTED_TYPE) {
|
|
log(w)
|
|
log(`hidding ${w.name}(${w.type}) from ${node.type}`)
|
|
log(node)
|
|
hideWidget(node, w)
|
|
} else {
|
|
log(`converting to widget ${w}`)
|
|
|
|
convertToWidget(node, input)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
export function inner_value_change(widget, value, event = undefined) {
|
|
if (widget.type == 'number' || widget.type == 'BBOX') {
|
|
value = Number(value)
|
|
} else if (widget.type == 'BOOL') {
|
|
value = Boolean(value)
|
|
}
|
|
widget.value = value
|
|
if (
|
|
widget.options &&
|
|
widget.options.property &&
|
|
node.properties[widget.options.property] !== undefined
|
|
) {
|
|
node.setProperty(widget.options.property, value)
|
|
}
|
|
if (widget.callback) {
|
|
widget.callback(widget.value, app.canvas, node, pos, event)
|
|
}
|
|
}
|
|
|
|
//- COLOR UTILS
|
|
export function isColorBright(rgb, threshold = 240) {
|
|
const brightess = getBrightness(rgb)
|
|
return brightess > threshold
|
|
}
|
|
|
|
function getBrightness(rgbObj) {
|
|
return Math.round(
|
|
(parseInt(rgbObj[0]) * 299 +
|
|
parseInt(rgbObj[1]) * 587 +
|
|
parseInt(rgbObj[2]) * 114) /
|
|
1000
|
|
)
|
|
}
|
|
|
|
//- HTML / CSS UTILS
|
|
export function defineClass(className, classStyles) {
|
|
const styleSheets = document.styleSheets
|
|
|
|
// Helper function to check if the class exists in a style sheet
|
|
function classExistsInStyleSheet(styleSheet) {
|
|
const rules = styleSheet.rules || styleSheet.cssRules
|
|
for (const rule of rules) {
|
|
if (rule.selectorText === `.${className}`) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Check if the class is already defined in any of the style sheets
|
|
let classExists = false
|
|
for (const styleSheet of styleSheets) {
|
|
if (classExistsInStyleSheet(styleSheet)) {
|
|
classExists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// If the class doesn't exist, add the new class definition to the first style sheet
|
|
if (!classExists) {
|
|
if (styleSheets[0].insertRule) {
|
|
styleSheets[0].insertRule(`.${className} { ${classStyles} }`, 0)
|
|
} else if (styleSheets[0].addRule) {
|
|
styleSheets[0].addRule(`.${className}`, classStyles, 0)
|
|
}
|
|
}
|
|
}
|