Files
Pseudotools-Pseudocomfy/web/node/process_mat_prompts.js
T
2025-05-27 14:47:00 -07:00

323 lines
12 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
const _ID = "PseudoProcessMaterialPrompts";
app.registerExtension({
name: 'pseudocomfy.' + _ID,
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== _ID) return;
//console.log("[pseudocomfy] Registering node extension for " + _ID);
nodeType.prototype.onNodeCreated = function () {
//console.log("[pseudocomfy] onNodeCreated");
// to ensure node height will show controls,
// add some temporary divs and store their widgets in domWidgets
for (let i = 0; i < 6; i++) {
const fillerDiv = document.createElement("div");
fillerDiv.style.width = "100%";
fillerDiv.style.backgroundColor = "yellow";
fillerDiv.style.visibility = "hidden"; // Invisible but takes up space
if (this.addDOMWidget) {
const widget = this.addDOMWidget(`Filler${i + 1}`, "custom", fillerDiv, {});
if (!this.domWidgets) this.domWidgets = [];
this.domWidgets.push(widget);
}
}
};
nodeType.prototype.onExecuted = function (message) {
// Properly remove previous widgets if needed
if (this.domWidgets && this.domWidgets.length) {
for (const w of this.domWidgets) {
// Remove DOM element if present
if (w.element && w.element.parentNode) w.element.parentNode.removeChild(w.element);
// Remove from node's widgets array if present
if (this.widgets && Array.isArray(this.widgets)) {
const idx = this.widgets.indexOf(w);
if (idx !== -1) this.widgets.splice(idx, 1);
}
}
this.domWidgets = [];
} else {
this.domWidgets = [];
}
// Defensive: ensure mat_msks and mat_txts are arrays
const matTxts = Array.isArray(message.mat_txts) ? message.mat_txts : [];
const matImgs = Array.isArray(message.mat_imgs) ? message.mat_imgs : [];
const maskImgs = Array.isArray(message.mat_msks) ? message.mat_msks : [];
if (!this._itemState) this._itemState = { idx: 0 };
const state = this._itemState;
if (state.idx >= maskImgs.length) state.idx = maskImgs.length - 1;
if (state.idx < 0) state.idx = 0;
let promptWidgetEl = null;
let controlsInfoDiv = null;
let itemCount = maskImgs.length;
// Info widget needs to be updated by buttons and image
const updateInfo = () => {
if (controlsInfoDiv && controlsInfoDiv.infoWidgetEl) {
controlsInfoDiv.infoWidgetEl.textContent = maskImgs.length
? `${state.idx + 1} of ${maskImgs.length}`
: "No materials found";
}
// update promptWidgetEl layout if needed ---
if (promptWidgetEl && promptWidgetEl.parentNode) {
// Check if layout type needs to change
const hasImg = matImgs.length > 0 && matImgs[state.idx];
const isSideBySide = promptWidgetEl.style.flexDirection === "row";
if ((hasImg && !isSideBySide) || (!hasImg && isSideBySide)) {
// Remove old widget and insert new one
const newPromptWidget = createPromptWidget(matTxts, matImgs, state);
promptWidgetEl.parentNode.replaceChild(newPromptWidget, promptWidgetEl);
promptWidgetEl = newPromptWidget;
} else {
// Always update value and image if present
promptWidgetEl.value = matTxts.length > 0 ? matTxts[state.idx] : "";
// If side-by-side, also update the image
if (isSideBySide && promptWidgetEl.children.length > 1) {
const imgDiv = promptWidgetEl.children[1];
const img = imgDiv.querySelector("img");
if (img) img.src = hasImg ? matImgs[state.idx] : "";
}
}
}
};
// Create widgets
const maskDiv = createMaskWidget(maskImgs, state, updateInfo);
controlsInfoDiv = createControlsInfoWidget(itemCount, state, maskDiv.img, updateInfo);
promptWidgetEl = createPromptWidget(matTxts, matImgs, state);
// Combine all into a single parent with top/bottom layout
const parent = document.createElement("div");
parent.style.display = "flex";
parent.style.flexDirection = "column";
parent.style.alignItems = "stretch";
parent.style.overflow = "hidden";
parent.style.height = "100%";
// Top: maskDiv + promptWidget (flexible)
const topDiv = document.createElement("div");
topDiv.style.display = "flex";
topDiv.style.flexDirection = "column";
topDiv.style.flex = "1 1 0"; // Flexible, fills available space
topDiv.style.overflow = "hidden";
topDiv.appendChild(maskDiv);
topDiv.appendChild(promptWidgetEl);
// Bottom: controlsInfoDiv (fixed height)
const bottomDiv = document.createElement("div");
bottomDiv.style.flex = "0 0 auto";
bottomDiv.style.height = "40px"; // Fixed height, adjust as needed
bottomDiv.style.display = "flex";
bottomDiv.style.alignItems = "center";
bottomDiv.style.justifyContent = "center";
bottomDiv.appendChild(controlsInfoDiv);
parent.appendChild(topDiv);
parent.appendChild(bottomDiv);
// Add as a single DOM widget
const widget = this.addDOMWidget?.("Material Mask", "custom", parent, {});
if (widget) this.domWidgets.push(widget);
};
},
});
function createMaskWidget(images, state, updateInfo) {
const wrapper = document.createElement("div");
wrapper.style.width = "100%";
wrapper.style.display = "flex";
wrapper.style.justifyContent = "center";
wrapper.style.alignItems = "center";
wrapper.style.marginBottom = "8px";
const img = document.createElement("img");
img.style.width = "100%";
img.style.maxWidth = "192px";
img.style.height = "auto";
img.style.display = "block";
img.src = images.length > 0 ? images[state.idx] : "";
img.updateSrc = () => {
img.src = images.length > 0 ? images[state.idx] : "";
updateInfo && updateInfo();
};
wrapper.appendChild(img);
// Expose img for controls
wrapper.img = img;
return wrapper;
}
function createControlsInfoWidget(itemCount, state, img, updateInfo) {
const wrapper = document.createElement("div");
wrapper.style.width = "100%";
wrapper.style.height = "40px"; // Fixed height, adjust as needed
wrapper.style.display = "flex";
wrapper.style.flexDirection = "row";
wrapper.style.alignItems = "center";
wrapper.style.justifyContent = "center";
//wrapper.style.marginBottom = "8px";
wrapper.style.gap = "8px";
// Button wrappers to allow flex-grow
const btnPrevWrapper = document.createElement("div");
btnPrevWrapper.style.flex = "1 1 0";
btnPrevWrapper.style.display = "flex";
btnPrevWrapper.style.justifyContent = "flex-end";
const btnPrev = document.createElement("button");
btnPrev.textContent = "<";
btnPrev.style.width = "100%";
btnPrev.onclick = () => {
if (itemCount > 0) {
state.idx = (state.idx - 1 + itemCount) % itemCount;
img.updateSrc();
updateInfo && updateInfo();
}
};
btnPrevWrapper.appendChild(btnPrev);
const info = document.createElement("div");
info.style.textAlign = "center";
info.style.fontSize = "0.95em";
info.style.color = "#333";
info.style.flex = "0 1 auto";
info.style.whiteSpace = "nowrap";
info.textContent = itemCount
? `${state.idx + 1} of ${itemCount}`
: "No materials found";
const btnNextWrapper = document.createElement("div");
btnNextWrapper.style.flex = "1 1 0";
btnNextWrapper.style.display = "flex";
btnNextWrapper.style.justifyContent = "flex-start";
const btnNext = document.createElement("button");
btnNext.textContent = ">";
btnNext.style.width = "100%";
btnNext.onclick = () => {
if (itemCount > 0) {
state.idx = (state.idx + 1) % itemCount;
img.updateSrc();
updateInfo && updateInfo();
}
};
btnNextWrapper.appendChild(btnNext);
// Expose info for updateInfo
wrapper.infoWidgetEl = info;
wrapper.appendChild(btnPrevWrapper);
wrapper.appendChild(info);
wrapper.appendChild(btnNextWrapper);
return wrapper;
}
function createPromptWidget(txts, imgs, state) {
//console.log("[pseudocomfy] createPromptWidget");
//console.log("[pseudocomfy] txts:", txts);
//console.log("[pseudocomfy] imgs:", imgs);
const wrapper = document.createElement("div");
wrapper.style.width = "100%";
wrapper.style.margin = "8px 0";
wrapper.style.flex = "1 1 0";
wrapper.style.display = "flex";
wrapper.style.flexDirection = "column";
// Defensive: ensure txts and imgs are arrays of the same length
const idx = state.idx;
const txt = (Array.isArray(txts) && txts.length > idx) ? txts[idx] : "";
const imgSrc = (Array.isArray(imgs) && imgs.length > idx) ? imgs[idx] : null;
// If no image, use original layout (textarea only)
if (!imgSrc) {
const textarea = document.createElement("textarea");
textarea.readOnly = true;
textarea.style.width = "100%";
textarea.style.height = "100%";
textarea.style.flex = "1 1 0";
textarea.style.resize = "none";
textarea.style.background = "#f8f8f8";
textarea.style.color = "#333";
textarea.style.fontSize = "0.95em";
textarea.style.border = "1px solid #ccc";
textarea.style.borderRadius = "4px";
textarea.style.padding = "4px";
textarea.value = txt;
wrapper.appendChild(textarea);
// Expose textarea for updateInfo
Object.defineProperty(wrapper, "value", {
get: () => textarea.value,
set: v => { textarea.value = v; }
});
return wrapper;
}
// If image is present, use side-by-side layout
wrapper.style.flexDirection = "row";
wrapper.style.alignItems = "stretch";
wrapper.style.gap = "8px";
// Textarea (left)
const textarea = document.createElement("textarea");
textarea.readOnly = true;
textarea.style.width = "100%";
textarea.style.height = "100%";
textarea.style.flex = "1 1 0";
textarea.style.resize = "none";
textarea.style.background = "#f8f8f8";
textarea.style.color = "#333";
textarea.style.fontSize = "0.95em";
textarea.style.border = "1px solid #ccc";
textarea.style.borderRadius = "4px";
textarea.style.padding = "4px";
textarea.value = txt;
// Image (right)
const imgDiv = document.createElement("div");
imgDiv.style.display = "flex";
imgDiv.style.alignItems = "center";
imgDiv.style.justifyContent = "center";
imgDiv.style.height = "100%";
imgDiv.style.flex = "0 0 128px"; // Fixed width for image column
imgDiv.style.maxWidth = "128px";
imgDiv.style.minWidth = "64px";
const img = document.createElement("img");
img.src = imgSrc;
img.style.maxWidth = "100%";
img.style.maxHeight = "128px";
img.style.display = "block";
img.style.objectFit = "contain";
imgDiv.appendChild(img);
wrapper.appendChild(textarea);
wrapper.appendChild(imgDiv);
// Expose textarea for updateInfo
Object.defineProperty(wrapper, "value", {
get: () => textarea.value,
set: v => { textarea.value = v; }
});
return wrapper;
}