Files
2026-08-24 13:49:00 -04:00

115 lines
4.2 KiB
JavaScript

import { app } from "../../scripts/app.js";
/**
* Prompt override widgets for the RM Styles nodes.
*
* Two problems solved here, both about keeping the node small and honest:
*
* 1. A multiline widget is ~6 rows tall. Two of them would roughly double the node for a
* feature most graphs never use, so each text box is hidden whenever its mode is Off.
* The mode selector stays visible, so the node always states what is active - the cost
* is two combo rows, and nothing when idle.
*
* 2. Positive = Replace bypasses the style library entirely. The selection widgets then
* do nothing, so they are greyed out rather than left looking operational. A control
* that appears live but is ignored is worse than one that is visibly switched off.
*/
const NODES = ["RMStylesFull", "RMStylesFullDisplay", "RMStylesPipe"];
// Widgets that only mean something when a style is actually being selected.
const SELECTION_WIDGETS = ["mode", "previous_prompt", "next_prompt", "minimum", "maximum", "custom_json_path"];
const HIDDEN_TYPE = "rmcp-hidden";
function hide(widget) {
if (widget.type === HIDDEN_TYPE) return;
widget._origType = widget.type;
widget._origComputeSize = widget.computeSize;
widget.type = HIDDEN_TYPE;
widget.computeSize = () => [0, -4]; // -4 cancels the inter-widget gap
if (widget.inputEl) widget.inputEl.style.display = "none";
}
function show(widget) {
if (widget.type !== HIDDEN_TYPE) return;
widget.type = widget._origType;
widget.computeSize = widget._origComputeSize;
if (widget.inputEl) widget.inputEl.style.display = "";
}
function setDisabled(widget, off) {
// ComfyUI honours `disabled` on standard widgets; the label tweak is a belt-and-braces
// cue for themes and widget types that ignore it.
widget.disabled = off;
if (off) {
if (!widget._origLabel) widget._origLabel = widget.label ?? widget.name;
widget.label = `${widget._origLabel} · unused`;
} else if (widget._origLabel) {
widget.label = widget._origLabel;
}
}
function applyState(node) {
const get = (n) => node.widgets?.find((w) => w.name === n);
const posMode = get("positive_mode");
const negMode = get("negative_mode");
const posText = get("positive_text");
const negText = get("negative_text");
if (!posMode || !negMode) return;
// Text boxes follow their own mode.
if (posText) (posMode.value === "Off" ? hide : show)(posText);
if (negText) (negMode.value === "Off" ? hide : show)(negText);
// Only the POSITIVE being Replace bypasses the library. Replacing just the negative
// still needs a style selected, so the selection widgets stay live in that case.
const freePrompt = posMode.value === "Replace";
for (const name of SELECTION_WIDGETS) {
const w = get(name);
if (w) setDisabled(w, freePrompt);
}
node.setDirtyCanvas(true, true);
const size = node.computeSize();
// Grow to fit, never shrink below what the user has dragged to.
node.setSize([Math.max(node.size[0], size[0]), Math.max(size[1], node.size[1] === undefined ? 0 : size[1])]);
}
app.registerExtension({
name: "RMAutomation.StylesOverrides",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (!NODES.includes(nodeData.name)) return;
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
onNodeCreated?.apply(this, arguments);
const node = this;
for (const name of ["positive_mode", "negative_mode"]) {
const w = node.widgets?.find((x) => x.name === name);
if (!w) continue;
const prev = w.callback;
w.callback = function () {
const r = prev?.apply(this, arguments);
applyState(node);
return r;
};
}
// Defer one frame: widgets from a loaded workflow are not all attached yet at
// onNodeCreated, so measuring or hiding now would use stale state.
requestAnimationFrame(() => applyState(node));
};
// Re-apply after a saved graph restores widget values.
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
const r = onConfigure?.apply(this, arguments);
requestAnimationFrame(() => applyState(this));
return r;
};
},
});