Files
2025-10-15 01:26:55 +02:00

149 lines
6.1 KiB
JavaScript

// Developed by Light-x02
// https://github.com/Light-x02/ComfyUI-Lightx02-Node
// ----- SECTION: Imports -----
import { app } from "/scripts/app.js";
// ----- SECTION: Register Extension -----
(function () {
const EXT_NAME = "LinkColors.ContextToggle.IOAwareAttachDetach.Separated";
const STORAGE_KEY = "linkcolors_enabled";
// ----- SECTION: State -----
const State = {
enabled: (() => { try { return localStorage.getItem(STORAGE_KEY) !== "0"; } catch { return true; } })(),
};
// ----- SECTION: Color Helpers -----
function getOutputType(canvas, link) {
const g = canvas?.graph; if (!g || !link) return null;
const on = g.getNodeById(link.origin_id);
const o = on?.outputs?.[link.origin_slot];
return o?.type || o?.name || null;
}
function getInputType(canvas, link) {
const g = canvas?.graph; if (!g || !link) return null;
const tn = g.getNodeById(link.target_id);
const i = tn?.inputs?.[link.target_slot];
return i?.type || i?.name || null;
}
function isGenericType(t) {
if (!t) return true;
const s = String(t).toUpperCase();
return s === "*" || s === "ANY" || s === "ANYTYPE" || s === "ANY_TYPE" || s === "WILDCARD";
}
function getTypeColorFromMap(type) {
const map = (typeof LGraphCanvas !== "undefined" && LGraphCanvas.link_type_colors) ? LGraphCanvas.link_type_colors : {};
if (!type) return null;
if (Object.prototype.hasOwnProperty.call(map, type)) {
const c = map[type];
if (c === "") return (typeof LiteGraph !== "undefined" && LiteGraph.LINK_COLOR) ? LiteGraph.LINK_COLOR : "#6f6";
return c || null;
}
return null;
}
function getTypeColor(type) {
return getTypeColorFromMap(type) ?? getTypeColorFromMap("*");
}
function parseColor(c) {
if (!c || typeof c !== "string") return null;
const s = c.replace(/\s+/g, "");
if (s[0] === "#") {
let r, g, b;
if (s.length === 4) { r = parseInt(s[1] + s[1], 16); g = parseInt(s[2] + s[2], 16); b = parseInt(s[3] + s[3], 16); return [r, g, b]; }
if (s.length === 7) { r = parseInt(s.slice(1, 3), 16); g = parseInt(s.slice(3, 5), 16); b = parseInt(s.slice(5, 7), 16); return [r, g, b]; }
return null;
}
const m = s.match(/^rgb\((\d+),(\d+),(\d+)\)$/i);
if (m) return [Number(m[1]), Number(m[2]), Number(m[3])];
return null;
}
function toHex([r, g, b]) {
const h = (n) => n.toString(16).padStart(2, "0");
return `#${h(Math.max(0, Math.min(255, r)))}${h(Math.max(0, Math.min(255, g)))}${h(Math.max(0, Math.min(255, b)))}`;
}
function blendColors(c1, c2) {
const a = parseColor(c1), b = parseColor(c2);
if (!a && !b) return null;
if (!a) return c2;
if (!b) return c1;
return toHex([Math.round((a[0] + b[0]) / 2), Math.round((a[1] + b[1]) / 2), Math.round((a[2] + b[2]) / 2)]);
}
function resolveColor(canvas, link, fallbackColor) {
const outType = getOutputType(canvas, link) || link?.type || link?.data_type || null;
const inType = getInputType(canvas, link) || null;
const outGeneric = isGenericType(outType);
const inGeneric = isGenericType(inType);
const outColor = outGeneric ? null : getTypeColor(outType);
const inColor = inGeneric ? null : getTypeColor(inType);
if (outColor && inColor) {
if (outColor === inColor) return outColor;
const mixed = blendColors(outColor, inColor);
if (mixed) return mixed;
}
if (outColor) return outColor;
if (inColor) return inColor;
if (fallbackColor) return fallbackColor;
return (typeof LiteGraph !== "undefined" && LiteGraph.LINK_COLOR) ? LiteGraph.LINK_COLOR : "#6f6";
}
// ----- SECTION: Patch Attach/Detach -----
let ORIGINAL_RENDER = null;
function ensureOriginal() {
if (!ORIGINAL_RENDER) ORIGINAL_RENDER = LiteGraph.LGraphCanvas.prototype.renderLink;
}
function attachPatch() {
ensureOriginal();
if (LiteGraph.LGraphCanvas.prototype.renderLink.__ioaware_patched__) return;
const PATCHED = function (ctx, a, b, link, skip_border, flow, color, start_dir, end_dir) {
const forced = resolveColor(this, link, color);
return ORIGINAL_RENDER.call(this, ctx, a, b, link, skip_border, flow, forced, start_dir, end_dir);
};
PATCHED.__ioaware_patched__ = true;
LiteGraph.LGraphCanvas.prototype.renderLink = PATCHED;
}
function detachPatch() {
if (!ORIGINAL_RENDER) return;
LiteGraph.LGraphCanvas.prototype.renderLink = ORIGINAL_RENDER;
}
// ----- SECTION: Toggle API -----
function setEnabled(v) {
State.enabled = !!v;
try { localStorage.setItem(STORAGE_KEY, State.enabled ? "1" : "0"); } catch { }
if (State.enabled) attachPatch(); else detachPatch();
if (app?.canvas) { app.canvas.setDirty(true, true); app.canvas.draw(true, true); }
}
function toggle() { setEnabled(!State.enabled); }
// ----- SECTION: Context Menu -----
let ORIGINAL_MENU = null;
function installCanvasMenu() {
if (ORIGINAL_MENU) return;
ORIGINAL_MENU = LiteGraph.LGraphCanvas.prototype.getCanvasMenuOptions;
LiteGraph.LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = ORIGINAL_MENU ? ORIGINAL_MENU.apply(this, arguments) : [];
options.push(null);
options.push({ content: "Lightx02", disabled: true });
options.push({ content: `${State.enabled ? "✅" : "❌"} 🎨 Link Colors (I/O)`, callback: () => toggle() });
options.push(null);
return options;
};
}
// ----- SECTION: Setup -----
app.registerExtension({
name: EXT_NAME,
setup() {
installCanvasMenu();
if (State.enabled) attachPatch(); else detachPatch();
},
});
})();