Files
JeyzerMC-comfyui-flakes/web/widgets/flake-model-combo.js
T

162 lines
6.8 KiB
JavaScript

import { css } from "../utils.js";
import { openPresetPicker } from "../pickers.js";
export function makeModelComboBlock({ preset, idx, isActive, onActivate, onRemove }) {
const block = document.createElement("div");
block.dataset.idx = String(idx);
css(block, `position:relative;height:80px;background:${
isActive ? "#2a4a3a" : "#2a2a2a"
};border:2px solid ${
isActive ? "#3a8a5a" : "#444"
};border-radius:4px;cursor:pointer;font-size:11px;color:#ddd;user-select:none;box-sizing:border-box;background-image:url(/flakes/preset_cover?name=${encodeURIComponent(preset)});background-size:cover;background-position:center;`);
// Dark overlay for cover readability
const overlay = document.createElement("div");
css(overlay, "position:absolute;inset:0;background:rgba(0,0,0,0.45);pointer-events:none;z-index:0;");
block.appendChild(overlay);
const nameEl = document.createElement("div");
nameEl.title = preset;
css(nameEl, "position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:500;text-align:center;line-height:1.2;text-shadow:0 1px 3px rgba(0,0,0,0.8);padding:0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:1;");
nameEl.textContent = preset;
block.appendChild(nameEl);
if (isActive) {
const check = document.createElement("div");
check.textContent = "\u2713";
css(check, "position:absolute;top:2px;left:2px;width:16px;height:16px;line-height:14px;text-align:center;font-size:10px;background:rgba(58,138,90,0.8);color:#fff;border-radius:2px;z-index:2;");
block.appendChild(check);
}
const rm = document.createElement("button");
rm.textContent = "\u2715";
rm.title = "Remove from combo";
css(rm, "position:absolute;top:2px;right:2px;width:16px;height:16px;line-height:14px;text-align:center;font-size:10px;background:rgba(0,0,0,0.5);color:#ddd;border:1px solid #555;border-radius:2px;cursor:pointer;padding:0;z-index:2;");
rm.addEventListener("click", (e) => { e.stopPropagation(); onRemove(idx); });
block.appendChild(rm);
block.addEventListener("click", () => onActivate(idx));
return block;
}
export function setupFlakeModelComboWidget(node) {
const presetWidget = node.widgets?.find(w => w.name === "preset");
const familyWidget = node.widgets?.find(w => w.name === "model_family");
if (!presetWidget) return;
// Move hidden preset widget to end of widgets array
const presetIdx = node.widgets.indexOf(presetWidget);
if (presetIdx !== -1) {
node.widgets.splice(presetIdx, 1);
node.widgets.push(presetWidget);
}
presetWidget.computeSize = () => [0, 0];
presetWidget.type = "hidden";
presetWidget.hidden = true;
if (presetWidget.element) presetWidget.element.style.display = "none";
if (presetWidget.inputEl) presetWidget.inputEl.style.display = "none";
function getFamily() {
return familyWidget?.value || "SDXL/Base";
}
if (!node.properties) node.properties = {};
if (!node.properties._combo_presets) node.properties._combo_presets = [];
if (node.properties._combo_active_index == null) node.properties._combo_active_index = 0;
function readPresets() {
return node.properties._combo_presets || [];
}
function writePresets(presets) {
node.properties._combo_presets = presets;
updateActivePreset();
}
function updateActivePreset() {
const presets = readPresets();
const idx = node.properties._combo_active_index || 0;
const active = presets[idx] || "Select a preset...";
presetWidget.value = active;
}
const container = document.createElement("div");
css(container, "display:flex;flex-direction:column;gap:2px;padding:0 6px 3px 6px;font-size:12px;color:#ddd;");
const grid = document.createElement("div");
css(grid, "display:grid;grid-template-columns:repeat(auto-fill, minmax(72px, 1fr));gap:4px;");
container.appendChild(grid);
function render() {
const presets = readPresets();
const activeIdx = node.properties._combo_active_index || 0;
grid.replaceChildren();
for (let i = 0; i < presets.length; i++) {
const blk = makeModelComboBlock({
preset: presets[i],
idx: i,
isActive: i === activeIdx,
onActivate: (idx) => {
node.properties._combo_active_index = idx;
updateActivePreset();
render();
},
onRemove: (idx) => {
const arr = readPresets();
arr.splice(idx, 1);
if (node.properties._combo_active_index >= arr.length) {
node.properties._combo_active_index = Math.max(0, arr.length - 1);
}
writePresets(arr);
render();
},
});
grid.appendChild(blk);
}
const addBtn = document.createElement("div");
css(addBtn, "position:relative;height:80px;background:#2a2a2a;border:1px dashed #555;border-radius:4px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;cursor:pointer;font-size:11px;color:#999;user-select:none;box-sizing:border-box;");
const icon = document.createElement("div");
css(icon, "font-size:20px;font-weight:300;color:#666;line-height:1;");
icon.textContent = "+";
addBtn.appendChild(icon);
const label = document.createElement("div");
css(label, "font-size:9px;text-align:center;");
label.textContent = "Add preset";
addBtn.appendChild(label);
addBtn.addEventListener("click", async () => {
const result = await openPresetPicker({ family: getFamily() });
if (!result || !result.name) return;
const arr = readPresets();
if (arr.includes(result.name)) {
window.alert("Preset already in combo");
return;
}
arr.push(result.name);
writePresets(arr);
render();
});
grid.appendChild(addBtn);
}
// Hook into native family widget changes
if (familyWidget) {
const origCallback = familyWidget.callback;
familyWidget.callback = function (value) {
const r = origCallback?.apply(this, arguments);
render();
return r;
};
}
node._model_combo_render = render;
const comboWidget = node.addDOMWidget("model_combo_ui", "div", container, { serialize: false, margin: 4 });
comboWidget.computeSize = () => {
const rows = Math.max(1, Math.ceil((readPresets().length + 1) / 2));
return [node.size[0], rows * 84 + 31];
};
updateActivePreset();
render();
}