diff --git a/README.en.md b/README.en.md
index 0ccab0d..abb9559 100644
--- a/README.en.md
+++ b/README.en.md
@@ -33,8 +33,7 @@
**v1.1.2 (2024/3/25)**
-PS: Please update [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) to v2, and moved v1 models to **ComfyUI\models\ipadapter** (Otherwise, the latest model is automatically downloaded from Huggingface)
-
+- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews
- Added `easy sv3dLoader`
- Added `easy dynamiCrafterLoader`
- Added `easy ipadapterApply`
diff --git a/README.md b/README.md
index b5a31e4..a72b2e2 100644
--- a/README.md
+++ b/README.md
@@ -37,8 +37,7 @@
**v1.1.2 (2024/3/25)**
-PS: 请更新至最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus), 并移动v1版本模型文件至 ComfyUI\models\ipadapter (否则会自动从huggingface下载最新模型)
-
+- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图
- 增加 `easy sv3dLoader`
- 增加 `easy dynamiCrafterLoader`
- 增加 `easy ipadapterApply`
diff --git a/py/api.py b/py/api.py
index d103511..e3ff12d 100644
--- a/py/api.py
+++ b/py/api.py
@@ -3,6 +3,7 @@ import os
import torch
import sys
import json
+import folder_paths
from server import PromptServer
from .config import RESOURCES_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_STYLES_SAMPLES
from .easyNodes import easyCache
@@ -111,6 +112,22 @@ async def getModelsList(request):
else:
return web.Response(status=400)
+# get models thumbnails
+@PromptServer.instance.routes.get("/easyuse/models/thumbnail")
+async def getModelsThumbnail(request):
+ checkpoints = folder_paths.get_filename_list("checkpoints_thumb")
+ loras = folder_paths.get_filename_list("loras_thumb")
+ checkpoints_full = []
+ loras_full = []
+ for index, i in enumerate(checkpoints):
+ full_path = folder_paths.get_full_path('checkpoints_thumb', str(i))
+ if full_path:
+ checkpoints_full.append(full_path)
+ for index, i in enumerate(loras):
+ full_path = folder_paths.get_full_path('loras_thumb', str(i))
+ if full_path:
+ loras_full.append(full_path)
+ return web.json_response(checkpoints_full + loras_full)
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
\ No newline at end of file
diff --git a/py/easyNodes.py b/py/easyNodes.py
index 37bfb2d..0820363 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -30,6 +30,8 @@ from .libs.easing import EasingBase
sampler = easySampler()
easyCache = easyLoader()
+image_suffixs = set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".tiff", ".svg", ".ico", ".apng", ".tif", ".hdr", ".exr"])
+
model_path = folder_paths.models_dir
add_folder_path_and_extensions("ultralytics_bbox", [os.path.join(model_path, "ultralytics", "bbox")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("ultralytics_segm", [os.path.join(model_path, "ultralytics", "segm")], folder_paths.supported_pt_extensions)
@@ -45,6 +47,9 @@ add_folder_path_and_extensions("rembg", [os.path.join(model_path, "rembg")], fol
add_folder_path_and_extensions("ipadapter", [os.path.join(model_path, "ipadapter")], folder_paths.supported_pt_extensions)
add_folder_path_and_extensions("dynamicrafter_models", [os.path.join(model_path, "dynamicrafter_models")], folder_paths.supported_pt_extensions)
+add_folder_path_and_extensions("checkpoints_thumb", [os.path.join(model_path, "checkpoints")], image_suffixs)
+add_folder_path_and_extensions("loras_thumb", [os.path.join(model_path, "loras")], image_suffixs)
+
# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------#
# 正面提示词
diff --git a/web/css/contextmenu.css b/web/css/contextmenu.css
new file mode 100644
index 0000000..9fc0e95
--- /dev/null
+++ b/web/css/contextmenu.css
@@ -0,0 +1,20 @@
+.easyuse-model{
+ position:relative;
+}
+.easyuse-model:hover img{
+ display: block;
+ opacity: 1;
+}
+.easyuse-model img{
+ position: absolute;
+ z-index:1;
+ right:-155px;
+ top:0;
+ width:150px;
+ height:auto;
+ display: none;
+ filter:brightness(70%);
+ -webkit-filter: brightness(70%);
+ opacity: 0;
+ transition:all 0.5s ease-in-out;
+}
\ No newline at end of file
diff --git a/web/css/index.css b/web/css/index.css
index fd11b8e..2069ddf 100644
--- a/web/css/index.css
+++ b/web/css/index.css
@@ -1,4 +1,5 @@
@import "theme.css";
@import "dropdown.css";
@import "selector.css";
-@import "groupmap.css";
\ No newline at end of file
+@import "groupmap.css";
+@import "contextmenu.css";
\ No newline at end of file
diff --git a/web/js/common/i18n.js b/web/js/common/i18n.js
index 1673a07..ce26b0d 100644
--- a/web/js/common/i18n.js
+++ b/web/js/common/i18n.js
@@ -10,8 +10,9 @@ const zhCN = {
"Auto Sorting": "自动排序",
"Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never": "点击`启用中/已忽略`可设置组模式, 长按可停用该组节点",
// Quick
- "Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流(ComfyUI-Easy-Use)",
- "Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条(ComfyUI-Easy-Use)"
+ "Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)": "启用ALT1~9从节点模板粘贴到工作流 (ComfyUI-Easy-Use)",
+ "Enable process bar in queue button (ComfyUI-Easy-Use)": "启用提示词队列进度显示条 (ComfyUI-Easy-Use)",
+ "Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)": "启用上下文菜单自动嵌套子目录 (ComfyUI-Easy-Use)"
}
export const $t = (key) => {
diff --git a/web/js/common/utils.js b/web/js/common/utils.js
index 4ba496e..56ddf0e 100644
--- a/web/js/common/utils.js
+++ b/web/js/common/utils.js
@@ -38,4 +38,11 @@ export function deepEqual(obj1, obj2) {
export function getLocale(){
const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
return locale
+}
+
+export function spliceExtension(fileName){
+ return fileName.substring(0,fileName.lastIndexOf('.'))
+}
+export function getExtension(fileName){
+ return fileName.substring(fileName.lastIndexOf('.') + 1)
}
\ No newline at end of file
diff --git a/web/js/easy/easyContextMenu.js b/web/js/easy/easyContextMenu.js
index 2e07938..511405f 100644
--- a/web/js/easy/easyContextMenu.js
+++ b/web/js/easy/easyContextMenu.js
@@ -1,116 +1,128 @@
-// import {app} from "/scripts/app.js";
-// import {api} from "/scripts/api.js";
-// import {$el} from "/scripts/ui.js";
-//
-// let script=document.createElement("script");
-// script.type="text/JavaScript";
-// script.innerHTML = `
-// function displayImage(e) {
-// console.log(e)
-// }
-// `
-// document.getElementsByTagName('head')[0].appendChild(script);
-//
-// const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader']
-// app.registerExtension({
-// name:"comfy.easyUse.contextMenu",
-// async setup(){
-// const existingContextMenu = LiteGraph.ContextMenu;
-// LiteGraph.ContextMenu = function(values,options){
-// const threshold = 20;
-// const enabled = true;
-// 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}/${content}` : content;
-// // 获取图像
-// // const imgRes = api.fetchApi(`/easyuse/model/thumbnail?name=${name}`)
-// // if (imgRes.status === 200) {
-// // let data = await imgRes.json();
-// // console.log(data)
-// // }
-//
-// const newContent = $el(
-// "span.easyuse-model",
-// {
-// $: (el) => {
-// el.onmousemove = (e) => {
-// console.log(1)
-// };
-// el.onmouseout = () => {
-// console.log(2)
-// // hiddenImage()
-// };
-// el.onmouseover = (e) => {
-// console.log(1)
-// // displayImage(el.dataset.imgName, styleName)
-// };
-// },
-// },content)
-//
-//
-// return {
-// content,
-// title:newContent.outerHTML,
-// callback: newCallback
-// }
-// }
-// const newValues = [];
-// for(const [folderName,folder] of Object.entries(folders)){
-// newValues.push({
-// content:folderName,
-// has_submenu:true,
-// callback:() => {},
-// submenu:{
-// options:folder.map(f => addContent(f,folderName)),
-// }
-// });
-// }
-// newValues.push(...folderless.map(f => ({
-// content:f,
-// callback:newCallback
-// })));
-// if(specialOps.length > 0)
-// newValues.push(...specialOps.map(f => ({
-// content:f,
-// callback:newCallback
-// })));
-// return existingContextMenu.call(this,newValues,options);
-// }
-// return existingContextMenu.apply(this,[...arguments]);
-// }
-// LiteGraph.ContextMenu.prototype = existingContextMenu.prototype;
-// },
-//
-// })
-//
+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'
+
+const setting_id = "Comfy.EasyUse.MenuNestSub"
+let enableMenuNestSub = true
+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, true)
+
+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
+ }
+ const existingContextMenu = LiteGraph.ContextMenu;
+ LiteGraph.ContextMenu = function(values,options){
+ const threshold = 15;
+ 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 {},
+ submenu:{
+ options:folder.map(f => addContent(f,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;
+ },
+
+})
+