- rename Smart Category Config → Smart Profile Switch 🧩 (code, JS, locales, demo workflow)
- add banner.svg and embed it at the top of README
- replace fragile OpenFileDialog folder-browse hack with native IFileDialog/FOS_PICKFOLDERS picker — fixes wrong-folder bug on single-click select
- flip Smart Resize's upscale_if_smaller default to true — node now resizes to target by default, as the name implies
- add width/height source tooltip to Smart Resolution Multiplier's image input
- fix stale widgets_values in smart_batch_resize_demo.json (10 fields → 13 after prefix/counter fields were added)
- full README rewrite: correct inputs/defaults/outputs for all 5 nodes, new Smart Profile Switch section, Example workflows section, expanded "Why these nodes?"
- add new example workflow (seedvr2_smartresize_demo.json), updated screenshots
- add assets/*.ai to .gitignore
- bump version to 1.0.2
106 lines
4.1 KiB
JavaScript
106 lines
4.1 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
|
|
app.registerExtension({
|
|
name: "Craftopia.SmartProfileSwitch",
|
|
|
|
async nodeCreated(node) {
|
|
if (node.comfyClass !== "SmartProfileSwitch") return;
|
|
|
|
// Slot header dividers — visual only, not serialized
|
|
const dividers = [];
|
|
for (let i = 1; i <= 4; i++) {
|
|
const div = node.addWidget("button", `_div_${i}`, null, () => {}, { serialize: false });
|
|
div.serialize = false;
|
|
const num = i;
|
|
div.draw = function (ctx, node, widgetWidth, y, height) {
|
|
const lx = 14;
|
|
const label = `SLOT ${num}`;
|
|
ctx.save();
|
|
ctx.font = "bold 10px sans-serif";
|
|
ctx.textBaseline = "middle";
|
|
ctx.textAlign = "left";
|
|
ctx.fillStyle = "#888";
|
|
ctx.fillText(label, lx, y + height / 2);
|
|
const lineX = lx + ctx.measureText(label).width + 8;
|
|
ctx.beginPath();
|
|
ctx.moveTo(lineX, y + height / 2);
|
|
ctx.lineTo(widgetWidth - 14, y + height / 2);
|
|
ctx.strokeStyle = "#333";
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
};
|
|
dividers.push(div);
|
|
}
|
|
|
|
// Status widget — shows active slot after execution
|
|
const statusWidget = node.addWidget("button", "_sps_status", null, () => {}, { serialize: false });
|
|
statusWidget.serialize = false;
|
|
statusWidget._text = "";
|
|
|
|
statusWidget.draw = function (ctx, node, widgetWidth, y, height) {
|
|
const margin = 14;
|
|
const innerW = widgetWidth - margin * 2;
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
if (ctx.roundRect) ctx.roundRect(margin, y + 2, innerW, height - 4, 4);
|
|
else ctx.rect(margin, y + 2, innerW, height - 4);
|
|
ctx.fillStyle = "#111";
|
|
ctx.fill();
|
|
ctx.strokeStyle = this._text ? "#3a3a3a" : "#222";
|
|
ctx.lineWidth = 1;
|
|
ctx.stroke();
|
|
ctx.font = "11px sans-serif";
|
|
ctx.textAlign = "center";
|
|
ctx.textBaseline = "middle";
|
|
ctx.fillStyle = this._text ? "#f28f41" : "#666";
|
|
ctx.fillText(
|
|
this._text || "— RUN TO SEE RESULT —",
|
|
widgetWidth / 2,
|
|
y + height / 2
|
|
);
|
|
ctx.restore();
|
|
};
|
|
|
|
// Reorder: index, [div1, label_1, w_1, h_1], ..., [div4, label_4, w_4, h_4], status
|
|
const indexWidget = node.widgets.find(w => w.name === "index");
|
|
const slotWidgets = [];
|
|
for (let i = 1; i <= 4; i++) {
|
|
slotWidgets.push(
|
|
dividers[i - 1],
|
|
node.widgets.find(w => w.name === `label_${i}`),
|
|
node.widgets.find(w => w.name === `width_${i}`),
|
|
node.widgets.find(w => w.name === `height_${i}`),
|
|
);
|
|
}
|
|
node.widgets = [indexWidget, ...slotWidgets, statusWidget];
|
|
|
|
// Serialize override — keeps non-serializable widgets out of widgets_values
|
|
const origSerialize = node.serialize;
|
|
node.serialize = function () {
|
|
const data = origSerialize.call(this);
|
|
if (data.widgets_values) {
|
|
data.widgets_values = this.widgets
|
|
.filter(w => w.serialize !== false && w.options?.serialize !== false)
|
|
.map(w => w.value);
|
|
}
|
|
return data;
|
|
};
|
|
|
|
// onExecuted — update status, hide default customtext textarea
|
|
const origOnExecuted = node.onExecuted;
|
|
node.onExecuted = function (output) {
|
|
origOnExecuted?.call(this, output);
|
|
if (output?.text?.[0]) {
|
|
statusWidget._text = output.text[0];
|
|
for (const w of node.widgets) {
|
|
if (w !== statusWidget && w.type === "customtext") {
|
|
w.computeSize = () => [0, -4];
|
|
}
|
|
}
|
|
}
|
|
node.setDirtyCanvas(true);
|
|
};
|
|
},
|
|
});
|