Files
sn0w12-ComfyUI-Sn0w-Scripts/web/js/characterNode.js
T
2025-06-29 23:44:28 +02:00

148 lines
5.1 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import { $el } from "../../../../../../../../../../../../../../../../scripts/ui.js";
const linkSetting = (name, setter, value, attrs) => {
const link = $el("a", {
href: attrs.href || "#",
target: attrs.target || "_blank",
textContent: attrs.text || "Open Link",
style: {
color: "#007bff",
textDecoration: "underline",
cursor: "pointer",
display: "block",
padding: "8px 0",
},
});
return link;
};
app.registerExtension({
name: "sn0w.CustomCharacterNode",
init() {
app.ui.settings.addSetting({
id: "sn0w.CharacterSettings.ManageVisibleSeries",
name: "Manage Visible Series",
type: linkSetting,
attrs: {
href: "/sn0w/series_selector",
target: "_blank",
text: "Manage Visible Series",
},
});
},
async setup() {
const existingContextMenu = LiteGraph.ContextMenu;
LiteGraph.ContextMenu = function (values, options) {
if (values[values.length - 1] != "SN0W_CHARACTER_SELECTOR") {
return existingContextMenu.call(this, values, options);
}
const originalCallback = options.callback;
const seriesGroups = {};
values.slice(1).forEach((value) => {
const match = value.match(/^(.+?)\s*\((.+?)\)$/);
if (match) {
const [, , seriesName] = match;
if (!seriesGroups[seriesName]) {
seriesGroups[seriesName] = [];
}
seriesGroups[seriesName].push(value);
}
});
const newCallback = (item, options) => {
if (originalCallback && item && item.content) {
originalCallback(item.content, options);
}
};
const serializeSubmenus = (submenu, depth = 0) => {
if (!submenu || !submenu.options) return "";
const items = submenu.options.map((option) => {
const content = typeof option === "string" ? option : option.content;
if (option.submenu && option.submenu.options) {
const nestedItems = serializeSubmenus(option.submenu, depth + 1);
return `${content}::{${nestedItems}}`;
}
return content;
});
return items.join("|");
};
const createMenuItem = (content, hasSubmenu = false, submenu = null) => {
const item = {
content: content,
disabled: false,
callback: newCallback,
toString: () => {
if (hasSubmenu && submenu && submenu.options) {
const serializedSubmenus = serializeSubmenus(submenu);
return `${content}::{${serializedSubmenus}}`;
}
return content;
},
};
if (hasSubmenu) {
item.has_submenu = true;
item.submenu = submenu;
}
return item;
};
const createNestedMenuItems = (data, level = 0) => {
if (typeof data === "string") {
return {
content: data,
callback: newCallback,
toString: () => data,
};
}
if (Array.isArray(data)) {
return data.map((item) => createNestedMenuItems(item, level));
}
if (data && typeof data === "object" && data.submenu) {
const submenuOptions = createNestedMenuItems(data.submenu, level + 1);
return createMenuItem(data.content, true, {
options: Array.isArray(submenuOptions) ? submenuOptions : [submenuOptions],
callback: newCallback,
});
}
return data;
};
const hierarchicalValues = [createMenuItem("None")];
Object.keys(seriesGroups)
.sort()
.forEach((seriesName) => {
const submenuOptions = seriesGroups[seriesName].map((characterName) => ({
content: characterName,
callback: newCallback,
toString: () => characterName,
}));
hierarchicalValues.push(
createMenuItem(seriesName, true, {
options: submenuOptions,
callback: newCallback,
})
);
});
options.callback = undefined;
existingContextMenu.call(this, hierarchicalValues, options);
};
},
});