From 0e6c3999d87d57b97525d80826afc94e32bb6d3e Mon Sep 17 00:00:00 2001 From: yolain Date: Sat, 16 Mar 2024 16:29:20 +0800 Subject: [PATCH] remove:forced control_before_generate --- README.en.md | 1 + README.md | 4 +- py/easyNodes.py | 26 +-- py/server.py | 92 ++++---- web/css/dropdown.css | 68 ++++++ web/css/easy.css | 115 +++++++++ web/css/groupmap.css | 13 ++ web/css/index.css | 4 + web/css/selector.css | 101 ++++++++ web/css/theme.css | 5 + web/js/bookmark.js | 2 +- web/js/common/dropdown.js | 218 +++++++++++++++++ web/js/common/i18n.js | 19 ++ web/js/common/utils.js | 41 ++++ web/js/easy/easy.js | 375 ++---------------------------- web/js/easy/easyDynamicWidgets.js | 16 +- web/js/easy/easyInterface.js | 207 +++++------------ web/js/easy/easyQuick.js | 10 +- web/js/easy/easySelector.js | 107 --------- web/js/easy/easyXYPlot.js | 6 +- 20 files changed, 752 insertions(+), 678 deletions(-) create mode 100644 web/css/dropdown.css create mode 100644 web/css/easy.css create mode 100644 web/css/groupmap.css create mode 100644 web/css/index.css create mode 100644 web/css/selector.css create mode 100644 web/css/theme.css create mode 100644 web/js/common/dropdown.js create mode 100644 web/js/common/i18n.js create mode 100644 web/js/common/utils.js 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; }