diff --git a/README.en.md b/README.en.md
index 19d676f..ecfe974 100644
--- a/README.en.md
+++ b/README.en.md
@@ -47,6 +47,7 @@ you need to run `pip install -r requirements.txt` to install python dependencies
**v1.1.1 (2024/3/16)**
+- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings
- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster
**v1.1.0 (d5ff84e)**
diff --git a/README.md b/README.md
index 9256179..2ccb90f 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,6 @@
- 沿用了 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的思路,大大减少了折腾工作流的时间成本。
- UI界面美化,首次安装的用户,如需使用UI主题,请在 Settings -> Color Palette 中自行切换主题并**刷新页面**即可
- 增加了预采样参数配置的节点,可与采样节点分离,更方便预览。
-- 预采样/采样器中的随机种使用提示词队列运行前操作的方式,兼容保留了 [cg-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) 的**highlight**功能
- 支持通配符与Lora的提示词节点,如需使用Lora Block Weight用法,需先保证自定义节点包中安装了 [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack)
- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义
- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes)
@@ -31,11 +30,14 @@
- 扩展 XYplot 的可用性
- 整合了Fooocus Inpaint功能
- 整合了常用的逻辑计算、转换类型、展示所有类型等
+- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4)
## 更新日志
**v1.1.1 (2024/3/16)**
+- 修补官方BUG: 当control_mode为before 在首次加载页面时未修改节点中widget名称为 control_before_generate
+- 去除强制**control_before_generate**设定
- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快
**v1.1.0 (d5ff84e)**
diff --git a/py/easyNodes.py b/py/easyNodes.py
index cf94a5b..aff6403 100644
--- a/py/easyNodes.py
+++ b/py/easyNodes.py
@@ -78,7 +78,7 @@ class wildcardsPrompt:
"text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}),
"Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),),
"Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -455,7 +455,7 @@ class latentNoisy:
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"end_at_step": ("INT", {"default": 10000, "min": 1, "max": 10000}),
"source": (["CPU", "GPU"],),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
"optional": {
"pipe": ("PIPE_LINE",),
@@ -596,12 +596,12 @@ class easySeed:
def INPUT_TYPES(s):
return {
"required": {
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
- RETURN_TYPES = ("INT",)
+ RETURN_TYPES = ("INT:seed",)
RETURN_NAMES = ("seed_num",)
FUNCTION = "doit"
@@ -1744,7 +1744,7 @@ class samplerSettings:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
@@ -1819,7 +1819,7 @@ class samplerSettingsAdvanced:
"start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}),
"end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}),
"add_noise": (["enable", "disable"],),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent": ("IMAGE",),
@@ -1895,7 +1895,7 @@ class samplerSettingsNoiseIn:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"optional_noise_seed": ("INT",{"forceInput": True}),
@@ -2052,7 +2052,7 @@ class sdTurboSettings:
"unsharp_kernel_size": ("INT", {"default": 3, "min": 1, "max": 21, "step": 1}),
"unsharp_sigma": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
"unsharp_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"},
}
@@ -2145,7 +2145,7 @@ class cascadeSettings:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image_to_latent_c": ("IMAGE",),
@@ -2250,7 +2250,7 @@ class layerDiffusionSettings:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler_ancestral"}),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "simple"}),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
"image": ("IMAGE",),
@@ -2410,7 +2410,7 @@ class dynamicCFGSettings:
"sampler_name": (comfy.samplers.KSampler.SAMPLERS,),
"scheduler": (comfy.samplers.KSampler.SCHEDULERS,),
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional":{
"image_to_latent": ("IMAGE",),
@@ -2554,7 +2554,7 @@ class samplerFull(LayerDiffuse):
"save_prefix": ("STRING", {"default": "ComfyUI"}),
},
"optional": {
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
"model": ("MODEL",),
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
@@ -3254,7 +3254,7 @@ class samplerCascadeFull:
"image_output": (["Hide", "Preview", "Save", "Hide/Save", "Sender", "Sender/Save"],),
"link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}),
"save_prefix": ("STRING", {"default": "ComfyUI"}),
- "seed_num": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
+ "seed_num": ("INT:seed", {"default": 0, "min": 0, "max": MAX_SEED_NUM}),
},
"optional": {
diff --git a/py/server.py b/py/server.py
index f66a6d8..43aee8f 100644
--- a/py/server.py
+++ b/py/server.py
@@ -123,40 +123,40 @@ def prompt_seed_update(json_data):
# control after generated
if mode is not None and not mode:
control_seed(node[1], action, seed_is_global)
- else:
- prompts = json_data['prompt'].items()
- for k, v in prompts:
- if 'class_type' not in v:
- continue
- cls = v['class_type']
- if cls in seed_nodes:
- extra_data = next((x for x in workflow["nodes"] if str(x["id"]) == k), None)
- if extra_data is not None:
- inputs = extra_data.get('inputs')
- widgets_value = extra_data.get('widgets_values')
- widgets_length = len(widgets_value)
- if "disable" in widgets_value:
- break
- if inputs is not None and inputs != []:
- seed_num_input = next((x for x in inputs if x['name'] == 'seed_num' and x['type'] == 'INT'), None)
- if seed_num_input is not None:
- action = 'fixed'
- else:
- action = widgets_value[widgets_length - 1]
- else:
- control_index = widgets_length - 2 if cls == 'easy seed' else widgets_length - 1
- action = widgets_value[control_index]
-
- # print(action)
- node = k, v
- value = control_seed(node[1], action, False)
-
- if k not in seed_widget_map:
- continue
-
- if 'seed_num' in v['inputs']:
- if isinstance(v['inputs']['seed_num'], int):
- v['inputs']['seed_num'] = value
+ # else:
+ # prompts = json_data['prompt'].items()
+ # for k, v in prompts:
+ # if 'class_type' not in v:
+ # continue
+ # cls = v['class_type']
+ # if cls in seed_nodes:
+ # extra_data = next((x for x in workflow["nodes"] if str(x["id"]) == k), None)
+ # if extra_data is not None:
+ # inputs = extra_data.get('inputs')
+ # widgets_value = extra_data.get('widgets_values')
+ # widgets_length = len(widgets_value)
+ # if "disable" in widgets_value:
+ # break
+ # if inputs is not None and inputs != []:
+ # seed_num_input = next((x for x in inputs if x['name'] == 'seed_num' and x['type'] == 'INT'), None)
+ # if seed_num_input is not None:
+ # action = 'fixed'
+ # else:
+ # action = widgets_value[widgets_length - 1]
+ # else:
+ # control_index = widgets_length - 2 if cls == 'easy seed' else widgets_length - 1
+ # action = widgets_value[control_index]
+ #
+ # # print(action)
+ # node = k, v
+ # value = control_seed(node[1], action, False)
+ #
+ # if k not in seed_widget_map:
+ # continue
+ #
+ # if 'seed_num' in v['inputs']:
+ # if isinstance(v['inputs']['seed_num'], int):
+ # v['inputs']['seed_num'] = value
return value is not None
@@ -178,18 +178,18 @@ def workflow_seed_update(json_data):
length = len(node['widgets_values'])
node['widgets_values'][length-1] = node['widgets_values'][0]
node['widgets_values'][0] = value
- elif node_id in seed_widget_map:
- widget_idx = seed_widget_map[node_id]
-
- if 'seed_num' in prompt[node_id]['inputs']:
- seed = prompt[node_id]['inputs']['seed_num']
- elif 'noise_seed' in prompt[node_id]['inputs']:
- seed = prompt[node_id]['inputs']['noise_seed']
- else:
- seed = prompt[node_id]['inputs']['seed']
-
- node['widgets_values'][widget_idx] = seed
- updated_seed_map[node_id] = seed
+ # elif node_id in seed_widget_map:
+ # widget_idx = seed_widget_map[node_id]
+ #
+ # if 'seed_num' in prompt[node_id]['inputs']:
+ # seed = prompt[node_id]['inputs']['seed_num']
+ # elif 'noise_seed' in prompt[node_id]['inputs']:
+ # seed = prompt[node_id]['inputs']['noise_seed']
+ # else:
+ # seed = prompt[node_id]['inputs']['seed']
+ #
+ # node['widgets_values'][widget_idx] = seed
+ # updated_seed_map[node_id] = seed
server.PromptServer.instance.send_sync("easyuse-global-seed", {"id": node_id, "value": value, "seed_map": updated_seed_map})
diff --git a/web/css/dropdown.css b/web/css/dropdown.css
new file mode 100644
index 0000000..176ff61
--- /dev/null
+++ b/web/css/dropdown.css
@@ -0,0 +1,68 @@
+.easy-dropdown, .easy-nested-dropdown {
+ position: relative;
+ box-sizing: border-box;
+ background-color: #171717;
+ box-shadow: 0 4px 4px rgba(255, 255, 255, .25);
+ padding: 0;
+ margin: 0;
+ list-style: none;
+ z-index: 1000;
+ overflow: visible;
+ max-height: fit-content;
+ max-width: fit-content;
+}
+
+.easy-dropdown {
+ position: absolute;
+ border-radius: 0;
+}
+
+/* Style for final items */
+.easy-dropdown li.item, .easy-nested-dropdown li.item {
+ font-weight: normal;
+ min-width: max-content;
+}
+
+/* Style for folders (parent items) */
+.easy-dropdown li.folder, .easy-nested-dropdown li.folder {
+ cursor: default;
+ position: relative;
+ border-right: 3px solid cyan;
+}
+
+.easy-dropdown li.folder::after, .easy-nested-dropdown li.folder::after {
+ content: ">";
+ position: absolute;
+ right: 2px;
+ font-weight: normal;
+}
+
+.easy-dropdown li, .easy-nested-dropdown li {
+ padding: 4px 10px;
+ cursor: pointer;
+ font-family: system-ui;
+ font-size: 0.7rem;
+ position: relative;
+}
+
+/* Style for nested dropdowns */
+.easy-nested-dropdown {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ margin: 0;
+ border: none;
+ display: none;
+}
+
+.easy-dropdown li.selected > .easy-nested-dropdown,
+.easy-nested-dropdown li.selected > .easy-nested-dropdown {
+ display: block;
+ border: none;
+}
+
+.easy-dropdown li.selected,
+.easy-nested-dropdown li.selected {
+ background-color: #e5e5e5;
+ border: none;
+}
\ No newline at end of file
diff --git a/web/css/easy.css b/web/css/easy.css
new file mode 100644
index 0000000..5351f02
--- /dev/null
+++ b/web/css/easy.css
@@ -0,0 +1,115 @@
+
+.pysssss-workflow-popup{
+ min-width:200px!important;
+ right:0px!important;
+ left:auto!important;
+}
+body{
+ font-family: var(--font-family)!important;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+textarea{
+ font-family: var(--font-family)!important;
+}
+
+.comfy-multiline-input{
+ background-color: transparent;
+ border:1px solid var(--border-color);
+ border-radius:8px;
+ padding: 8px;
+ font-size: 12px;
+}
+.comfy-modal {
+ border:1px solid var(--border-color);
+ box-shadow:none;
+ backdrop-filter: blur(8px) brightness(120%);
+}
+.comfy-menu{
+ top:38%;
+ border-radius:16px;
+ box-shadow:0 0 1px var(--descrip-text);
+ backdrop-filter: blur(8px) brightness(120%);
+}
+.comfy-menu button,.comfy-modal button {
+ font-size: 16px;
+ padding:6px 0;
+ margin-bottom:8px;
+}
+.comfy-menu button.comfy-settings-btn{
+ font-size: 12px;
+}
+.comfy-menu-btns {
+ margin-bottom: 4px;
+}
+.comfy-menu-btns button,.comfy-list-actions button{
+ font-size: 12px;
+}
+.comfy-menu > button,
+.comfy-menu-btns button,
+.comfy-menu .comfy-list button,
+.comfy-modal button {
+ border-width:1px;
+}
+
+dialog{
+ border:1px solid var(--border-color);
+ background:transparent;
+ backdrop-filter: blur(8px) brightness(120%);
+ box-shadow:none;
+}
+.cm-title{
+ background-color:transparent!important;
+}
+.cm-notice-board{
+ border-radius:10px!important;
+ border:1px solid var(--border-color)!important;
+}
+.cm-menu-container{
+ margin-bottom:50px!important;
+}
+hr{
+ border:1px solid var(--border-color);
+}
+#shareButton{
+ background:linear-gradient(to left,var(--theme-color),var(--theme-color-light))!important;
+ color:white!important;
+}
+#queue-button{
+ position:relative;
+ overflow:hidden;
+ z-index:1;
+}
+
+#queue-button:after{
+ content:attr(data-attr);
+ background:green;
+ color:#FFF;
+ width:var(--process-bar-width);
+ height:100%;
+ position:absolute;
+ top:0;
+ left:0;
+ z-index:0;
+ text-align:center;
+ display:flex;
+ justify-content:center;
+ align-items:center;
+}
+
+.litegraph .litemenu-entry.has_submenu {
+ border-right: 2px solid var(--theme-color);
+}
+::-webkit-scrollbar {
+ width: 0em;
+}
+::-webkit-scrollbar-track {
+ background-color: transparent;
+}
+::-webkit-scrollbar-thumb {
+ background-color: transparent;
+ border-radius: 2px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background-color: transparent;
+}
\ No newline at end of file
diff --git a/web/css/groupmap.css b/web/css/groupmap.css
new file mode 100644
index 0000000..fdf313a
--- /dev/null
+++ b/web/css/groupmap.css
@@ -0,0 +1,13 @@
+#easyuse_groups_map{
+ flex-direction: column;
+ align-items: end;
+ display:flex;position: absolute;
+ top: 50px; left: 10px; width: 180px;
+ border-radius:12px;
+ min-height:100px;
+ max-height:400px;
+ color: var(--descrip-text);
+ background-color: var(--comfy-menu-bg);
+ padding: 10px 4px;
+ border: 1px solid var(--border-color);z-index: 999999999;padding-top: 0;
+}
\ No newline at end of file
diff --git a/web/css/index.css b/web/css/index.css
new file mode 100644
index 0000000..fd11b8e
--- /dev/null
+++ b/web/css/index.css
@@ -0,0 +1,4 @@
+@import "theme.css";
+@import "dropdown.css";
+@import "selector.css";
+@import "groupmap.css";
\ No newline at end of file
diff --git a/web/css/selector.css b/web/css/selector.css
new file mode 100644
index 0000000..5a60b6e
--- /dev/null
+++ b/web/css/selector.css
@@ -0,0 +1,101 @@
+.easyuse-prompt-styles .tools{
+ display:flex;
+ justify-content:space-between;
+ height:30px;
+ padding-bottom:10px;
+ border-bottom:2px solid var(--border-color);
+}
+.easyuse-prompt-styles .tools button.delete{
+ height:30px;
+ border-radius: 8px;
+ border: 2px solid var(--border-color);
+ font-size:11px;
+ background:var(--comfy-input-bg);
+ color:var(--error-text);
+ box-shadow:none;
+ cursor:pointer;
+}
+.easyuse-prompt-styles .tools button.delete:hover{
+ filter: brightness(1.2);
+}
+.easyuse-prompt-styles .tools textarea.search{
+ flex:1;
+ margin-left:10px;
+ height:20px;
+ line-height:20px;
+ border-radius: 8px;
+ border: 2px solid var(--border-color);
+ font-size:11px;
+ background:var(--comfy-input-bg);
+ color:var(--input-text);
+ box-shadow:none;
+ padding:4px 10px;
+ outline: none;
+ resize: none;
+ appearance:none;
+}
+.easyuse-prompt-styles-list{
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ min-height: 150px;
+ height: calc(100% - 40px);
+ overflow: auto;
+ // display: flex;
+ // flex-wrap: wrap;
+}
+.easyuse-prompt-styles-tag{
+ display: inline-block;
+ vertical-align: middle;
+ margin-top: 8px;
+ margin-right: 8px;
+ padding:4px;
+ color: var(--input-text);
+ background-color: var(--comfy-input-bg);
+ border-radius: 8px;
+ border: 2px solid var(--border-color);
+ font-size:11px;
+ cursor:pointer;
+}
+.easyuse-prompt-styles-tag.hide{
+ display:none;
+}
+.easyuse-prompt-styles-tag:hover{
+ filter: brightness(1.2);
+}
+.easyuse-prompt-styles-tag input{
+ --ring-color: transparent;
+ position: relative;
+ box-shadow: none;
+ border: 2px solid var(--border-color);
+ border-radius: 2px;
+ background: linear-gradient(135deg, var(--comfy-menu-bg) 0%, var(--comfy-input-bg) 60%);
+}
+.easyuse-prompt-styles-tag input[type=checkbox]:checked{
+ border: 1px solid var(--theme-color-light);
+ background-color: var(--theme-color-light);
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+}
+.easyuse-prompt-styles-tag input[type=checkbox]{
+ color-adjust: exact;
+ display: inline-block;
+ flex-shrink: 0;
+ vertical-align: middle;
+ appearance: none;
+ border: 2px solid var(--border-color);
+ background-origin: border-box;
+ padding: 0;
+ width: 1rem;
+ height: 1rem;
+ border-radius:4px;
+ color:var(--theme-color-light);
+ user-select: none;
+}
+.easyuse-prompt-styles-tag span{
+ margin:0 4px;
+ vertical-align: middle;
+}
+#show_image_id{
+ width:128px;
+ height:128px;
+}
\ No newline at end of file
diff --git a/web/css/theme.css b/web/css/theme.css
new file mode 100644
index 0000000..78b92f3
--- /dev/null
+++ b/web/css/theme.css
@@ -0,0 +1,5 @@
+:root {
+ --theme-color:#3f3eed;
+ --theme-color-light:#006691;
+ --font-family: Inter, -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;
+}
\ No newline at end of file
diff --git a/web/js/bookmark.js b/web/js/bookmark.js
index 554b484..0006898 100644
--- a/web/js/bookmark.js
+++ b/web/js/bookmark.js
@@ -1,4 +1,4 @@
-import { app } from "../../../scripts/app.js";
+import { app } from "/scripts/app.js";
app.registerExtension({
diff --git a/web/js/common/dropdown.js b/web/js/common/dropdown.js
new file mode 100644
index 0000000..f1fd485
--- /dev/null
+++ b/web/js/common/dropdown.js
@@ -0,0 +1,218 @@
+let activeDropdown = null;
+
+export function removeDropdown() {
+ if (activeDropdown) {
+ activeDropdown.removeEventListeners();
+ activeDropdown.dropdown.remove();
+ activeDropdown = null;
+ }
+}
+export function createDropdown(inputEl, suggestions, onSelect, isDict = false) {
+ removeDropdown();
+ new Dropdown(inputEl, suggestions, onSelect, isDict);
+}
+
+class Dropdown {
+ constructor(inputEl, suggestions, onSelect, isDict = false) {
+ this.dropdown = document.createElement('ul');
+ this.dropdown.setAttribute('role', 'listbox');
+ this.dropdown.classList.add('easy-dropdown');
+ this.selectedIndex = -1;
+ this.inputEl = inputEl;
+ this.suggestions = suggestions;
+ this.onSelect = onSelect;
+ this.isDict = isDict;
+
+ this.focusedDropdown = this.dropdown;
+
+ this.buildDropdown();
+
+ this.onKeyDownBound = this.onKeyDown.bind(this);
+ this.onWheelBound = this.onWheel.bind(this);
+ this.onClickBound = this.onClick.bind(this);
+
+ this.addEventListeners();
+ }
+
+ buildDropdown() {
+ if (this.isDict) {
+ this.buildNestedDropdown(this.suggestions, this.dropdown);
+ } else {
+ this.suggestions.forEach((suggestion, index) => {
+ this.addListItem(suggestion, index, this.dropdown);
+ });
+ }
+
+ const inputRect = this.inputEl.getBoundingClientRect();
+ this.dropdown.style.top = (inputRect.top + inputRect.height - 10) + 'px';
+ this.dropdown.style.left = inputRect.left + 'px';
+
+ document.body.appendChild(this.dropdown);
+ activeDropdown = this;
+ }
+
+ buildNestedDropdown(dictionary, parentElement) {
+ let index = 0;
+ Object.keys(dictionary).forEach((key) => {
+ const item = dictionary[key];
+ if (typeof item === "object" && item !== null) {
+ const nestedDropdown = document.createElement('ul');
+ nestedDropdown.setAttribute('role', 'listbox');
+ nestedDropdown.classList.add('easy-nested-dropdown');
+ const parentListItem = document.createElement('li');
+ parentListItem.classList.add('folder');
+ parentListItem.textContent = key;
+ parentListItem.appendChild(nestedDropdown);
+ parentListItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
+ parentElement.appendChild(parentListItem);
+ this.buildNestedDropdown(item, nestedDropdown);
+ index = index + 1;
+ } else {
+ const listItem = document.createElement('li');
+ listItem.classList.add('item');
+ listItem.setAttribute('role', 'option');
+ listItem.textContent = key;
+ listItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
+ listItem.addEventListener('mousedown', this.onMouseDown.bind(this, key));
+ parentElement.appendChild(listItem);
+ index = index + 1;
+ }
+ });
+ }
+
+ addListItem(item, index, parentElement) {
+ const listItem = document.createElement('li');
+ listItem.setAttribute('role', 'option');
+ listItem.textContent = item;
+ listItem.addEventListener('mouseover', this.onMouseOver.bind(this, index));
+ listItem.addEventListener('mousedown', this.onMouseDown.bind(this, item));
+ parentElement.appendChild(listItem);
+ }
+
+ addEventListeners() {
+ document.addEventListener('keydown', this.onKeyDownBound);
+ this.dropdown.addEventListener('wheel', this.onWheelBound);
+ document.addEventListener('click', this.onClickBound);
+ }
+
+ removeEventListeners() {
+ document.removeEventListener('keydown', this.onKeyDownBound);
+ this.dropdown.removeEventListener('wheel', this.onWheelBound);
+ document.removeEventListener('click', this.onClickBound);
+ }
+
+ onMouseOver(index, parentElement) {
+ if (parentElement) {
+ this.focusedDropdown = parentElement;
+ }
+ this.selectedIndex = index;
+ this.updateSelection();
+ }
+
+ onMouseOut() {
+ this.selectedIndex = -1;
+ this.updateSelection();
+ }
+
+ onMouseDown(suggestion, event) {
+ event.preventDefault();
+ this.onSelect(suggestion);
+ this.dropdown.remove();
+ this.removeEventListeners();
+ }
+
+ onKeyDown(event) {
+ const enterKeyCode = 13;
+ const escKeyCode = 27;
+ const arrowUpKeyCode = 38;
+ const arrowDownKeyCode = 40;
+ const arrowRightKeyCode = 39;
+ const arrowLeftKeyCode = 37;
+ const tabKeyCode = 9;
+
+ const items = Array.from(this.focusedDropdown.children);
+ const selectedItem = items[this.selectedIndex];
+
+ if (activeDropdown) {
+ if (event.keyCode === arrowUpKeyCode) {
+ event.preventDefault();
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
+ this.updateSelection();
+ }
+
+ else if (event.keyCode === arrowDownKeyCode) {
+ event.preventDefault();
+ this.selectedIndex = Math.min(items.length - 1, this.selectedIndex + 1);
+ this.updateSelection();
+ }
+
+ else if (event.keyCode === arrowRightKeyCode) {
+ event.preventDefault();
+ if (selectedItem && selectedItem.classList.contains('folder')) {
+ const nestedDropdown = selectedItem.querySelector('.easy-nested-dropdown');
+ if (nestedDropdown) {
+ this.focusedDropdown = nestedDropdown;
+ this.selectedIndex = 0;
+ this.updateSelection();
+ }
+ }
+ }
+
+ else if (event.keyCode === arrowLeftKeyCode && this.focusedDropdown !== this.dropdown) {
+ const parentDropdown = this.focusedDropdown.closest('.easy-dropdown, .easy-nested-dropdown').parentNode.closest('.easy-dropdown, .easy-nested-dropdown');
+ if (parentDropdown) {
+ this.focusedDropdown = parentDropdown;
+ this.selectedIndex = Array.from(parentDropdown.children).indexOf(this.focusedDropdown.parentNode);
+ this.updateSelection();
+ }
+ }
+
+ else if ((event.keyCode === enterKeyCode || event.keyCode === tabKeyCode) && this.selectedIndex >= 0) {
+ event.preventDefault();
+ if (selectedItem.classList.contains('item')) {
+ this.onSelect(items[this.selectedIndex].textContent);
+ this.dropdown.remove();
+ this.removeEventListeners();
+ }
+
+ const nestedDropdown = selectedItem.querySelector('.easy-nested-dropdown');
+ if (nestedDropdown) {
+ this.focusedDropdown = nestedDropdown;
+ this.selectedIndex = 0;
+ this.updateSelection();
+ }
+ }
+
+ else if (event.keyCode === escKeyCode) {
+ this.dropdown.remove();
+ this.removeEventListeners();
+ }
+ }
+ }
+
+ onWheel(event) {
+ const top = parseInt(this.dropdown.style.top);
+ if (localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")) {
+ this.dropdown.style.top = (top + (event.deltaY < 0 ? 10 : -10)) + "px";
+ } else {
+ this.dropdown.style.top = (top + (event.deltaY < 0 ? -10 : 10)) + "px";
+ }
+ }
+
+ onClick(event) {
+ if (!this.dropdown.contains(event.target) && event.target !== this.inputEl) {
+ this.dropdown.remove();
+ this.removeEventListeners();
+ }
+ }
+
+ updateSelection() {
+ Array.from(this.focusedDropdown.children).forEach((li, index) => {
+ if (index === this.selectedIndex) {
+ li.classList.add('selected');
+ } else {
+ li.classList.remove('selected');
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/web/js/common/i18n.js b/web/js/common/i18n.js
new file mode 100644
index 0000000..1673a07
--- /dev/null
+++ b/web/js/common/i18n.js
@@ -0,0 +1,19 @@
+import {getLocale} from './utils.js'
+const locale = getLocale()
+
+const zhCN = {
+ // GroupMap
+ "Groups Map (EasyUse)": "管理组 (EasyUse)",
+ "Always": "启用中",
+ "Bypass": "已忽略",
+ "Never": "已停用",
+ "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)"
+}
+
+export const $t = (key) => {
+ return locale === 'zh-CN' ? zhCN[key] : key
+}
\ No newline at end of file
diff --git a/web/js/common/utils.js b/web/js/common/utils.js
new file mode 100644
index 0000000..4ba496e
--- /dev/null
+++ b/web/js/common/utils.js
@@ -0,0 +1,41 @@
+export function addPreconnect(href, crossorigin=false){
+ const preconnect = document.createElement("link");
+ preconnect.rel = 'preconnect'
+ preconnect.href = href
+ if(crossorigin) preconnect.crossorigin = ''
+ document.head.appendChild(preconnect);
+}
+
+export function addCss(href, base=true) {
+ const link = document.createElement("link");
+ link.rel = "stylesheet";
+ link.type = "text/css";
+ link.href = base ? "extensions/ComfyUI-Easy-Use/"+href : href;
+ document.head.appendChild(link);
+}
+
+export function deepEqual(obj1, obj2) {
+ if (typeof obj1 !== typeof obj2) {
+ return false
+ }
+ if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
+ return obj1 === obj2
+ }
+ const keys1 = Object.keys(obj1)
+ const keys2 = Object.keys(obj2)
+ if (keys1.length !== keys2.length) {
+ return false
+ }
+ for (let key of keys1) {
+ if (!deepEqual(obj1[key], obj2[key])) {
+ return false
+ }
+ }
+ return true
+}
+
+
+export function getLocale(){
+ const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
+ return locale
+}
\ No newline at end of file
diff --git a/web/js/easy/easy.js b/web/js/easy/easy.js
index 2cce036..a4daa7a 100644
--- a/web/js/easy/easy.js
+++ b/web/js/easy/easy.js
@@ -1,28 +1,8 @@
import { app } from "/scripts/app.js";
-import { ComfyWidgets } from "/scripts/widgets.js";
-import { $el } from "/scripts/ui.js";
-import { api } from "/scripts/api.js";
+import {deepEqual,addCss} from "../common/utils.js";
+import {$t} from '../common/i18n.js';
-
-function deepEqual (obj1, obj2) {
- if (typeof obj1 !== typeof obj2) {
- return false
- }
- if (typeof obj1 !== 'object' || obj1 === null || obj2 === null) {
- return obj1 === obj2
- }
- const keys1 = Object.keys(obj1)
- const keys2 = Object.keys(obj2)
- if (keys1.length !== keys2.length) {
- return false
- }
- for (let key of keys1) {
- if (!deepEqual(obj1[key], obj2[key])) {
- return false
- }
- }
- return true
-}
+addCss('css/index.css')
app.registerExtension({
name: "comfy.easyUse",
@@ -31,36 +11,23 @@ app.registerExtension({
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = getCanvasMenuOptions.apply(this, arguments);
- const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
let draggerEl = null
let isGroupMapcanMove = true
+ let old_groups = []
let emptyImg = new Image()
emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
options.push(null,
{
- content: locale == 'zh-CN' ? "📜管理组 (EasyUse)" : "📜Groups Map (EasyUse)",
- callback: () => {
+ content: '📜 '+ $t('Groups Map (EasyUse)'),
+ callback: async() => {
let groups = app.canvas.graph._groups
let nodes = app.canvas.graph._nodes
- let groups_len = groups.length
+ let old_nodes = groups.length
let div =
document.querySelector('#easyuse_groups_map') ||
document.createElement('div')
div.id = 'easyuse_groups_map'
- div.style = `
- flex-direction: column;
- align-items: end;
- display:flex;position: absolute;
- top: 50px; left: 10px; width: 180px;
- border-radius:12px;
- min-height:100px;
- max-height:400px;
- color: var(--descrip-text);
- background-color: var(--comfy-menu-bg);
- padding: 10px 4px;
- border: 1px solid var(--border-color);z-index: 999999999;padding-top: 0;`
-
div.innerHTML = ''
let btn = document.createElement('div')
btn.style = `display: flex;
@@ -74,10 +41,12 @@ app.registerExtension({
btn.appendChild(textB)
btn.appendChild(hideBtn)
textB.style.fontSize = '11px'
- textB.innerHTML = locale == 'zh-CN' ? `管理组 (EasyUse)` : `Groups Map (EasyUse)`
+ textB.innerHTML = `${$t('Groups Map (EasyUse)')}`
hideBtn.style = `float: right;color: var(--input-text);border-radius:6px;font-size:9px;
background-color: var(--comfy-input-bg); border: 1px solid var(--border-color);cursor: pointer;padding: 5px;aspect-ratio: 1 / 1;`
- hideBtn.addEventListener('click', () => {div.style.display = 'none'})
+ hideBtn.addEventListener('click', () => {
+ div.style.display = 'none'
+ })
hideBtn.innerText = '❌'
div.appendChild(btn)
@@ -114,9 +83,9 @@ app.registerExtension({
for (let index in groups) {
const group = groups[index]
const title = group.title
- const show_text = locale == 'zh-CN' ? '启用中' : 'Always'
- const hide_text = locale == 'zh-CN' ? '已忽略' : 'Bypass'
- const mute_text = locale == 'zh-CN' ? '已停用' : 'Never'
+ const show_text = $t('Always')
+ const hide_text = $t('Bypass')
+ const mute_text = $t('Never')
let group_item = document.createElement('div')
let group_item_style = `justify-content: space-between;display:flex;background-color: var(--comfy-input-bg);border-radius: 5px;border:1px solid var(--border-color);margin-top:5px;`
group_item.addEventListener("mouseover",event=>{
@@ -247,7 +216,7 @@ app.registerExtension({
let autoSortDiv = document.createElement('button')
autoSortDiv.style = `cursor:pointer;font-size:10px;padding:2px 4px;color:var(--input-text);background-color: var(--comfy-input-bg);border: 1px solid var(--border-color);border-radius:4px;`
- autoSortDiv.innerText = locale == 'zh-CN' ? '自动排序' : 'Automatic sorting'
+ autoSortDiv.innerText = $t('Auto Sorting')
autoSortDiv.addEventListener('click',e=>{
e.preventDefault()
groupsDiv.innerHTML = ``
@@ -261,21 +230,23 @@ app.registerExtension({
let remarkDiv = document.createElement('p')
remarkDiv.style = `text-align:center; font-size:10px; padding:0 10px;color:var(--descrip-text)`
- remarkDiv.innerText = locale == 'zh-CN' ? "点击`启用中/已忽略`可设置组模式, 长按可停用该组节点" : "Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never"
+ remarkDiv.innerText = $t('Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never')
div.appendChild(groupsDiv)
div.appendChild(remarkDiv)
div.appendChild(autoSortDiv)
+
let graphDiv = document.getElementById("graph-canvas")
graphDiv.addEventListener('mouseover', async () => {
- let n = (await app.graphToPrompt()).output
- if (!deepEqual(n, nodes)) {
- groupsDiv.innerHTML = ``
- let new_groups = app.canvas.graph._groups
- updateGroups(new_groups, groupsDiv, autoSortDiv)
- }
+ groupsDiv.innerHTML = ``
+ let new_groups = app.canvas.graph._groups
+ updateGroups(new_groups, groupsDiv, autoSortDiv)
+ old_nodes = nodes
})
+
if (!document.querySelector('#easyuse_groups_map')){
document.body.appendChild(div)
+ }else{
+ div.style.display = 'flex'
}
}
@@ -293,300 +264,4 @@ app.registerExtension({
};
}
},
-});
-
-
-// easy Dropdown
-var styleElement = document.createElement("style");
-const cssCode = `
-.easy-dropdown, .easy-nested-dropdown {
- position: relative;
- box-sizing: border-box;
- background-color: #171717;
- box-shadow: 0 4px 4px rgba(255, 255, 255, .25);
- padding: 0;
- margin: 0;
- list-style: none;
- z-index: 1000;
- overflow: visible;
- max-height: fit-content;
- max-width: fit-content;
-}
-
-.easy-dropdown {
- position: absolute;
- border-radius: 0;
-}
-
-/* Style for final items */
-.easy-dropdown li.item, .easy-nested-dropdown li.item {
- font-weight: normal;
- min-width: max-content;
-}
-
-/* Style for folders (parent items) */
-.easy-dropdown li.folder, .easy-nested-dropdown li.folder {
- cursor: default;
- position: relative;
- border-right: 3px solid cyan;
-}
-
-.easy-dropdown li.folder::after, .easy-nested-dropdown li.folder::after {
- content: ">";
- position: absolute;
- right: 2px;
- font-weight: normal;
-}
-
-.easy-dropdown li, .easy-nested-dropdown li {
- padding: 4px 10px;
- cursor: pointer;
- font-family: system-ui;
- font-size: 0.7rem;
- position: relative;
-}
-
-/* Style for nested dropdowns */
-.easy-nested-dropdown {
- position: absolute;
- top: 0;
- left: 100%;
- margin: 0;
- border: none;
- display: none;
-}
-
-.easy-dropdown li.selected > .easy-nested-dropdown,
-.easy-nested-dropdown li.selected > .easy-nested-dropdown {
- display: block;
- border: none;
-}
-
-.easy-dropdown li.selected,
-.easy-nested-dropdown li.selected {
- background-color: #e5e5e5;
- border: none;
-}
-`
-styleElement.innerHTML = cssCode
-document.head.appendChild(styleElement);
-
-let activeDropdown = null;
-
-export function easy_RemoveDropdown() {
- if (activeDropdown) {
- activeDropdown.removeEventListeners();
- activeDropdown.dropdown.remove();
- activeDropdown = null;
- }
-}
-
-class Dropdown {
- constructor(inputEl, suggestions, onSelect, isDict = false) {
- this.dropdown = document.createElement('ul');
- this.dropdown.setAttribute('role', 'listbox');
- this.dropdown.classList.add('easy-dropdown');
- this.selectedIndex = -1;
- this.inputEl = inputEl;
- this.suggestions = suggestions;
- this.onSelect = onSelect;
- this.isDict = isDict;
-
- this.focusedDropdown = this.dropdown;
-
- this.buildDropdown();
-
- this.onKeyDownBound = this.onKeyDown.bind(this);
- this.onWheelBound = this.onWheel.bind(this);
- this.onClickBound = this.onClick.bind(this);
-
- this.addEventListeners();
- }
-
- buildDropdown() {
- if (this.isDict) {
- this.buildNestedDropdown(this.suggestions, this.dropdown);
- } else {
- this.suggestions.forEach((suggestion, index) => {
- this.addListItem(suggestion, index, this.dropdown);
- });
- }
-
- const inputRect = this.inputEl.getBoundingClientRect();
- this.dropdown.style.top = (inputRect.top + inputRect.height - 10) + 'px';
- this.dropdown.style.left = inputRect.left + 'px';
-
- document.body.appendChild(this.dropdown);
- activeDropdown = this;
- }
-
- buildNestedDropdown(dictionary, parentElement) {
- let index = 0;
- Object.keys(dictionary).forEach((key) => {
- const item = dictionary[key];
- if (typeof item === "object" && item !== null) {
- const nestedDropdown = document.createElement('ul');
- nestedDropdown.setAttribute('role', 'listbox');
- nestedDropdown.classList.add('easy-nested-dropdown');
- const parentListItem = document.createElement('li');
- parentListItem.classList.add('folder');
- parentListItem.textContent = key;
- parentListItem.appendChild(nestedDropdown);
- parentListItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
- parentElement.appendChild(parentListItem);
- this.buildNestedDropdown(item, nestedDropdown);
- index = index + 1;
- } else {
- const listItem = document.createElement('li');
- listItem.classList.add('item');
- listItem.setAttribute('role', 'option');
- listItem.textContent = key;
- listItem.addEventListener('mouseover', this.onMouseOver.bind(this, index, parentElement));
- listItem.addEventListener('mousedown', this.onMouseDown.bind(this, key));
- parentElement.appendChild(listItem);
- index = index + 1;
- }
- });
- }
-
- addListItem(item, index, parentElement) {
- const listItem = document.createElement('li');
- listItem.setAttribute('role', 'option');
- listItem.textContent = item;
- listItem.addEventListener('mouseover', this.onMouseOver.bind(this, index));
- listItem.addEventListener('mousedown', this.onMouseDown.bind(this, item));
- parentElement.appendChild(listItem);
- }
-
- addEventListeners() {
- document.addEventListener('keydown', this.onKeyDownBound);
- this.dropdown.addEventListener('wheel', this.onWheelBound);
- document.addEventListener('click', this.onClickBound);
- }
-
- removeEventListeners() {
- document.removeEventListener('keydown', this.onKeyDownBound);
- this.dropdown.removeEventListener('wheel', this.onWheelBound);
- document.removeEventListener('click', this.onClickBound);
- }
-
- onMouseOver(index, parentElement) {
- if (parentElement) {
- this.focusedDropdown = parentElement;
- }
- this.selectedIndex = index;
- this.updateSelection();
- }
-
- onMouseOut() {
- this.selectedIndex = -1;
- this.updateSelection();
- }
-
- onMouseDown(suggestion, event) {
- event.preventDefault();
- this.onSelect(suggestion);
- this.dropdown.remove();
- this.removeEventListeners();
- }
-
- onKeyDown(event) {
- const enterKeyCode = 13;
- const escKeyCode = 27;
- const arrowUpKeyCode = 38;
- const arrowDownKeyCode = 40;
- const arrowRightKeyCode = 39;
- const arrowLeftKeyCode = 37;
- const tabKeyCode = 9;
-
- const items = Array.from(this.focusedDropdown.children);
- const selectedItem = items[this.selectedIndex];
-
- if (activeDropdown) {
- if (event.keyCode === arrowUpKeyCode) {
- event.preventDefault();
- this.selectedIndex = Math.max(0, this.selectedIndex - 1);
- this.updateSelection();
- }
-
- else if (event.keyCode === arrowDownKeyCode) {
- event.preventDefault();
- this.selectedIndex = Math.min(items.length - 1, this.selectedIndex + 1);
- this.updateSelection();
- }
-
- else if (event.keyCode === arrowRightKeyCode) {
- event.preventDefault();
- if (selectedItem && selectedItem.classList.contains('folder')) {
- const nestedDropdown = selectedItem.querySelector('.easy-nested-dropdown');
- if (nestedDropdown) {
- this.focusedDropdown = nestedDropdown;
- this.selectedIndex = 0;
- this.updateSelection();
- }
- }
- }
-
- else if (event.keyCode === arrowLeftKeyCode && this.focusedDropdown !== this.dropdown) {
- const parentDropdown = this.focusedDropdown.closest('.easy-dropdown, .easy-nested-dropdown').parentNode.closest('.easy-dropdown, .easy-nested-dropdown');
- if (parentDropdown) {
- this.focusedDropdown = parentDropdown;
- this.selectedIndex = Array.from(parentDropdown.children).indexOf(this.focusedDropdown.parentNode);
- this.updateSelection();
- }
- }
-
- else if ((event.keyCode === enterKeyCode || event.keyCode === tabKeyCode) && this.selectedIndex >= 0) {
- event.preventDefault();
- if (selectedItem.classList.contains('item')) {
- this.onSelect(items[this.selectedIndex].textContent);
- this.dropdown.remove();
- this.removeEventListeners();
- }
-
- const nestedDropdown = selectedItem.querySelector('.easy-nested-dropdown');
- if (nestedDropdown) {
- this.focusedDropdown = nestedDropdown;
- this.selectedIndex = 0;
- this.updateSelection();
- }
- }
-
- else if (event.keyCode === escKeyCode) {
- this.dropdown.remove();
- this.removeEventListeners();
- }
- }
- }
-
- onWheel(event) {
- const top = parseInt(this.dropdown.style.top);
- if (localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")) {
- this.dropdown.style.top = (top + (event.deltaY < 0 ? 10 : -10)) + "px";
- } else {
- this.dropdown.style.top = (top + (event.deltaY < 0 ? -10 : 10)) + "px";
- }
- }
-
- onClick(event) {
- if (!this.dropdown.contains(event.target) && event.target !== this.inputEl) {
- this.dropdown.remove();
- this.removeEventListeners();
- }
- }
-
- updateSelection() {
- Array.from(this.focusedDropdown.children).forEach((li, index) => {
- if (index === this.selectedIndex) {
- li.classList.add('selected');
- } else {
- li.classList.remove('selected');
- }
- });
- }
-}
-
-export function easy_CreateDropdown(inputEl, suggestions, onSelect, isDict = false) {
- easy_RemoveDropdown();
- new Dropdown(inputEl, suggestions, onSelect, isDict);
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/web/js/easy/easyDynamicWidgets.js b/web/js/easy/easyDynamicWidgets.js
index d705e57..9b270bb 100644
--- a/web/js/easy/easyDynamicWidgets.js
+++ b/web/js/easy/easyDynamicWidgets.js
@@ -783,14 +783,16 @@ app.registerExtension({
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
onNodeCreated ? onNodeCreated.apply(this, []) : undefined;
- const values = ["randomize", "fixed", "increment", "decrement"]
+ // const values = ["randomize", "fixed", "increment", "decrement"]
+ // const seed_widget = this.widgets.find(w => w.name == 'seed_num')
+ // const seed_control = this.addWidget("combo", "control_before_generate", values[0], () => {
+ // }, {
+ // values,
+ // serialize: false
+ // })
+ // seed_widget.linkedWidgets = [seed_control]
const seed_widget = this.widgets.find(w => w.name == 'seed_num')
- const seed_control = this.addWidget("combo", "control_before_generate", values[0], () => {
- }, {
- values,
- serialize: false
- })
- seed_widget.linkedWidgets = [seed_control]
+ const seed_control = this.widgets.find(w=> ['control_before_generate','control_after_generate'].includes(w.name))
if(nodeData.name == 'easy seed'){
this.addWidget("button", "🎲 Manual Random Seed", null, _=>{
if(seed_control.value != 'fixed'){
diff --git a/web/js/easy/easyInterface.js b/web/js/easy/easyInterface.js
index 270e4a3..cf3c837 100644
--- a/web/js/easy/easyInterface.js
+++ b/web/js/easy/easyInterface.js
@@ -1,6 +1,7 @@
import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
import { $el } from "/scripts/ui.js";
+import {addPreconnect, addCss} from "../common/utils.js";
const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
@@ -88,8 +89,8 @@ const ui = {
}
}
-const custom_theme_name = 'custom_obsidian'
let custom_theme = null
+let control_mode = null
try{
custom_theme = localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes') ? JSON.parse(localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes')) : {};
}
@@ -108,25 +109,53 @@ try{
localStorage.setItem('Comfy.Settings.Comfy.CustomColorPalettes', JSON.stringify(custom_theme));
}
let theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette')
+ control_mode = localStorage.getItem('Comfy.Settings.Comfy.WidgetControlMode')
+ if(control_mode) {
+ control_mode = JSON.parse(control_mode)
+ if(control_mode == 'before'){
+ localStorage['Comfy.Settings.AE.mouseover'] = false
+ localStorage['Comfy.Settings.AE.highlight'] = false
+ }
+ }
// 兼容 ComfyUI Revision: 1887 [235727fe] 以上版本
if(api.storeSettings){
const _settings = await api.getSettings()
+ let settings = null
+ // 运行操作设置
+ if(!control_mode && _settings['Comfy.WidgetControlMode']) {
+ control_mode = _settings['Comfy.WidgetControlMode']
+ }else if(!control_mode) control_mode = 'after'
+ if(control_mode == 'before'){
+ if(!settings) settings = {}
+ settings["AE.mouseover"] = false
+ settings["AE.highlight"] = false
+ }
+ // 主题设置
+ console.log(theme_name)
if(!theme_name && _settings['Comfy.ColorPalette']) {
- theme_name = _settings['Comfy.ColorPalette']
- localStorage.setItem('Comfy.Settings.Comfy.ColorPalette',`"${theme_name}"`)
+ theme_name = `"${_settings['Comfy.ColorPalette']}"`
+ localStorage.setItem('Comfy.Settings.Comfy.ColorPalette', theme_name)
}
- const settings = {
- "Comfy.CustomColorPalettes": localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes') ? JSON.parse(localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes')) : {},
+ if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)) {
+ if(!settings) settings = {}
+ settings["Comfy.ColorPalette"] = JSON.parse(theme_name)
}
- if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)) settings["Comfy.ColorPalette"] = custom_theme_name
-
if(!_settings || !_settings["Comfy.CustomColorPalettes"] || !_settings["Comfy.CustomColorPalettes"]["obsidian"] || _settings["Comfy.CustomColorPalettes"]["obsidian"]['version'] button,
- .comfy-menu-btns button,
- .comfy-menu .comfy-list button,
- .comfy-modal button {
- border-width:1px;
- }
-
- dialog{
- border:1px solid var(--border-color);
- background:transparent;
- backdrop-filter: blur(8px) brightness(120%);
- box-shadow:none;
- }
- .cm-title{
- background-color:transparent!important;
- }
- .cm-notice-board{
- border-radius:10px!important;
- border:1px solid var(--border-color)!important;
- }
- .cm-menu-container{
- margin-bottom:50px!important;
- }
- hr{
- border:1px solid var(--border-color);
- }
- #shareButton{
- background:linear-gradient(to left,${customThemeColor},${LGraphCanvas.node_colors.pale_blue.color})!important;
- color:white!important;
- }
- #queue-button{
- position:relative;
- overflow:hidden;
- z-index:1;
- }
-
- #queue-button:after{
- content:attr(data-attr);
- background:green;
- color:#FFF;
- width:var(--process-bar-width);
- height:100%;
- position:absolute;
- top:0;
- left:0;
- z-index:0;
- text-align:center;
- display:flex;
- justify-content:center;
- align-items:center;
- }
-
- .litegraph .litemenu-entry.has_submenu {
- border-right: 2px solid #3f3eed;
- }
- ::-webkit-scrollbar {
- width: 0em;
- }
- ::-webkit-scrollbar-track {
- background-color: transparent;
- }
- ::-webkit-scrollbar-thumb {
- background-color: transparent;
- border-radius: 2px;
- }
- ::-webkit-scrollbar-thumb:hover {
- background-color: transparent;
- }
- `
- styleElement.innerHTML = cssCode
- document.head.appendChild(styleElement);
}
}catch(e){
console.error(e)
}
+function updateControlWidgetLabel(widget, controlValueRunBefore=false) {
+ let replacement = "after";
+ let find = "before";
+ if (controlValueRunBefore) {
+ [find, replacement] = [replacement, find]
+ }
+ widget.label = (widget.label ?? widget.name).replace(find, replacement);
+ widget.name = widget.label;
+}
+
// 节点颜色
const COLOR_THEMES = LGraphCanvas.node_colors
const NODE_COLORS = {
@@ -857,12 +761,25 @@ app.registerExtension({
Object.assign(LGraphCanvas.link_type_colors, customLinkColors);
},
- nodeCreated(node) {
+ async nodeCreated(node) {
if (NODE_COLORS.hasOwnProperty(node.comfyClass)) {
const colorKey = NODE_COLORS[node.comfyClass]
const theme = COLOR_THEMES[colorKey];
setNodeColors(node, theme);
}
-
- }
+ // 修复官方bug: 应该初始化修改节点的control_mode name
+ if(control_mode && control_mode == 'before'){
+ const controlValueRunBefore = control_mode == 'before'
+ for (const w of node.widgets) {
+ if (['control_before_generate','control_after_generate'].includes(w.name)) {
+ await updateControlWidgetLabel(w, controlValueRunBefore);
+ if (w.linkedWidgets) {
+ for (const l of w.linkedWidgets) {
+ await updateControlWidgetLabel(l, controlValueRunBefore);
+ }
+ }
+ }
+ }
+ }
+ },
})
diff --git a/web/js/easy/easyQuick.js b/web/js/easy/easyQuick.js
index f1a2112..c2a58a5 100644
--- a/web/js/easy/easyQuick.js
+++ b/web/js/easy/easyQuick.js
@@ -2,18 +2,18 @@
import { app } from "/scripts/app.js";
import { GroupNodeConfig } from "/extensions/core/groupNode.js";
import { api } from "/scripts/api.js";
+import { $t } from "../common/i18n.js"
const nodeTemplateShortcutId = "Comfy.EasyUse.NodeTemplateShortcut"
const processBarId = "Comfy.EasyUse.queueProcessBar"
let enableNodeTemplateShortcut = true
let enableQueueProcess = false
-const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
export function addNodeTemplateShortcutSetting(app) {
app.ui.settings.addSetting({
id: nodeTemplateShortcutId,
- name: locale == 'zh-CN' ? "启用ALT1~9从节点模板粘贴到工作流(ComfyUI-Easy-Use)" : "Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)",
+ name: $t("Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)"),
type: "boolean",
defaultValue: enableNodeTemplateShortcut,
onChange(value) {
@@ -24,11 +24,11 @@ export function addNodeTemplateShortcutSetting(app) {
export function addQueueProcessSetting(app) {
app.ui.settings.addSetting({
id: processBarId,
- name: locale == 'zh-CN' ? "启用提示词队列进度显示条(ComfyUI-Easy-Use)" : "Enable process bar in queue button (ComfyUI-Easy-Use)",
+ name: $t("Enable process bar in queue button (ComfyUI-Easy-Use)"),
type: "boolean",
- defaultValue: enableNodeTemplateShortcut,
+ defaultValue: enableQueueProcess,
onChange(value) {
- enableNodeTemplateShortcut = !!value;
+ enableQueueProcess = !!value;
},
});
}
diff --git a/web/js/easy/easySelector.js b/web/js/easy/easySelector.js
index 045dfa5..719f2b3 100644
--- a/web/js/easy/easySelector.js
+++ b/web/js/easy/easySelector.js
@@ -3,113 +3,6 @@ import { app } from "/scripts/app.js";
import { api } from "/scripts/api.js";
import { $el } from "/scripts/ui.js";
-// 添加样式
-const styleElement = document.createElement("style");
-const cssCode = `
- .easyuse-prompt-styles .tools{
- display:flex;
- justify-content:flex-between;
- height:30px;
- padding-bottom:10px;
- border-bottom:2px solid var(--border-color);
- }
- .easyuse-prompt-styles .tools button.delete{
- height:30px;
- border-radius: 8px;
- border: 2px solid var(--border-color);
- font-size:11px;
- background:var(--comfy-input-bg);
- color:var(--error-text);
- box-shadow:none;
- cursor:pointer;
- }
- .easyuse-prompt-styles .tools button.delete:hover{
- filter: brightness(1.2);
- }
- .easyuse-prompt-styles .tools textarea.search{
- flex:1;
- margin-left:10px;
- height:20px;
- line-height:20px;
- border-radius: 8px;
- border: 2px solid var(--border-color);
- font-size:11px;
- background:var(--comfy-input-bg);
- color:var(--input-text);
- box-shadow:none;
- padding:4px 10px;
- outline: none;
- resize: none;
- appearance:none;
- }
- .easyuse-prompt-styles-list{
- list-style: none;
- padding: 0;
- margin: 0;
- min-height: 150px;
- height: calc(100% - 40px);
- overflow: auto;
- // display: flex;
- // flex-wrap: wrap;
- }
- .easyuse-prompt-styles-tag{
- display: inline-block;
- vertical-align: middle;
- margin-top: 8px;
- margin-right: 8px;
- padding:4px;
- color: var(--input-text);
- background-color: var(--comfy-input-bg);
- border-radius: 8px;
- border: 2px solid var(--border-color);
- font-size:11px;
- cursor:pointer;
- }
- .easyuse-prompt-styles-tag.hide{
- display:none;
- }
- .easyuse-prompt-styles-tag:hover{
- filter: brightness(1.2);
- }
- .easyuse-prompt-styles-tag input{
- --ring-color: transparent;
- position: relative;
- box-shadow: none;
- border: 2px solid var(--border-color);
- border-radius: 2px;
- background: linear-gradient(135deg, var(--comfy-menu-bg) 0%, var(--comfy-input-bg) 60%);
- }
- .easyuse-prompt-styles-tag input[type=checkbox]:checked{
- border: 1px solid #006691;
- background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
- background-color: #006691;
- }
- .easyuse-prompt-styles-tag input[type=checkbox]{
- color-adjust: exact;
- display: inline-block;
- flex-shrink: 0;
- vertical-align: middle;
- appearance: none;
- border: 2px solid var(--border-color);
- background-origin: border-box;
- padding: 0;
- width: 1rem;
- height: 1rem;
- border-radius:4px;
- color:#006691;
- user-select: none;
- }
- .easyuse-prompt-styles-tag span{
- margin:0 4px;
- vertical-align: middle;
- }
- #show_image_id{
- width:128px;
- height:128px;
- }
-`
-styleElement.innerHTML = cssCode
-document.head.appendChild(styleElement);
// 获取风格列表
let styles_list_cache = {}
let styles_image_cache = {}
diff --git a/web/js/easy/easyXYPlot.js b/web/js/easy/easyXYPlot.js
index e4b4a7a..0e71eb4 100644
--- a/web/js/easy/easyXYPlot.js
+++ b/web/js/easy/easyXYPlot.js
@@ -1,5 +1,5 @@
import { app } from "/scripts/app.js";
-import { easy_CreateDropdown, easy_RemoveDropdown } from "./easy.js";
+import {removeDropdown, createDropdown} from "../common/dropdown.js";
function generateNumList(dictionary) {
const minimum = dictionary["min"] || 0;
@@ -154,13 +154,13 @@ function dropdownCreator(node) {
const filteredOptionsList = optionsList.filter(option => option.toLowerCase().includes(currentSegmentLower)).map(option => option.replace(/; /g, ''));
if (filteredOptionsList.length > 0) {
- easy_CreateDropdown(w.inputEl, filteredOptionsList, (selectedOption) => {
+ createDropdown(w.inputEl, filteredOptionsList, (selectedOption) => {
const verifiedText = replaceOptionSegments(selectedOption, inputSegments, cursorSegmentIndex, optionsList);
w.inputEl.value = verifiedText;
});
}
else {
- easy_RemoveDropdown();
+ removeDropdown();
const verifiedText = replaceOptionSegments(null, inputSegments, cursorSegmentIndex, optionsList);
w.inputEl.value = verifiedText;
}