Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uM7Ru1JYn48n6c5Rzwd99
115 lines
4.2 KiB
JavaScript
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;
|
|
};
|
|
},
|
|
});
|