Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
175 lines
7.5 KiB
JavaScript
175 lines
7.5 KiB
JavaScript
import {app} from "../../../../scripts/app.js";
|
|
import {api} from "../../../../scripts/api.js";
|
|
import {$el} from "../../../../scripts/ui.js";
|
|
import {$t} from "../common/i18n.js";
|
|
import {getExtension, spliceExtension} from '../common/utils.js'
|
|
import {toast} from "../common/toast.js";
|
|
|
|
const setting_id = "Comfy.EasyUse.MenuNestSub"
|
|
let enableMenuNestSub = false
|
|
let thumbnails = []
|
|
|
|
export function addMenuNestSubSetting(app) {
|
|
app.ui.settings.addSetting({
|
|
id: setting_id,
|
|
name: $t("Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)"),
|
|
type: "boolean",
|
|
defaultValue: enableMenuNestSub,
|
|
onChange(value) {
|
|
enableMenuNestSub = !!value;
|
|
},
|
|
});
|
|
}
|
|
|
|
const getEnableMenuNestSub = _ => app.ui.settings.getSettingValue(setting_id, enableMenuNestSub)
|
|
|
|
|
|
const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader']
|
|
app.registerExtension({
|
|
name:"comfy.easyUse.contextMenu",
|
|
async setup(app){
|
|
addMenuNestSubSetting(app)
|
|
// 获取所有模型图像
|
|
const imgRes = await api.fetchApi(`/easyuse/models/thumbnail`)
|
|
if (imgRes.status === 200) {
|
|
let data = await imgRes.json();
|
|
thumbnails = data
|
|
}
|
|
else if(getEnableMenuNestSub()){
|
|
toast.error($t("Too many thumbnails, have closed the display"))
|
|
}
|
|
const existingContextMenu = LiteGraph.ContextMenu;
|
|
LiteGraph.ContextMenu = function(values,options){
|
|
const threshold = 10;
|
|
const enabled = getEnableMenuNestSub();
|
|
if(!enabled || (values?.length || 0) <= threshold || !(options?.callback) || values.some(i => typeof i !== 'string')){
|
|
if(enabled){
|
|
// console.log('Skipping context menu auto nesting for incompatible menu.');
|
|
}
|
|
return existingContextMenu.apply(this,[...arguments]);
|
|
}
|
|
const compatValues = values;
|
|
const originalValues = [...compatValues];
|
|
const folders = {};
|
|
const specialOps = [];
|
|
const folderless = [];
|
|
for(const value of compatValues){
|
|
const splitBy = value.indexOf('/') > -1 ? '/' : '\\';
|
|
const valueSplit = value.split(splitBy);
|
|
if(valueSplit.length > 1){
|
|
const key = valueSplit.shift();
|
|
folders[key] = folders[key] || [];
|
|
folders[key].push(valueSplit.join(splitBy));
|
|
}else if(value === 'CHOOSE' || value.startsWith('DISABLE ')){
|
|
specialOps.push(value);
|
|
}else{
|
|
folderless.push(value);
|
|
}
|
|
}
|
|
const foldersCount = Object.values(folders).length;
|
|
if(foldersCount > 0){
|
|
const oldcallback = options.callback;
|
|
options.callback = null;
|
|
const newCallback = (item,options) => {
|
|
if(['None','无','無','なし'].includes(item.content)) oldcallback('None',options)
|
|
else oldcallback(originalValues.find(i => i.endsWith(item.content),options));
|
|
};
|
|
const addContent = (content, folderName='') => {
|
|
const name = folderName ? folderName + '\\' + spliceExtension(content) : spliceExtension(content);
|
|
const ext = getExtension(content)
|
|
const time = new Date().getTime()
|
|
let thumbnail = ''
|
|
if(['ckpt', 'pt', 'bin', 'pth', 'safetensors'].includes(ext)){
|
|
for(let i=0;i<thumbnails.length;i++){
|
|
let thumb = thumbnails[i]
|
|
if(name && thumb && thumb.indexOf(name) != -1){
|
|
thumbnail = thumbnails[i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
let newContent
|
|
if(thumbnail){
|
|
const protocol = window.location.protocol
|
|
const host = window.location.host
|
|
const base_url = `${protocol}//${host}`
|
|
const thumb_url = thumbnail.replace(':','%3A').replace(/\\/g,'/')
|
|
newContent = $el("div.easyuse-model", {},[$el("span",{},content + ' *'),$el("img",{src:`${base_url}/${thumb_url}?t=${time}`})])
|
|
}else{
|
|
newContent = $el("div.easyuse-model", {},[
|
|
$el("span",{},content)
|
|
])
|
|
}
|
|
|
|
return {
|
|
content,
|
|
title:newContent.outerHTML,
|
|
callback: newCallback
|
|
}
|
|
}
|
|
const newValues = [];
|
|
const add_sub_folder = (folder, folderName) => {
|
|
let subs = []
|
|
let less = []
|
|
const b = folder.map(name=> {
|
|
const _folders = {};
|
|
const splitBy = name.indexOf('/') > -1 ? '/' : '\\';
|
|
const valueSplit = name.split(splitBy);
|
|
if(valueSplit.length > 1){
|
|
const key = valueSplit.shift();
|
|
_folders[key] = _folders[key] || [];
|
|
_folders[key].push(valueSplit.join(splitBy));
|
|
}
|
|
const foldersCount = Object.values(folders).length;
|
|
if(foldersCount > 0){
|
|
let key = Object.keys(_folders)[0]
|
|
if(key && _folders[key]) subs.push({key, value:_folders[key][0]})
|
|
else{
|
|
less.push(addContent(name,key))
|
|
}
|
|
}
|
|
return addContent(name,folderName)
|
|
})
|
|
if(subs.length>0){
|
|
let subs_obj = {}
|
|
subs.forEach(item => {
|
|
subs_obj[item.key] = subs_obj[item.key] || []
|
|
subs_obj[item.key].push(item.value)
|
|
})
|
|
return [...Object.entries(subs_obj).map(f => {
|
|
return {
|
|
content: f[0],
|
|
has_submenu: true,
|
|
callback: () => {},
|
|
submenu: {
|
|
options: add_sub_folder(f[1], f[0]),
|
|
}
|
|
}
|
|
}),...less]
|
|
}
|
|
else return b
|
|
}
|
|
|
|
for(const [folderName,folder] of Object.entries(folders)){
|
|
newValues.push({
|
|
content:folderName,
|
|
has_submenu:true,
|
|
callback:() => {},
|
|
submenu:{
|
|
options:add_sub_folder(folder,folderName),
|
|
}
|
|
});
|
|
}
|
|
newValues.push(...folderless.map(f => addContent(f, '')));
|
|
if(specialOps.length > 0) newValues.push(...specialOps.map(f => addContent(f, '')));
|
|
return existingContextMenu.call(this,newValues,options);
|
|
}
|
|
return existingContextMenu.apply(this,[...arguments]);
|
|
}
|
|
LiteGraph.ContextMenu.prototype = existingContextMenu.prototype;
|
|
},
|
|
|
|
})
|
|
|