149 lines
6.1 KiB
JavaScript
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();
|
|
},
|
|
});
|
|
})();
|
|
|
|
|