148 lines
5.1 KiB
JavaScript
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);
|
|
};
|
|
},
|
|
});
|