JasonSmithSO's picture
Upload 578 files
8866644 verified
raw
history blame
12.7 kB
import { app } from "../../../../scripts/app.js";
import { ComfyWidgets } from "../../../../scripts/widgets.js";
const KEY_CODES = { ENTER: 13, ESC: 27, ARROW_DOWN: 40, ARROW_UP: 38 };
const WIDGET_GAP = -4;
function hideInfoWidget(e, node, widget) {
let dropdownShouldBeRemoved = false;
let selectionIndex = -1;
if (e) {
e.preventDefault();
e.stopPropagation();
displayDropdown(widget);
} else {
hideWidget(widget, node);
}
function createDropdownElement() {
const dropdown = document.createElement('ul');
dropdown.id = 'hideinfo-dropdown';
dropdown.setAttribute('role', 'listbox');
dropdown.classList.add('hideInfo-dropdown');
return dropdown;
}
function createDropdownItem(textContent, action) {
const listItem = document.createElement('li');
listItem.id = `hideInfo-item-${textContent.replace(/ /g, '')}`;
listItem.classList.add('hideInfo-item');
listItem.setAttribute('role', 'option');
listItem.textContent = textContent;
listItem.addEventListener('mousedown', (event) => {
event.preventDefault();
action(widget, node); // perform the action when dropdown item is clicked
removeDropdown();
dropdownShouldBeRemoved = false;
});
listItem.dataset.action = textContent.replace(/ /g, ''); // store the action in a data attribute
return listItem;
}
function displayDropdown(widget) {
removeDropdown();
const dropdown = createDropdownElement();
const listItemHide = createDropdownItem('Hide info Widget', hideWidget);
const listItemHideAll = createDropdownItem('Hide for all of this node-type', hideWidgetForNodetype);
dropdown.appendChild(listItemHide);
dropdown.appendChild(listItemHideAll);
const inputRect = widget.inputEl.getBoundingClientRect();
dropdown.style.top = `${inputRect.top + inputRect.height}px`;
dropdown.style.left = `${inputRect.left}px`;
dropdown.style.width = `${inputRect.width}px`;
document.body.appendChild(dropdown);
dropdownShouldBeRemoved = true;
widget.inputEl.removeEventListener('keydown', handleKeyDown);
widget.inputEl.addEventListener('keydown', handleKeyDown);
document.addEventListener('click', handleDocumentClick);
}
function removeDropdown() {
const dropdown = document.getElementById('hideinfo-dropdown');
if (dropdown) {
dropdown.remove();
widget.inputEl.removeEventListener('keydown', handleKeyDown);
}
document.removeEventListener('click', handleDocumentClick);
}
function handleKeyDown(event) {
const dropdownItems = document.querySelectorAll('.hideInfo-item');
if (event.keyCode === KEY_CODES.ENTER && dropdownShouldBeRemoved) {
event.preventDefault();
if (selectionIndex !== -1) {
const selectedAction = dropdownItems[selectionIndex].dataset.action;
if (selectedAction === 'HideinfoWidget') {
hideWidget(widget, node);
} else if (selectedAction === 'Hideforall') {
hideWidgetForNodetype(widget, node);
}
removeDropdown();
dropdownShouldBeRemoved = false;
}
} else if (event.keyCode === KEY_CODES.ARROW_DOWN && dropdownShouldBeRemoved) {
event.preventDefault();
if (selectionIndex !== -1) {
dropdownItems[selectionIndex].classList.remove('selected');
}
selectionIndex = (selectionIndex + 1) % dropdownItems.length;
dropdownItems[selectionIndex].classList.add('selected');
} else if (event.keyCode === KEY_CODES.ARROW_UP && dropdownShouldBeRemoved) {
event.preventDefault();
if (selectionIndex !== -1) {
dropdownItems[selectionIndex].classList.remove('selected');
}
selectionIndex = (selectionIndex - 1 + dropdownItems.length) % dropdownItems.length;
dropdownItems[selectionIndex].classList.add('selected');
} else if (event.keyCode === KEY_CODES.ESC && dropdownShouldBeRemoved) {
event.preventDefault();
removeDropdown();
}
}
function hideWidget(widget, node) {
node.properties['infoWidgetHidden'] = true;
widget.type = "esayHidden";
widget.computeSize = () => [0, WIDGET_GAP];
node.setSize([node.size[0], node.size[1]]);
}
function hideWidgetForNodetype(widget, node) {
hideWidget(widget, node)
const hiddenNodeTypes = JSON.parse(localStorage.getItem('hiddenWidgetNodeTypes') || "[]");
if (!hiddenNodeTypes.includes(node.constructor.type)) {
hiddenNodeTypes.push(node.constructor.type);
}
localStorage.setItem('hiddenWidgetNodeTypes', JSON.stringify(hiddenNodeTypes));
}
function handleDocumentClick(event) {
const dropdown = document.getElementById('hideinfo-dropdown');
// If the click was outside the dropdown and the dropdown should be removed, remove it
if (dropdown && !dropdown.contains(event.target) && dropdownShouldBeRemoved) {
removeDropdown();
dropdownShouldBeRemoved = false;
}
}
}
var styleElement = document.createElement("style");
const cssCode = `
.easy-info_widget {
background-color: var(--comfy-input-bg);
color: var(--input-text);
overflow: hidden;
padding: 2px;
resize: none;
border: none;
box-sizing: border-box;
font-size: 10px;
border-radius: 7px;
text-align: center;
text-wrap: balance;
}
.hideInfo-dropdown {
position: absolute;
box-sizing: border-box;
background-color: #121212;
border-radius: 7px;
box-shadow: 0 2px 4px rgba(255, 255, 255, .25);
padding: 0;
margin: 0;
list-style: none;
z-index: 1000;
overflow: auto;
max-height: 200px;
}
.hideInfo-dropdown li {
padding: 4px 10px;
cursor: pointer;
font-family: system-ui;
font-size: 0.7rem;
}
.hideInfo-dropdown li:hover,
.hideInfo-dropdown li.selected {
background-color: #e5e5e5;
border-radius: 7px;
}
`
styleElement.innerHTML = cssCode
document.head.appendChild(styleElement);
const InfoSymbol = Symbol();
const InfoResizeSymbol = Symbol();
// WIDGET FUNCTIONS
function addInfoWidget(node, name, opts, app) {
const INFO_W_SIZE = 50;
node.addProperty('infoWidgetHidden', false)
function computeSize(size) {
if (node.widgets[0].last_y == null) return;
let y = node.widgets[0].last_y;
// Compute the height of all non easyInfo widgets
let widgetHeight = 0;
const infoWidges = [];
for (let i = 0; i < node.widgets.length; i++) {
const w = node.widgets[i];
if (w.type === "easyInfo") {
infoWidges.push(w);
} else {
if (w.computeSize) {
widgetHeight += w.computeSize()[1] + 4;
} else {
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
}
}
let infoWidgetSpace = infoWidges.length * INFO_W_SIZE; // Height for all info widgets
// Check if there's enough space for all widgets
if (size[1] < y + widgetHeight + infoWidgetSpace) {
// There isn't enough space for all the widgets, increase the size of the node
node.size[1] = y + widgetHeight + infoWidgetSpace;
node.graph.setDirtyCanvas(true);
}
// Position each of the widgets
for (const w of node.widgets) {
w.y = y;
if (w.type === "easyInfo") {
y += INFO_W_SIZE;
} else if (w.computeSize) {
y += w.computeSize()[1] + 4;
} else {
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
}
}
const widget = {
type: "easyInfo",
name,
get value() {
return this.inputEl.value;
},
set value(x) {
this.inputEl.value = x;
},
draw: function (ctx, _, widgetWidth, y, widgetHeight) {
if (!this.parent.inputHeight) {
// If we are initially offscreen when created we wont have received a resize event
// Calculate it here instead
computeSize(node.size);
}
const visible = app.canvas.ds.scale > 0.5 && this.type === "easyInfo";
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 + y);
Object.assign(this.inputEl.style, {
transformOrigin: "0 0",
transform: transform,
left: "0px",
top: "0px",
width: `${widgetWidth - (margin * 2)}px`,
height: `${this.parent.inputHeight - (margin * 2)}px`,
position: "absolute",
background: (!node.color)?'':node.color,
color: (!node.color)?'':'white',
zIndex: app.graph._nodes.indexOf(node),
});
this.inputEl.hidden = !visible;
},
};
widget.inputEl = document.createElement("textarea");
widget.inputEl.className = "easy-info_widget";
widget.inputEl.value = opts.defaultVal;
widget.inputEl.placeholder = opts.placeholder || "";
widget.inputEl.readOnly = true;
widget.parent = node;
document.body.appendChild(widget.inputEl);
node.addCustomWidget(widget);
app.canvas.onDrawBackground = function () {
// Draw node isnt fired once the node is off the screen
// if it goes off screen quickly, the input may not be removed
// this shifts it off screen so it can be moved back if the node is visible.
for (let n in app.graph._nodes) {
n = app.graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, "inputEl")) {
wid.inputEl.style.left = -8000 + "px";
wid.inputEl.style.position = "absolute";
}
}
}
};
node.onRemoved = function () {
// When removing this node we need to remove the input from the DOM
for (let y in this.widgets) {
if (this.widgets[y].inputEl) {
this.widgets[y].inputEl.remove();
}
}
};
widget.onRemove = () => {
widget.inputEl?.remove();
// Restore original size handler if we are the last
if (!--node[InfoSymbol]) {
node.onResize = node[InfoResizeSymbol];
delete node[InfoSymbol];
delete node[InfoResizeSymbol];
}
};
if (node[InfoSymbol]) {
node[InfoSymbol]++;
} else {
node[InfoSymbol] = 1;
const onResize = (node[InfoResizeSymbol] = node.onResize);
node.onResize = function (size) {
computeSize(size);
// Call original resizer handler
if (onResize) {
console.log(this, arguments)
onResize.apply(this, arguments);
}
};
}
return { widget };
}
// WIDGETS
const easyCustomWidgets = {
INFO(node, inputName, inputData, app) {
const defaultVal = inputData[1].default || "";
return addInfoWidget(node, inputName, { defaultVal, ...inputData[1] }, app);
},
}
app.registerExtension({
name: "comfy.easy.widgets",
getCustomWidgets(app) {
return easyCustomWidgets;
},
nodeCreated(node) {
if (node.widgets) {
// Locate info widgets
const widgets = node.widgets.filter((n) => (n.type === "easyInfo"));
for (const widget of widgets) {
widget.inputEl.addEventListener('contextmenu', function(e) {
hideInfoWidget(e, node, widget);
});
widget.inputEl.addEventListener('click', function(e) {
hideInfoWidget(e, node, widget);
});
}
}
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
const hiddenNodeTypes = JSON.parse(localStorage.getItem('hiddenWidgetNodeTypes') || "[]");
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
if (this.properties['infoWidgetHidden']) {
for (let i in this.widgets) {
if (this.widgets[i].type == "easyInfo") {
hideInfoWidget(null, this, this.widgets[i]);
}
}
}
return r;
};
const origOnAdded = nodeType.prototype.onAdded;
nodeType.prototype.onAdded = function () {
const r = origOnAdded ? origOnAdded.apply(this, arguments) : undefined;
if (hiddenNodeTypes.includes(this.type)) {
for (let i in this.widgets) {
if (this.widgets[i].type == "easyInfo") {
this.properties['infoWidgetHidden'] = true;
}
}
}
return r;
}
}
});