323 lines
12 KiB
JavaScript
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;
|
|
} |