diff --git a/Example/hPic/H20251222-NodeAlignPro全新节点对齐与上色插件.png b/Example/hPic/H20251222-NodeAlignPro全新节点对齐与上色插件.png
new file mode 100644
index 0000000..3ef5471
Binary files /dev/null and b/Example/hPic/H20251222-NodeAlignPro全新节点对齐与上色插件.png differ
diff --git a/Example/hPic/H20251222-全新节点对齐与上色UI实操演示.png b/Example/hPic/H20251222-全新节点对齐与上色UI实操演示.png
new file mode 100644
index 0000000..275f931
Binary files /dev/null and b/Example/hPic/H20251222-全新节点对齐与上色UI实操演示.png differ
diff --git a/README.md b/README.md
index 8378d8a..173bce1 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,15 @@
[](https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro/stargazers) [](LICENSE) [](README_ZH.md) [](README_ZH.md)
-The newly designed ComfyUI node alignment and coloring plugin has completely redesigned and developed new UI icons and layouts. The layout of the alignment tool is more in line with the common operation logic and experience in the design industry. The position of the coloring tool is more reasonable, facilitating quick coloring.
-## 🔥 [+] Update Preview (A Major Update for the Upcoming Version 😁)↓↓↓↓
- [+] The basic code and concept were basically completed in June-July of this year. Due to my recent busyness, the update has been on hold for a long time. Please be patient and wait; we will strive to launch the major update version before the National Day (see the picture below 👇). Thank you for your support and understanding!
-
+Completely reconstructed ComfyUI Node Alignment and Coloring Plugin, featuring brand-new UI icons and interface interaction logic. The layout of the alignment tool is more in line with the universal operational logic and experience in the design industry, and the position of the coloring tool is more reasonable, facilitating quick coloring operations.
-## ✨ Core Features (Coming Soon)
+## 🔥 [+] The major version update is here😁↓↓↓↓
+The basic code and concept were basically completed as early as June-July this year, but due to my busy schedule recently, I have put it aside for a long time and have not updated it;
+
+ [+] I took time out to update it these two days. On the premise of retaining the UI and interaction habits of the previous version as much as possible, I redesigned the relevant UI, and basically reconstructed all the relevant logic (see the figure below👇).
+
+
+## ✨ Core Features (Officially Launched)
| 💫Intelligent Alignment System | [+]💫New Coloring System | [+]💫User-Friendly Interaction Design | [+]💫New Color Picker (Original Design) |
|---------------------------|---------------------------|---------------------------|---------------------------|
@@ -16,14 +19,17 @@ The newly designed ComfyUI node alignment and coloring plugin has completely red
| 📐 Dynamic Spacing Distribution Algorithm | [+]🔗 Real-Time Linked Display of Color Picking Effect | [+]🧩 New Custom Configuration | [+]🎨 Color Card Modes: Color/Gray Scale/Custom |
| 👥 Collaborative Operation of Multiple Node Groups | [+]💾 Custom Color Card Collection and Restoration System | [+]💾 Layout Memory and Restoration System | [+]👏 Color Cards: Random/Collect/Remove/Manual Addition |
-⚠️Note: `HT20250430 - New UI Scaling Adaptation` (There is a minor bug causing `incomplete display due to excessive global scaling`, but it `does not affect basic usage` and will be fixed later. Therefore, you can currently **use the basic UI scaling with confidence**, i.e., **directly click the "UI Scaling" button**).
-⚠️ [+] Note: The three major modules - `💫New Coloring System`, `💫User-Friendly Interaction Design`, and `💫New Color Picker (Original Design)` - have all undergone restructuring to varying degrees, and the basic operation logic of the `v1.0.4_rc version` has been retained as much as possible. Everyone just needs to **wait patiently for the upcoming update**.
-## 🖼️ Effect Demonstration
+## ✅ New Version Effect Demo🖼️
-
+
+⚠️ Note: Regarding multiple alignment modes, you can `try Alt key + Alignment Button` to experience the `new alignment methods` in the new `easter egg feature`.
-## ✅ Function Update Records
+(Note: The alignment-related functions of the `new Node2.0 Beta version` are not yet stable, and there are cumulative offset issues due to various conversion errors. Reproduction: You can repeatedly enable/disable the native Node2.0 Beta switch in ComfyUI, and you will find that nodes accumulate errors, and their sizes and coordinates will continue to shift. Therefore, adaptation is temporarily not provided (although Node2.0 adaptation has been completed, the `logic for adapting to Node2.0 is temporarily removed` and `will be updated back when it is more stable`))
+
+⚠️ [+] Note: The three major modules - `💫Brand New Coloring System`, `💫User-Friendly Interaction Design`, and `💫New Color Picker (Original Design)` - have all been refactored to varying degrees, while retaining the basic operation logic of the `v1.0.4_rc version` as much as possible. Due to being busy in daily life and developing as a hobbyist (not a professional programmer), `there may inevitably be many bugs in this hasty update`. Please help test it, and feel free to submit Issue feedback. Thank you for your support and understanding!
+
+## ❌ Old Version Interface Comparison


diff --git a/README_ZH.md b/README_ZH.md
index 295c1ee..7d1daf4 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -2,30 +2,35 @@
[](https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro/stargazers) [](LICENSE) [](README_ZH.md) [](README.md)
-全新设计的ComfyUI节点对齐与上色插件,完整重新设计开发了新的UI图标和布局。对齐工具的布局更符合设计行业通用的操作逻辑和体验,上色工具位置更合理,方便快速上色。
+全新重构的ComfyUI节点对齐与上色插件,全新的UI图标和界面交互逻辑。对齐工具的布局更符合设计行业通用的操作逻辑和体验,上色工具位置更合理,方便快速上色。
-## 🔥 [+] 更新预告 (近期版本将会大更新😁)↓↓↓↓
- [+] 基础代码和构思已于今年6-7月份基本完成,由于本人近期较忙,搁置了挺久迟迟没有更新,还请大家耐心等待,争取国庆节前上线大更新版本(详见下图👇)。感谢各位支持和理解!
-
+## 🔥 [+] 版本大更新已经来了😁↓↓↓↓
+基础代码和构思早已于今年6-7月份基本完成,由于本人近期较忙,搁置了挺久迟迟没有更新;
+
+ [+] 这两天抽空更新了下,在尽可能保留上一版UI及交互习惯的前提下,重新设计了相关UI,基本上可以说是全部重构了相关逻辑(详见下图👇)。
+
-## ✨ 核心功能 (即将上线)
+## ✨ 核心功能 (已正式上线)
| 💫智能对齐系统 | [+]💫全新上色系统 | [+]💫人性化交互设计 | [+]💫全新取色器(原创设计) |
|---------------------------|---------------------------|---------------------------|---------------------------|
-| ✅ 8种基础对齐模式 | [+]🖼️ 一键式颜色管理方案 | 🖱️ 悬浮式工具面板 | [+]✅ 原生JS/CSS实现全新取色系统 |
+| ✅ 多种对齐模式 | [+]🖼️ 一键式颜色管理方案 | 🖱️ 悬浮式工具面板 | [+]✅ 原生JS/CSS实现全新取色系统 |
| 📐 动态间距分布算法 | [+]🔗 实时联动显示取色效果 | [+]🧩 全新自定义配置 | [+]🎨 色卡模式: 彩色/灰度/自定义 |
| 👥 多节点组协同操作 | [+]💾 自定义色卡收藏与恢复系统 | [+]💾 布局记忆与恢复系统 | [+]👏 色卡:随机/收藏/移除/以及手动添加 |
-⚠️注:`HT20250430-新增UI缩放适配` (有轻微bug导致`全局缩放过大导致显示不全`,但`不影响基础使用`,后续会修复。
-因此目前你可以**放心的使用基础UI缩放**,即**直接点击“UI缩放”按钮**)。
-⚠️ [+] 注:`💫全新上色系统` `💫人性化交互设计` `💫全新取色器(原创设计)` 这三大板块均进行了不同程度的重构,且尽可能保留了`v1.0.4_rc版`的基础操作逻辑,大家`**耐心等待近期更新**`即可。
-## 🖼️ 效果演示
+## ✅ 新版效果演示🖼️
-
+
+⚠️注:关于多种对齐模式,可`尝试Alt键+对齐按钮 ` 体验下新的`彩蛋功能`里的`全新对齐方式`。
-## ✅ 更新功能记录
+(注意`新版Node2.0测试版`的对齐相关功能目前还不够稳定,存在各种换算误差的累积偏移问题 (复现:可以来回开启/关闭ComfyUI原生的Node2.0测试版开关,就会发现,节点会累积误差,尺寸、坐标会不断的偏移。因此,暂时不作适配(虽然已经适配好Node2.0了,但还是`暂时移除适配Node2.0的逻辑`,后面`稍微稳定些会更新回来`))
+
+⚠️ [+] 注:`💫全新上色系统` `💫人性化交互设计` `💫全新取色器(原创设计)` 这三大板块均进行了不同程度的重构,且尽可能保留了`v1.0.4_rc版`的基础操作逻辑,由于平时较忙且业余开发(非专业程序猿),`仓促更新难免会有不少bug`,还请大家帮忙测试,欢迎提交Issue反馈,感谢各位支持和理解!
+
+
+## ❌ 旧版界面对照


diff --git a/__init__.py b/__init__.py
index 78ed34a..440eb0d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,8 +1,5 @@
import os
-# 插件节点映射(可以不定义任何节点)
NODE_CLASS_MAPPINGS = {}
-
-# 前端文件路径
current_directory = os.path.dirname(os.path.abspath(__file__))
-# 打印输出当前目录
WEB_DIRECTORY = os.path.join(current_directory)
+# NODE_JS = ["ArtsticH/hNodeAlignPro.js"]
\ No newline at end of file
diff --git a/node_info.js b/node_info.js
index b266d41..5a68edc 100644
--- a/node_info.js
+++ b/node_info.js
@@ -1,1328 +1,2411 @@
-import {
- ColorSVGs
- } from "./web/color_svgs.js";
- const buildSVG = (innerContent, {
- t,
- pId
- }) => {
- return ``;
- };
- const Elements = {
- rect: (x, y, width, height) =>
- ``,
- vLine: (x, height = 24) =>
- ``,
- hLine: (y, width = 24) =>
- ``,
- dotMatrix: () => {
- const dots = [];
- const positions = [
- [11, 6],
- [21, 6],
- [11, 16],
- [21, 16],
- [11, 26],
- [21, 26]
- ];
- positions.forEach(([cx, cy], index) => {
- dots.push(``);
- });
- return dots.join('');
+/**
+ * 【ComfyUI_EasyKitHT_NodeAlignPro】:核心代码
+ * @Artstich_Example
+ * ArtsticH/hNodeAlignPro.js
+ * @name ComfyUI_EasyKitHT_NodeAlignPro (ComfyUI Plugin)
+ * @description Advanced node alignment and coloring tools for ComfyUI
+ * @author Artstich @date 2025-06-15 @version 1.0.2 @license GPL-3.0
+ * @see https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro
+ */
+(function() {
+ 'use strict';
+
+ // 【== CSS 样式注入 ==】
+ const styles = `
+.hCMP-SwitchInput, .hAlign-label, .hAlignBtn_Group3, #h0__hApBar0_apBall, #hNAP-Title, #hApBar4__ProH, #hApBar4__ProH__Rename { display: none !important; }
+:root {
+ /* ↓↓↓全局核心颜色定义,不得修改↓↓↓ */
+ --hC_Bg: 24, 24, 27;
+ --hC_Bg95: rgba(var(--hC_Bg), 0.95);
+ --hC_Border: 61, 61, 67;
+ --hC_hMenu: var(--hC_Border);
+ --hC_hBtn_Std: 32, 32, 36;
+ --hC_hBtn_Hover: rgba(var(--hC_BW1_Black), 0.5);
+ --hC_hBtn1_Highlight: 55, 55, 126;
+ --hC_hBtn2_Warning: 69, 30, 30;
+ --hC_hBtn3_WarningHover: 90, 42, 42;
+ --hC_hBtn_svg: 107, 107, 112;
+ --hC_hBtn_svg_B: rgb(var(--hC_BW4_Gray));
+ --hC_hBtn_svg_W: rgb(var(--hC_BW6_BrightGray));
+ --hC_hBarDivider: 39, 39, 43;
+ --hC_ComfyUIACBar: 112, 163, 243;
+
+ --hC1_Red: 128, 38, 38;
+ --hC2_Orange: 133, 64, 31;
+ --hC3_Yellow: 130, 93, 30;
+ --hC4_Green: 37, 87, 37;
+ --hC5_Cyan: 42, 87, 86;
+ --hC6_Blue: 28, 78, 105;
+ --hC7_Purple: 55, 55, 125;
+
+ --hC_BW1_Black: 0, 0, 0;
+ --hC_BW2_DarkGray: 13, 13, 13;
+ --hC_BW3_DeepGray: 26, 26, 26;
+ --hC_BW4_Gray: 38, 38, 38;
+ --hC_BW5_LightGray: 77, 77, 77;
+ --hC_BW6_BrightGray: 204, 204, 204;
+ --hC_BW7_White: 255, 255, 255;
+ /* ↑↑↑全局核心颜色定义,不得修改↑↑↑ */
+
+ /* 新增:取色器相关变量(全部已按注释要求重命名并统一为x,x,x格式) */
+ --hC_CPr0__PurpleStd: var(--hC_hBtn1_Highlight);
+ --hC_CPr1__bgDark: 30, 30, 30;
+ --hC_CPr2__bgDarker: 21, 21, 21;
+ --hC_CPr3__bgLight: 37, 37, 37;
+ --hC_CPr4__border: --hC_Border;
+ --hC_CPr5__hNodeDot: 102, 102, 102;
+ --hC_CPr6__hNodeText: 224, 224, 224;
+ --hC_CPr7__hNodeIMG: 100, 181, 246;
+ --hC_CPr8__hNodeLaten: 255, 156, 249;
+ --hC_CPr9__hNodeVAE: 255, 110, 110;
+
+ /* 【h彩虹色】基础颜色变量 */
+ --hC_Rb01__hR__: #ff0000;
+ --hC_Rb02__Orange: #ff9900;
+ --hC_Rb03__hYY: #cdff00;
+ --hC_Rb04__hG__: #35ff00;
+ --hC_Rb05__hG2__: #00ff66;
+ --hC_Rb06__hCC: #00ffcc;
+ --hC_Rb07__hCC2: #00ccff;
+ --hC_Rb08__hB__: #0066ff;
+ --hC_Rb09__hB2__: #3300ff;
+ --hC_Rb10__Purple: #cc00ff;
+ --hC_Rb11__hMM: #ff0099;
+ --hC_Rb12__hR__: #ff0000;
+ --hC__RbList: #ff0000, #ff9900, #cdff00, #35ff00, #00ff66, #00ffcc, #00ccff, #0066ff, #3300ff, #cc00ff, #ff0099, #ff0000;
+ --hC_Rb__Bottom: linear-gradient(to bottom, var(--hC__RbList));
+ --hC_Rb__Left: linear-gradient(to left, var(--hC__RbList));
+ --hC_Rb__Right: linear-gradient(to right, var(--hC__RbList));
+ --hC_Rb__Top: linear-gradient(to top, var(--hC__RbList));
+
+ /* 标准尺寸变量 - 以h32为基准 */
+ --h32: 32px;
+ --h2: calc(var(--h32) / 16);
+ --h3: calc(var(--h32) * 0.09375);
+ --h4: calc(var(--h32) / 8);
+ --h6: calc(var(--h32) * 0.1875);
+ --h8: calc(var(--h32) / 4);
+ --h10: calc(var(--h32) / 3.2);
+ --h12: calc(var(--h32) * 0.375);
+ --h14: calc(var(--h32) * 0.4375);
+ --h15: calc(var(--h32) * 0.46875);
+ --h16: calc(var(--h32) / 2);
+ --h18: calc(var(--h32) * 0.5625);
+ --h20: calc(var(--h32) * 0.625);
+ --h22: calc(var(--h32) * 0.6875);
+ --h24: calc(var(--h32) * 0.75);
+ --h26: calc(var(--h32) * 0.8125);
+ --h30: calc(var(--h32) * 0.9375);
+ --h36: calc(var(--h32) * 1.125);
+ --h40: calc(var(--h32) * 1.25);
+ --h48: calc(var(--h32) * 1.5);
+ --h50: calc(var(--h32) * 1.5625);
+ --h58: calc(var(--h32) * 1.8125);
+ --h64: calc(var(--h32) * 2);
+ --h72: calc(var(--h32) * 2.25);
+ --h80: calc(var(--h32) * 2.5);
+ --h82: calc(var(--h32) * 2.5625);
+ --h96: calc(var(--h32) * 3);
+ --h108: calc(var(--h32) * 3.375);
+ --h128: calc(var(--h32) * 4);
+ --h136: calc(var(--h32) * 4.25);
+ --h140: calc(var(--h32) * 4.375);
+ --h144: calc(var(--h32) * 4.5);
+ --h220: calc(var(--h32) * 6.875);
+ --h256: calc(var(--h32) * 8);
+ --h288: calc(var(--h32) * 9);
+ --h384: calc(var(--h32) * 12);
+ --h512: calc(var(--h32) * 16);
+
+ /* 尺寸变量 */
+ --border-radius: var(--h4);
+ --border-width: 1px;
+ --title-bar-radius: var(--h6);
+
+ --node-title-height: var(--h40);
+ --node-main-height: var(--h80);
+ --palette-size: var(--h220);
+ --palette-bar-size: var(--h24);
+ --hSlider-width: var(--h128);
+ --slider-width: var(--h12);
+ --slider-height: var(--palette-size);
+ --slider-bar-width: var(--h24);
+ --slider-bar-height: var(--h12);
+ --hsb-height: var(--h8);
+ --hsb-bar-width: var(--h8);
+ --hsb-bar-height: var(--h16);
+ --slider-spacing: var(--h8);
+ --hColorPicker__hCPr-width: var(--h384);
+
+ /* 动画与交互变量 */
+ --hTrans3: 0.3s;
+ --btn-hover-scale: 1.2;
+ --rotate-in-duration: 0.35s;
+ --rotate-out-duration: 0.25s;
+ --rotate-in-angle: 75deg;
+ --easing-standard: cubic-bezier(0.34, 1.56, 0.64, 1);
+ --easing-out: cubic-bezier(0.22, 1, 0.36, 1);
+ .hDebugInfo { position: fixed; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: rgb(var(--hC_hBtn_svg)); padding: 8px; border-radius: 4px; font-size: 12px; font-family: monospace; z-index: calc(var(--hZindex) + 10010); }
+ .hDebugInfo_V2 { position: fixed !important; top: var(--h108) !important; left: var(--h64) !important; color: rgb(var(--hC_hBtn_svg)) !important; padding: var(--h8) !important; border-radius: var(--h8) !important; font-size: var(--h12) !important; font-family: monospace !important; z-index: var(--h512) !important; transform: none !important; box-sizing: border-box !important; pointer-events: auto !important; will-change: transform !important; isolation: isolate !important; }
+
+ /* Z-index 层级管理 */
+ --hZindex: 10001;
+ --hZindex__h1__hApBar1_Color: calc(var(--hZindex) + 99);
+ --hZindex__h2__hNodeAlignPro: calc(var(--hZindex) + 199);
+ --hZindex__hNAP-Title: calc(var(--hZindex) + 3999);
+ --hZindex__hApBar2__Align: calc(var(--hZindex) + 999);
+ --hZindex__hApBar4__ProH: calc(var(--hZindex) + 1399);
+ --hZindex__hApBar4__ProH__Rename: calc(var(--hZindex) + 1799);
+ --hZindex__hColorPicker: calc(var(--hZindex) + 3799);
+ --hZindex__h6__hMenu: calc(var(--hZindex) + 5999);
+ --hZindex__hMenuDropdown: calc(var(--hZindex) + 6000);
+ --hZindex__dragging: calc(var(--hZindex) + 10000);
+ --hColorPicker__font-size: 8pt;
+
+ /* 滑块样式变量 */
+ --slider-bg: rgb(var(--hC_Border));
+ --slider-handle-bg: rgb(var(--hC_BW6_BrightGray));
+ --slider-handle-border: 0px solid rgb(var(--hC_BW7_White));
+ --slider-handle-shadow: 0 0 3px rgba(var(--hC_BW1_Black), 0.8);
+ --slider-handle-active-bg: rgb(var(--hC_BW7_White));
+ --slider-handle-active-size: 14px;
+}
+
+.hCMP-SwitchInput, .hAlign-label, .hAlignBtn_Group3,
+#h0__hApBar0_apBall, #hNAP-Title, #hApBar4__ProH, #hApBar4__ProH__Rename { display: none !important; }
+
+#hApBar2__Align, #hApBar4__ProH, #hApBar4__ProH__Rename, #h6__hMenu { padding: 0 var(--h8) !important; }
+#hNodeAlignKit, #h0__hApBar0_apBall, #h1__hApBar1_Color, #hNAP-Title,
+#hNAP-Title__LOGO, #hNAP-Title__Content, #hNAP-Title__Content-Text, #hNAP-Title__Content-ModeSwitch,#hNAP-Title__Context, #hNAP-Title__MenuA,
+.hBtnC, .hInput { box-sizing: border-box; }
+#hNAP-Title, #hBarMove, #hNAP-Title__LOGOAlign, .hBarDivider, .hCPr__header { cursor: move; }
+#h0__hApBar0_apBall, #hNAP-Title__LOGO, #hNAP-Title__Content-ModeSwitch, #hNAP-Title__MenuA,
+.hBtnC, .hBtn, .hMenu-btn, .hCMP-hSel__option {cursor: pointer; }
+
+#hNodeAlignKit { z-index: var(--hZindex); position: fixed; margin: 0; border: none; background: none; box-sizing: border-box; }
+#h0__hApBar0_apBall { display: flex; width: var(--h32); height: var(--h32); border: none; border-radius: var(--h32); background: rgb(var(--hC_hBtn1_Highlight)); box-sizing: border-box; transition: all var(--hTrans3) ease; align-items: center; justify-content: center; cursor: pointer; position: relative; }
+#h0__hApBar0_apBall:hover { background-color: var(--hC_hBtn_Hover); }
+#h0__hApBar0_apBall.active {
+ width: var(--h64); height: var(--h64); border-radius: var(--h32); background-color: var(--hC_Bg95); border: 2px solid rgb(var(--hC_Border)); z-index: 1001;
+ box-shadow: 0 0 var(--h5) rgba(100, 100, 255, 0.5), 0 0 var(--h10) rgba(100, 100, 255, 0.3), 0 0 var(--h15) rgba(100, 100, 255, 0.1);
+ animation: breathe 3s infinite ease-in-out;
+}
+@keyframes breathe {
+ 0%, 100% {box-shadow: 0 0 5px rgba(100, 100, 255, 0.5), 0 0 10px rgba(100, 100, 255, 0.3), 0 0 15px rgba(100, 100, 255, 0.1);}
+ 50% {box-shadow: 0 0 10px rgba(100, 100, 255, 0.7), 0 0 20px rgba(100, 100, 255, 0.5), 0 0 30px rgba(100, 100, 255, 0.3);}
+}
+
+#h0__hApBar0_apBall #hBtnY_barLOGO { transition: all 0.3s ease; width: 100%; height: 100%; }
+#h0__hApBar0_apBall.active #hBtnY_barLOGO { filter: invert(23%) sepia(61%) saturate(1291%) hue-rotate(215deg) brightness(94%) contrast(92%); transition: filter 0.3s ease, width 0.3s ease, height 0.3s ease; width: 52px; height: 52px; }
+#h0__hApBar0_apBall #hBtnY_barLOGO { transition: width 0.3s ease, height 0.3s ease; }
+#hIcon #hBtnY_barLOGO { width: var(--h48); height: var(--h48); }
+#h1__hApBar1_Color { position: relative; display: flex; align-items: center; background-color: transparent; padding: var(--h10); border-radius: var(--h12); border: none; box-sizing: border-box; }
+.hBtnC { width: var(--h24); height: var(--h24); padding: 0; border: none; background: none; cursor: pointer; border-radius: 50%; position: relative; transition: all var(--hTrans3) var(--easing-standard); box-sizing: border-box; margin: 0 var(--h2); }
+
+#hClear, #hPick, #hRandom { border-radius: 50% !important; overflow: hidden !important; }
+#hClear:hover, #hPick:hover, #hRandom:hover { border-radius: 50% !important; overflow: hidden !important; }
+#hClear .hIcon, #hPick .hIcon, #hRandom .hIcon { border-radius: 0 !important; overflow: visible !important; }
+.hBtnC_7c { border: 1px solid rgb(var(--hC_BW2_DarkGray)); background: rgb(var(--hC_BW1_Black)); }
+.hBtnC:hover { transform: scale(var(--btn-hover-scale)); background: rgb(var(--hC_BW1_Black)); box-shadow: 0 0 5px rgba(var(--hC_Border), 0.7); }
+.hBtnC_7c:hover { border: 1px solid rgb(var(--hC_BW2_DarkGray)); }
+.hIconC { width: 100%; height: 100%; display: block; }
+.hIcon { width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
+
+#hClear, #hPick, #hRandom { background-color: rgb(var(--hC_BW1_Black)); }
+.hColorA_Clear__Slash { fill: rgb(var(--hC1_Red)); }
+#hColorA_Clear, #hColorB_Pick, #hColorC_Random { transition: transform var(--hTrans3) var(--easing-standard); }
+#hClear:hover, #hPick:hover, #hRandom:hover { transform: scale(var(--btn-hover-scale)) rotate(var(--rotate-in-angle)); animation: rotateIn var(--rotate-in-duration) var(--easing-out); }
+#hClear:not(:hover), #hPick:not(:hover), #hRandom:not(:hover) { animation: rotateOut var(--rotate-out-duration) var(--easing-standard); }
+
+@keyframes rotateIn { 0% { transform: rotate(0deg) scale(var(--btn-hover-scale)); } 100% { transform: rotate(var(--rotate-in-angle)) scale(var(--btn-hover-scale)); } }
+@keyframes rotateOut { 0% { transform: rotate(var(--rotate-in-angle)) scale(1); } 100% { transform: rotate(0deg) scale(1); } }
+.custom-color-btn { position: relative; }
+.love-icon-container { position: absolute; top: 54%; left: 50%; width: 50%; height: 50%; transform: translate(-50%, -50%); z-index: 1; display: flex; align-items: center; justify-content: center; }
+
+.love-icon-container svg { width: 100%; height: 100%; }
+.hBtnC.disabled-state { opacity: 0.5 !important; pointer-events: none !important; cursor: not-allowed !important; }
+#hClear.disabled-state, #hPick.disabled-state, #hRandom.disabled-state { transform: none !important; animation: none !important; }
+.hBtn_svg { fill: currentColor; }
+#hNAP-Title { display: flex; align-items: center; height: var(--h24); padding: 0 var(--h8); margin: 0; background-color: rgb(var(--hC_hBtn_Std)); border: none; border-top-left-radius: var(--title-bar-radius); border-top-right-radius: var(--title-bar-radius); box-sizing: border-box; cursor: move; }
+#hNAP-Title__LOGO { width: var(--h22); height: var(--h22); padding: 0; margin: 0 var(--h4); border: none; background: none; cursor: pointer; box-sizing: border-box; }
+#hNAP-Title__Content { display: flex; align-items: center; width: 264px; height: 24px; padding: 0; margin: 0; background: none; border: none; box-sizing: border-box; }
+#hNAP-Title__Content-Text { padding: 0; margin: 0; color: rgb(var(--hC_hBtn_svg)); font-size: var(--h16); text-align: left; white-space: nowrap; width: auto; box-sizing: border-box; }
+#hNAP-Title__Content-ModeSwitch { width: var(--h16); height: var(--h16); padding: 0; margin: 0 var(--h4); border: none; background: var(--hC_hBtn2_Warning); cursor: pointer; box-sizing: border-box; }
+#hNAP-Title__Context { height: var(--h16); padding: 0 var(--h8); margin: 0 var(--h4); background-color: var(--hC_Bg95); border: none; border-radius: var(--border-radius); color: rgba(var(--hC_hBtn_svg), 0.5); font-size: 10px; text-align: right; white-space: nowrap; width: 100%; box-sizing: border-box; flex: 1; }
+#hNAP-Title__MenuA { width: var(--h32); height: var(--h22); padding: 0; margin: 0 var(--h4); border: none; background: none; cursor: pointer; box-sizing: border-box; }
+
+#hIcon__MenuA { width: var(--h32); height: var(--h22); background: rgb(var(--hC_hBtn_Std)); }
+#h2__hNodeAlignPro { z-index: var(--hZindex__h2__hNodeAlignPro); padding: 0; margin: 0; border: 1px solid rgb(var(--hC_Border)); border-radius: var(--h8); background: none; background: var(--hC_Bg95); }
+#hApBar2__Align { z-index: var(--hZindex__hApBar2__Align); display: flex; align-items: center; }
+#hApBar4__ProH { display: flex; align-items: center; }
+#hApBar4__ProH__Rename { display: flex; align-items: center; }
+
+[class^="hAlignBtn_Group"], [class^="hSelectBtn_Group"] { display: flex; align-items: center; gap: 0px; padding: 0; }
+.hBtn { width: var(--h32); height: var(--h32); padding: 0; border: none; border-radius: var(--border-radius); background: none; cursor: pointer; transition: background-color 0.2s ease; }
+
+.hBtn:hover { background-color: var(--hC_hBtn_Hover); }
+.hAlign-label { width: var(--h40); color: rgb(var(--hC_hBtn_svg)); font-size: calc(var(--h32)-4); }
+.hAlign-label2 { width: calc(var(--h32)+2); color: rgb(var(--hC_hBtn_svg)); font-size: calc(var(--h32)-4); }
+#hBtn-DispMode__hAlways { width: var(--h48); height: var(--h32); border: none; background: rgb(var(--hC_hBtn1_Highlight)); }
+
+.hCMP-SwitchInput { display: flex; align-items: center; width: var(--h82); height: var(--h26); background: rgb(var(--hC_BW1_Black)); border: 1px solid rgb(var(--hC_Border)); border-radius: var(--border-radius); }
+.hCMP-Switch { display: flex; flex-direction: column; padding: 0; gap: 0; width: var(--h18); height: var(--h26); background: var(--hC_hBtn2_Warning); }
+.hCMP-Switch > * { margin: 0; }
+.hCMP-Switch__Btn { height: var(--h12); border: none; border-radius: var(--border-radius); background: none; }
+.hCMP-Switch__Btn-SVG { width: var(--h18); }
+.hInput { width: var(--h64); height: var(--h26); box-sizing: border-box; padding: 0 var(--h4) 0 var(--h2); text-align: right; border: none; background: none; color: rgb(var(--hC_hBtn_svg)); font-size: var(--h16) px; }
+.hAlign-label__RenameAB { display: flex; width: var(--h40); height: var(--h32); align-items: center; justify-content: right; padding: 0 var(--h4) 0 var(--h2); color: rgb(var(--hC_hBtn_svg)); font-size: calc(var(--h32)-4); }
+.hBarDivider { width: var(--h2); height: var(--h40); background: rgb(var(--hC_hBarDivider)); margin: 0 var(--h8); border: none; cursor: move; }
+#hBarMove { cursor: move; }
+
+#h6__hMenu { position: absolute; background: rgb(var(--hC_hMenu)); border-radius: var(--h8); z-index: var(--hZindex__h6__hMenu); display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: auto auto; gap: var(--h8) var(--h4); width: auto; padding: var(--h4) var(--h8) var(--h8) var(--h8) !important; border: 1px solid rgb(var(--hC_Border)); box-sizing: border-box; }
+#h6__hMenu > div:last-child { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--h4); justify-items: center; margin-top: 0; height: var(--h26); position: relative; width: 100%; }
+.hMenu-btn { border: none; width: var(--h82); height: var(--h26); border-radius: var(--border-radius); background-color: rgb(var(--hC_hBtn_Std)); color: rgb(var(--hC_BW6_BrightGray)); font-size: var(--h16); cursor: pointer; transition: all var(--hTrans3) ease; display: flex; align-items: center; justify-content: center; position: relative; z-index: 2; box-sizing: border-box; }
+
+.hMenu-btn:hover { background-color: rgb(var(--hC_hBtn1_Highlight)); }
+.hMenu-btnReset { background: rgb(var(--hC_hBtn2_Warning)); }
+.hMenu-btnReset:hover { background: rgb(var(--hC_hBtn3_WarningHover)); }
+#hBugReport { cursor: alias; }
+#hGuide { cursor: help; }
+
+.hCMP__hSelKit { display: inline-block; flex-direction: column; gap: var(--h2); width: var(--h82); height: calc(var(--h16) + var(--h26)); position: relative; vertical-align: top; }
+.hCMP-hSel, .hCMP-hSel .hMenu-btn { width: var(--h82) !important; box-sizing: border-box; }
+.hSelKit-label { display: block; width: var(--h82); height: var(--h14); color: rgb(var(--hC_hBtn_svg)); font-size: var(--h12); margin: 0; padding: 0 0 0 var(--h4); line-height: 14px; text-align: left; box-sizing: border-box; }
+.hCMP-hSel { position: relative; width: var(--h82); height: var(--h26); }
+.hCMP-hSel::before { content: ''; position: absolute; top: -8px; left: -8px; right: -8px; bottom: -8px; z-index: 1; display: none; }
+.hCMP-hSel:hover::before { display: block; }
+
+.hCMP-hSel__options { position: absolute; top: 100%; left: 0; width: var(--h82) !important; background-color: rgb(var(--hC_hBtn_Std)); border: 1px solid rgb(var(--hC_Border)); border-radius: var(--border-radius); margin-top: var(--h2); z-index: var(--hZindex__hMenuDropdown); overflow: hidden; box-shadow: 0 var(--h4) var(--h8) rgba(var(--hC_BW1_Black), 0.2); opacity: 0; visibility: hidden; transform: translateY(-5px); transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s ease; box-sizing: border-box; }
+.hCMP-hSel__options.active { opacity: 1; visibility: visible; transform: translateY(0); }
+.hCMP-hSel__options.fade-out { opacity: 0; visibility: hidden; transform: translateY(-5px); }
+.hCMP-hSel__option { padding: var(--h6) var(--h8); color: rgb(var(--hC_BW6_BrightGray)); cursor: pointer; transition: all var(--hTrans3) ease; text-align: center; display: flex; align-items: center; justify-content: center; font-size: var(--h16); }
+.hCMP-hSel__option:hover { background-color: rgb(var(--hC_hBtn1_Highlight)); }
+.hCMP-hSel__option.selected { background-color: rgba(var(--hC_hBtn1_Highlight), 0.5); }
+#hNAP-Title__LOGOAlign:hover, #hBarMove:hover { background-color: rgb(var(--hC_ComfyUIACBar)); }
+#hNodeAlignKit, .hBtnC, .hIconC, #h1__hApBar1_Color, .hCPr__header { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-drag: none; -webkit-user-drag: none; -khtml-user-drag: none; -moz-user-drag: none; -o-user-drag: none; }
+.Artstich_hColorPicker, .Artstich_hColorPicker * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Microsoft YaHei', sans-serif; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; font-size: var(--hColorPicker__font-size); }
+.hIcon__LOGO { width: var(--h32); height: var(--h32); }
+.Artstich_hColorPicker { position: absolute; z-index: var(--hZindex__hColorPicker); border: 1px solid rgb(var(--hC_Border)); border-radius: var(--h8); background: rgba(var(--hC_Bg), 1); box-shadow: 0 var(--h10) var(--h30) rgba(var(--hC_BW1_Black), 0.5); overflow: hidden; display: flex; flex-direction: column; width: fit-content; }
+.Artstich_hColorPicker input { text-align: end; padding-right: 4px; }
+#hexInput2 { width: 56px; }
+.hCPr__valueG { border-bottom: 1px solid rgb(var(--hC_Border)); }
+.hCPr__header { padding: 4px 8px; display: grid; grid-template-columns: repeat(2, auto); justify-content: start; border-bottom: 1px solid rgb(var(--hC_Border)); width: 100%; }
+.hCPr__header h2 { font-weight: normal; color: rgb(var(--hC_hBtn_svg)); font-size: calc(var(--hColorPicker__font-size) * 2 + 2pt); }
+.hColorPicker__hCPr { width: fit-content; padding: 8px; box-sizing: border-box; display: flex; flex-direction: column; gap: 0px; margin: 0 auto; }
+.hColorPicker__ValueCopy { display: flex; justify-content: flex-end; gap: 10px; margin-bottom: 0px; width: 100%; }
+.hCPr__main { display: flex; gap: 20px; width: fit-content; }
+.hCPr__mainLeft { margin-top: 16px; margin-left: 8px; display: flex; flex-direction: column; gap: 10px; width: fit-content; }
+.hCPr__mainRight { display: flex; flex-direction: column; gap: 0px; width: fit-content; }
+.hCPr__mainPicker { display: flex; gap: var(--slider-spacing); align-items: center; width: fit-content; }
+.hCPr__mainPicker_colorAreaG { position: relative; width: var(--palette-size); height: var(--palette-size); }
+.hCPr__mainPicker_colorArea { width: 100%; height: 100%; border-radius: var(--border-radius); overflow: hidden; cursor: crosshair; position: relative; }
+
+#hCPr__mainPicker_colorCanvas { width: 100%; height: 100%; display: block; border-radius: var(--border-radius); }
+
+.hCPr__mainPicker_colorCursor { position: absolute; width: var(--palette-bar-size); height: var(--palette-bar-size); border: 2px solid rgb(var(--hC_BW7_White)); border-radius: 50%; box-shadow: 0 0 3px rgba(var(--hC_BW1_Black), 0.8); transform: translate(-50%, -50%); pointer-events: none; z-index: 10; }
+.hCPr__mainPicker_hueSliderG { margin-left: 8px; position: relative; width: var(--slider-width); height: var(--slider-height); }
+.hCPr__mainPicker_hueSlider { width: 100%; height: 100%; background: var(--hC_Rb__Bottom); border-radius: var(--border-radius); position: relative; cursor: pointer; }
+.hCPr__mainPicker_hueCursor { position: absolute; left: 50%; width: var(--slider-bar-width); height: var(--slider-bar-height); background: rgb(var(--hC_BW7_White)); border: 2px solid rgb(var(--hC_BW7_White)); border-radius: calc(var(--slider-bar-height) / 2); transform: translate(-50%, -50%); pointer-events: none; box-shadow: 0 0 3px rgba(var(--hC_BW1_Black), 0.8); }
+.hCPr__nodePreview { border-radius: var(--border-radius); cursor: pointer; margin-top: 10px; display: grid; place-items: center; height: 140px; background: transparent !important; border: none !important; width: fit-content; }
+
+#hPreview__Node { width: 100%; height: 136px; }
+#hCPr__nodeSvg { width: 288px; height: auto; }
+.hPreview__Node-Main { fill: rgb(var(--hC_BW4_Gray)); rx: 7.25; ry: 7.25; filter: url(#drop-shadow); }
+.hPreview__Node-Title { fill: rgb(var(--hC_CPr0__PurpleStd)); }
+.hPreview__Node-Divider { stroke: rgb(var(--hC_Bg)); stroke-width: .75px; stroke-miterlimit: 10; }
+.hPreview__Node-Txt { fill: rgb(var(--hC_CPr6__hNodeText)); font-family: MicrosoftYaHei,'Microsoft YaHei'; font-size: 12px; }
+.hPreview__Node-Dot { stroke-width: 0; }
+.hPreview__Node-Dot1 { fill: rgb(var(--hC_CPr5__hNodeDot)); r: 4.62; }
+.hPreview__Node-Input1 { fill: rgb(var(--hC_CPr8__hNodeLaten)); r: 3.87; }
+.hPreview__Node-Output2 { fill: rgb(var(--hC_CPr7__hNodeIMG)); stroke: rgb(var(--hC_BW1_Black)); stroke-width: .75px; r: 3.87; }
+.hPreview__Node-Input2 { fill: rgb(var(--hC_CPr9__hNodeVAE)); r: 3.87; }
+.hCPr__hsbBarKit { border-radius: var(--border-radius); padding: 15px; width: 288px; height: 96px; }
+.hCPr__hsbBar_sliderG { margin-bottom: 0; display: flex; align-items: center; gap: 0px; height: 24px; }
+.hCPr__hsbBar_sliderLabel { width: 50px; color: rgb(var(--hC_CPr6__hNodeText)); white-space: nowrap; }
+.hCPr__hsbBar_hsbBarG { display: flex; align-items: center; gap: 8px; flex: 1; }
+
+.slider { flex: 1; height: var(--hsb-height); background: var(--slider-bg); border-radius: calc(var(--hsb-height) / 2); position: relative; cursor: pointer; width: var(--hSlider-width); }
+.hCPr__sliderFill { position: absolute; height: 100%; width: 100%; left: 0; top: 0; border-radius: calc(var(--hsb-height) / 2); }
+.hCPr__hueSliderFill { background: var(--hC_Rb__Right); height: 100%; border-radius: calc(var(--hsb-height) / 2); }
+.slider-handle { position: absolute; width: var(--hsb-bar-width); height: var(--hsb-bar-height); background: var(--slider-handle-bg); border: var(--slider-handle-border); border-radius: calc(var(--hsb-bar-width) / 2); box-shadow: var(--slider-handle-shadow); transform: translate(-50%, -50%); cursor: pointer; z-index: 10; top: 50%; }
+.slider-handle:active { background: var(--slider-handle-active-bg); width: var(--slider-handle-active-size); height: 22px; transform: translate(-50%, -50%); }
+.hCPr__sliderValue { width: 40px; height: 22px; background: rgb(var(--hC_BW1_Black)); border: 1px solid rgb(var(--hC_Border)); border-radius: var(--border-radius); padding: 0px; color: rgb(var(--hC_hBtn_svg)); text-align: center; }
+.hCPr__valueG { display: flex; align-items: center; gap: 0; }
+.hCPr__valueLabel { width: 58px; font-size: 12px; color: rgb(var(--hC_CPr6__hNodeText)); height: 16px; line-height: 16px; padding: 0 4px; white-space: nowrap; text-align: right; }
+.value-input { height: 22px; background: rgb(var(--hC_BW1_Black)); border: 1px solid rgb(var(--hC_Border)); color: rgb(var(--hC_hBtn_svg)); font-size: 12px; font-weight: bold; padding: 0 4px; line-height: 14px; width: 48px; border-radius: 0; outline: none; }
+.value-input:first-of-type { border-radius: var(--border-radius) 0 0 var(--border-radius); border-right: none; }
+.rgb-input { width: 32px; border-radius: 0; border-right: none; }
+.rgb-input:first-of-type { border-radius: var(--border-radius) 0 0 var(--border-radius); }
+.rgb-input:last-of-type { border-right: 1px solid rgb(var(--hC_Border)); border-radius: 0; }
+.hCPr__rgbLabel { width: 36px; }
+
+.copy-btn { background: rgb(var(--hC_Border)); color: rgb(var(--hC_hBtn_svg)); border: 1px solid rgb(var(--hC_Border)); height: 22px; width: 22px; padding: 0; display: flex; justify-content: center; align-items: center; cursor: pointer; transition: background 0.2s; border-radius: 0 var(--border-radius) var(--border-radius) 0; position: relative; }
+.rgb-copy-btn { border-radius: 0 var(--border-radius) var(--border-radius) 0; margin-left: 0; }
+.copy-btn:hover { background: rgb(var(--hC_CPr0__PurpleStd)); }
+.hCPr__copyIcon { width: 10px; height: 12px; position: relative; transform: translateX(1px) translateY(-1px); }
+.hCPr__copyIcon-front { position: absolute; width: 10px; height: 12px; background: rgb(var(--hC_hBtn_svg)); border: 2px solid rgb(var(--hC_BW1_Black)); border-radius: 1px; top: 3px; left: -3px; }
+.hCPr__copyIcon-back { position: absolute; width: 10px; height: 12px; background: rgb(var(--hC_hBtn_svg)); border: 2px solid rgb(var(--hC_BW1_Black)); border-radius: 1px; }
+.hCPr__hTips { display: grid; grid-template-columns: repeat(2, auto); justify-content: start; align-items: center; }
+#hCPr__nodePreviewTips { color: rgb(var(--hC_CPr6__hNodeText)); font-size: var(--h16); pointer-events: none; display: flex; align-items: center; }
+.hCPr__nodeMode { padding: 4px 8px; background: rgb(var(--bar)); border-radius: var(--border-radius); pointer-events: none; color: rgb(var(--hC_CPr6__hNodeText)); font-size: var(--h16); display: flex; align-items: center; margin-left: -8px; }
+.slider-touch-area { position: absolute; top: -4px; bottom: -4px; left: -4px; right: -4px; z-index: 2; cursor: pointer; }
+.hCPr__mainPicker_hueSlider_touchArea { position: absolute; top: -4px; bottom: -4px; left: -4px; right: -4px; z-index: 2; cursor: pointer; }
+#hCPr__HUE_sliderControl { background: none; }
+#hCPr__HUE_sliderClip { width: 100% !important; }
+.hIcon { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
+.hBtn_svg { fill: rgb(var(--hC_hBtn_svg)); }
+.hBtn_full { fill: rgb(var(--hC_Bg)); }
+.hBtn_stroke { fill: none; stroke: rgb(var(--hC_hBtn_svg)); stroke-miterlimit: 10; }
+.hBtn_strokeRound { fill: none; stroke: rgb(var(--hC_hBtn_svg)); stroke-linecap: round; stroke-linejoin: round; }
+.hColor1_Red { fill: rgb(var(--hC1_Red)); }
+.hColor2_Orange { fill: rgb(var(--hC2_Orange)); }
+.hColor3_Yellow { fill: rgb(var(--hC3_Yellow)); }
+.hColor4_Green { fill: rgb(var(--hC4_Green)); }
+.hColor5_Cyan { fill: rgb(var(--hC5_Cyan)); }
+.hColor6_Blue { fill: rgb(var(--hC6_Blue)); }
+.hColor7_Purple { fill: rgb(var(--hC7_Purple)); }
+#hBtnX_barDivider .hBtn_svg { fill: rgb(var(--hC_hBarDivider));
+ }
+ `;
+ const styleSheet = document.createElement('style'); styleSheet.textContent = styles; document.head.appendChild(styleSheet);
+
+ // 【== 全局日志管理器 ==】
+ class __hLogManager {
+ constructor() { this.debugElement = null; this.maxLines = 7; this.lines = []; this.lastLogKey = null; this.repeatCount = 0; this.initDebugElement(); this.bindToWindow(); }
+ // 创建新的调试元素
+ initDebugElement() { this.debugElement = document.createElement('div'); this.debugElement.id = 'hDebugInfo_V2', this.debugElement.className = 'hDebugInfo_V2', this.debugElement.innerHTML = 'hNodeAlignKit正在初始化...'; document.body.appendChild(this.debugElement); }
+
+ getTimestamp() {
+ const now = new Date(), year = now.getFullYear(), month = String(now.getMonth() + 1).padStart(2, '0'), day = String(now.getDate()).padStart(2, '0');
+ const hours = String(now.getHours()).padStart(2, '0'), minutes = String(now.getMinutes()).padStart(2, '0'), seconds = String(now.getSeconds()).padStart(2, '0');
+ return `h${year}${month}${day}_t${hours}${minutes}${seconds}`;
+ }
+
+ // 检查参数是否为标识符:支持标识符类型:1. [--@开头+]结尾字符串 2. --@开头字符串 3. 数字(用于简洁标识) 4. Symbol类型
+ isIdentifier(arg) { return typeof arg === 'string' ? arg.startsWith('[--@') && arg.endsWith(']') || arg.startsWith('--@') : typeof arg === 'number' || typeof arg === 'symbol'; }
+ // 提取标识符和实际消息
+ parseLogArgs(args) { let identifier = null, messageArgs = args; args.length > 0 && this.isIdentifier(args[0]) && (identifier = args[0], messageArgs = args.slice(1)); return { identifier, messageArgs }; }
+
+ // 生成日志键(只基于标识符):如果有标识符,只使用标识符作为键;如果没有标识符,使用级别+时间戳+随机数作为键(确保每条无标识符的日志独立)
+ generateLogKey(level, identifier) { if (identifier) return String(identifier); return `${level}:${Date.now()}:${Math.random()}`; }
+
+ formatMessage(level, message, count = 1) {
+ const timestamp = this.getTimestamp(), timestampSpan = `${timestamp}`;
+ let displayMessage = count > 1 ? `${message} [${count}]` : message, contentSpan = `${displayMessage}`;
+ switch (level) {
+ case 'error': contentSpan = `${displayMessage}`; break;
+ case 'warn': contentSpan = `${displayMessage}`; break;
+ case 'info': contentSpan = `${displayMessage}`; break;
+ }
+ return `${timestampSpan}>_ ${contentSpan}`;
+ }
+
+ // 添加日志行(支持重复合并)
+ addLine(level, identifier, message, count = 1) {
+ const currentKey = this.generateLogKey(level, identifier);
+ currentKey === this.lastLogKey
+ ? (this.repeatCount++, this.lines.length > 0 && (() => { const lastLine = this.lines[this.lines.length - 1]; lastLine.count = this.repeatCount; lastLine.message = message; lastLine.html = this.formatMessage(level, message, this.repeatCount); })())
+ : (this.lastLogKey = currentKey, this.repeatCount = 1, this.lines.push({ level, message, count: 1, html: this.formatMessage(level, message, 1) }), this.lines.length > this.maxLines && this.lines.shift());
+ this.updateDisplay();
+ }
+ // 渐隐日志行
+ updateDisplay() {
+ if (!this.debugElement) return;
+ const totalLines = this.lines.length;
+ const displayLines = this.lines.map((line, index) => {
+ const opacity = 1.0 - (0.7 * ((totalLines - 1 - index) / (totalLines - 1))); return `
${line.html}
`;
+ }).join('');
+ this.debugElement.innerHTML = displayLines;
+ }
+
+ // 内部日志方法(处理标识符和消息)
+ _log(level, ...args) {
+ const { identifier, messageArgs } = this.parseLogArgs(args);
+ const message = messageArgs.map(arg => typeof arg === 'object' ? (() => { try { return JSON.stringify(arg); } catch { return String(arg); } })() : String(arg)).join(' ');
+ this.addLine(level, identifier, message);
+ }
+
+ // 公共日志方法
+ log(...args) { this._log('log', ...args); }
+ error(...args) { this._log('error', ...args); }
+ warn(...args) { this._log('warn', ...args); }
+ info(...args) { this._log('info', ...args); }
+ debug(...args) { this._log('debug', ...args); }
+
+ bindToWindow() { window.hLog = { log: this.log.bind(this), error: this.error.bind(this), warn: this.warn.bind(this), info: this.info.bind(this), debug: this.debug.bind(this) }; }
}
- };
- // ======================== 具体图标模板 ========================
- const Templates = {
- // 拖动按钮
- hBtn_moveElement: () => {
- const dotMatrixContent = Elements.dotMatrix().replace(/id="dot"/g, 'id="base-dot"');
- return `
- ${dotMatrixContent}
- ${dotMatrixContent.replace(/id="base-dot"/g, 'use href="#base-dot"')}
- `;
- },
- // 左对齐
- hBtnA_alignLeft: () => `
- ${Elements.rect(10, 9, 12, 6)}
- ${Elements.rect(10, 19, 18, 6)}
- ${Elements.vLine(5, 24)}`,
- // 竖向居中对齐
- hBtnB_alignCenterV: () => `
- ${Elements.rect(9, 9, 14, 6)}
- ${Elements.rect(6, 19, 20, 6)}
- ${Elements.vLine(15, 24)}`,
- // 右对齐
- hBtnC_alignRight: () => `
- ${Elements.rect(11, 9, 12, 6)}
- ${Elements.rect(5, 19, 18, 6)}
- ${Elements.vLine(26, 24)}`,
- // 顶部对齐
- hBtnD_alignTop: () => `
- ${Elements.rect(8, 9, 6, 12)}
- ${Elements.rect(18, 9, 6, 18)}
- ${Elements.hLine(5, 24)}`,
- // 横向居中对齐
- hBtnE_alignCenterH: () => `
- ${Elements.rect(18, 6, 6, 20)}
- ${Elements.rect(8, 9, 6, 14)}
- ${Elements.hLine(15, 24)}`,
- // 底部对齐
- hBtnF_alignButton: () => `
- ${Elements.rect(8, 11, 6, 12)}
- ${Elements.rect(18, 5, 6, 18)}
- ${Elements.hLine(26, 24)}`,
- // 横向分布 基准线(y=7,长度24px)
- hBtnG_distributionH: () => `
- ${Elements.rect(19, 5, 2, 7)}
- ${Elements.rect(11, 5, 2, 7)}
- ${Elements.rect(19, 13, 6, 11)}
- ${Elements.rect(7, 13, 6, 15)}
- ${Elements.hLine(7, 24)}`,
- // 竖向分布 基准线(x=6,高度24px)
- hBtnH_distributionV: () => `
- ${Elements.rect(12, 7, 11, 6)}
- ${Elements.rect(4, 11, 7, 2)}
- ${Elements.rect(4, 19, 7, 2)}
- ${Elements.rect(12, 19, 16, 6)}
- ${Elements.vLine(6, 24)}`,
- // 等宽
- hBtnI_equalWidth: () => `
- ${Elements.rect(9, 9, 14, 6)}
- ${Elements.rect(11, 19, 10, 6)}
- ${Elements.vLine(4, 24)}
- ${Elements.vLine(26, 24)}
- ${Elements.rect(4, 21, 5, 2)}
- ${Elements.rect(23, 21, 5, 2)}`,
- // 等高
- hBtnJ_equalHeight: () => `
- ${Elements.rect(18, 9, 6, 14)}
- ${Elements.rect(8, 11, 6, 10)}
- ${Elements.hLine(4, 24)}
- ${Elements.hLine(26, 24)}
- ${Elements.rect(10, 4, 2, 5)}
- ${Elements.rect(10, 23, 2, 5)}`
- };
- // ======================== 最终导出的SVG常量 ========================
- const hSvg_moveElement = buildSVG(Templates.hBtn_moveElement(), {
- t: '1745769856000',
- pId: '8808'
- });
- const hSvgA_alignLeft = buildSVG(Templates.hBtnA_alignLeft(), {
- t: '1725534370541',
- pId: '2104'
- });
- const hSvgB_alignCenterV = buildSVG(Templates.hBtnB_alignCenterV(), {
- t: '1725534363707',
- pId: '1810'
- });
- const hSvgC_alignRight = buildSVG(Templates.hBtnC_alignRight(), {
- t: '1725534384109',
- pId: '2398'
- });
- const hSvgD_alignTop = buildSVG(Templates.hBtnD_alignTop(), {
- t: '1725534367556',
- pId: '1957'
- });
- const hSvgE_alignCenterH = buildSVG(Templates.hBtnE_alignCenterH(), {
- t: '1725534379860',
- pId: '2251'
- });
- const hSvgF_alignButton = buildSVG(Templates.hBtnF_alignButton(), {
- t: '1725534360155',
- pId: '1663'
- });
- const hSvgG_distributionH = buildSVG(Templates.hBtnG_distributionH(), {
- t: '1725534354023',
- pId: '1516'
- });
- const hSvgH_distributionV = buildSVG(Templates.hBtnH_distributionV(), {
- t: '1725534350231',
- pId: '1369'
- });
- const hSvgI_equalWidth = buildSVG(Templates.hBtnI_equalWidth(), {
- t: '1725606034670',
- pId: '7216'
- });
- const hSvgJ_equalHeight = buildSVG(Templates.hBtnJ_equalHeight(), {
- t: '1725606224564',
- pId: '7793'
- });
- const ButtonManager = {
- zoomLevels: [1, 1.5, 2, 0.5],
- currentZoom: 1,
- isSystemZoom: false,
- isRunButtonAligned: false,
- linkMode: 0,
- linkColors: ['', '#60A5FA', '#355757', '#675535', '#32512A'],
- linkOffset: -1,
- syncRunButtonPosition() {
- if (!this.isRunButtonAligned || this.linkMode === 0) return;
- const alignKit = this.hMainToolbar;
- const runKit = document.querySelector('.p-panel.actionbar');
- if (alignKit && runKit) {
- const alignRect = alignKit.getBoundingClientRect();
- const runRect = runKit.getBoundingClientRect();
- const offset = this.linkOffset;
- let left, top;
- switch (this.linkMode) {
- case 1:
- left = alignRect.right + window.scrollX + offset;
- top = alignRect.top + window.scrollY;
- break;
- case 2:
- left = alignRect.left + window.scrollX;
- top = alignRect.bottom + window.scrollY + offset;
- break;
- case 3:
- left = alignRect.left + window.scrollX;
- top = alignRect.top + window.scrollY - runRect.height - offset;
- break;
- case 4:
- left = alignRect.left + window.scrollX - runRect.width - offset;
- top = alignRect.top + window.scrollY;
- break;
+ // 【== HTML 结构创建 ==】
+ function __hCreateHTML() {
+ const container = document.createElement('div'); container.id = 'hNodeAlignKit';
+ container.innerHTML = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Node Align Pro
+
+
+
+
+
+
+
+
+ `;
+ return container;
+ }
+
+ // 【== 统一弹窗定位管理器 ==】
+ class __hMgr_PopEl__Position {
+ constructor() { this.container = null; this.currentScale = 1; }
+ init(container) { this.container = container; window.containerController && (this.currentScale = window.containerController.scale); }
+
+ /** * 计算相对于容器的位置 * @param {DOMRect} targetRect - 目标元素的边界矩形 * @param {String} align - 对齐方式:'top', 'bottom', 'left', 'right', 'center' * @param {Object} offset - 偏移量 {x: 0, y: 0} * @returns {Object} 相对位置 {left, top} */
+ calculateRelativePosition(targetRect, align = 'bottom', offset = { x: 0, y: 0 }) {
+ if (!this.container) return { left: 0, top: 0 };
+ const containerRect = this.container.getBoundingClientRect(), currentScale = window.containerController ? window.containerController.scale : 1;
+ const relativeLeft = (targetRect.left - containerRect.left) / currentScale, relativeTop = (targetRect.top - containerRect.top) / currentScale, relativeRight = (targetRect.right - containerRect.left) / currentScale, relativeBottom = (targetRect.bottom - containerRect.top) / currentScale;
+ let left = 0, top = 0;
+ switch (align) {
+ case 'top': left = relativeLeft + offset.x; top = relativeTop + offset.y; break;
+ case 'bottom': left = relativeLeft + offset.x; top = relativeBottom + offset.y; break;
+ case 'left': left = relativeLeft + offset.x; top = relativeTop + offset.y; break;
+ case 'right': left = relativeLeft + offset.x; top = relativeTop + offset.y; break;
+ case 'center': left = relativeLeft + (targetRect.width / currentScale / 2) + offset.x; top = relativeTop + (targetRect.height / currentScale / 2) + offset.y; break;
+ default: left = relativeLeft + offset.x; top = relativeBottom + offset.y;
+ }
+ return { left, top };
}
- runKit.style.left = `${left}px`;
- runKit.style.top = `${top}px`;
- }
- },
- ColorConfig: {
- colors: {
- red: '#a93232',
- orange: '#79461d',
- yellow: '#6e6e1d',
- green: '#2b652b',
- cyan: '#248382',
- blue: '#246283',
- purple: '#3c3c83',
- custom: '#ffffff'
- },
- colorMapping: {
- titleDarken: -0.1,
- noteTextLighten: 0.2
- }
- },
- isInitialized: false,
- hMainToolbar: null,
- contextMenu: null,
- isPermanent: true,
- initialX: 0,
- initialY: 0,
- dragStartX: 0,
- dragStartY: 0,
- isDragging: false,
- hasShownhTooltip: false,
- isShow: true,
- init() {
- if (this.isInitialized) return;
- this.isRunButtonAligned = false;
- this.currentZoomIndex = 0;
- this.systemWideZoom = false;
- this.boundOnDragging = this.onDragging.bind(this);
- this.boundOnDragEnd = this.onDragEnd.bind(this);
- window.addEventListener('beforeunload', () => {
- document.removeEventListener('mousedown', this.hideMenuHandler);
- document.removeEventListener('keydown', this.escKeyHandler);
- });
- const style = document.createElement('style');
- style.textContent = `
- /* 2025-04-28 07:56:21 ArtsticH-提取自 SVG 的全局通用样式 */
- .ht-1 {
- fill: #6b6b70; /* 主图标颜色 */
- stroke-width: 0px;
- }
- .ht-2 {
- fill: #525252; /* 拖动按钮圆点颜色 */
- stroke-width: 0px;
- }
- /* 修改运行按钮容器高度 */
- .p-panel.actionbar {
- height: 48px !important; /* 原高度为32px,增加2px */
- min-height: 48px !important; /* 同步调整最小高度 */
- align-items: center; /* 确保内容垂直居中 */
- }
- #pv_id_11_content {
- /* 这是目标元素-ComfyUI官方原生的【运行按钮】 */
- /* 选择器: #comfyui-body-top > div.comfyui-menu.flex.items-center > div.p-panel.p-component.actionbar.w-fit */
- /* Xpath: //*[@id="comfyui-body-top"]/div[1]/div[4] */
- /* Xpath: /html/body/div[1]/main/div[1]/div[1]/div[1]/div[4] */
- }
- #hNodeAlignKit { /* 【对齐按钮组样式】 */
- /* white-space: nowrap; 设置元素为绝对定位方式 position: absolute; */
- top: 20px;
- right: 10px;
- left: 10px;
- bottom: unset;
- display: flex;
- flex-direction: row;
- background: #18181B;
- padding: 8px;
- border-radius: 6px;
- z-index: 9999;
- width: fit-content;
- height: 48px;
- position: absolute;
- border: 1px solid;
- border-color: #3D3D43;
- align-self: center;
- box-sizing: border-box;
- /* place-items: center; 子元素水平和垂直居中 */
- /* text-align: center; 让子元素水平居中 */
- max-width: 800;
- min-width: 420;
- }
- /* 禁止子元素改变容器尺寸 */
- #hNodeAlignKit > * {
- flex-shrink: 0;
- }
- .hC_alignBtn {
- width: 32px;
- height: 32px;
- background-size: contain;
- background-repeat: no-repeat;
- background-position: center;
- background-color: #18181B;
- border: none;
- cursor: pointer;
- padding: 4px;
- border-radius: 4px;
- transition: background-color 0.3s ease;
- align-self: center;
- vertical-align: middle;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
- .hC_alignBtn:hover {
- background-color: #222226;
- }
- /* ↓2025-04-28 01:09:28 Artstich新增移动按钮专属悬浮色-开始 fill: #232935 !important; */
- #hBtn_moveElement:hover {
- background-color: #70A3F3 !important;
- cursor: move;
- }
- /* ↓2025-04-28 18:00:05 新增SVG图标样式-开始 */
- /* .hC_alignBtn { border: 1px solid red !important; } 显示按钮大小位置示意测试 */
- .hC_alignBtn svg path {
- pointer-events: none; /* 防止路径阻挡点击 */
- }
- .hC_alignBtn:hover svg {
- filter: drop-shadow(0 2px 4px rgba(255,255,255,0.2));
- }
- /* #hBtn_moveElement:hover svg .ht-1 {fill: #232935 !important;} */
- #hNodeAlignKit #hBtn_moveElement:hover svg .ht-1 { /* 精准覆盖 SVG 内部类名 */
- fill: #232935 !important; /* 优先级覆盖内联样式-增加嵌套层级 */
- }
- /* ↑2025-04-28 01:09:28 Artstich新增移动按钮专属悬浮色-结束 */
- .hC_dividerLine {
- width: 1px;
- height: 40px;
- background-color: #27272B;
- margin: 0px 10px;
- border-radius: 9px;
- cursor: move;
- align-self: center;
- }
- .hC_dividerLine:active {
- cursor: grabbing;
- }
- /* 提示样式 */
- #hTooltip {
- position: absolute;
- bottom: -20px;
- right: 0;
- background: #70A3F3;
- color: white;
- padding: 5px 10px;
- border-radius: 6px;
- display: none;
- z-index: 1001;
- white-space: nowrap;
- font-size: 16px;
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-右键菜单容器样式 */
- #hContext-Menu {
- display: flex; /* flex布局 */
- white-space: nowrap; /* 防止换行、合并空白字符 */
- box-sizing: border-box;
- background: #18181B; /* 设置提示框的背景颜色-测试定位用 #70A3F3; */
- align-items: center; /* 确保子元素垂直居中 */
- justify-content: center; /* 确保子元素水平居中 */
- width: auto; /* 改为自动宽度width: 1300;auto; */
- height: auto; /* 保持固定高度height: 32px; */
- border-radius: 6px;
- z-index: 9999;
- padding: 0 8px; /* 添加内边距 */
- gap: 6px; /* 按钮间距 */
- margin-top: -1px !important; /* 微调垂直偏移量 */
- margin-left: -644.28px;/* 微调水平偏移量 margin-left: -630.73px; */
- /* margin-left: -200px; 向左偏移 20px */
- /* display: inline-block; */
- /* vertical-align: middle; */
- /* align-self: center; 新增:确保单个按钮垂直居中 */
- /* place-items: center; 子元素水平和垂直居中 */
- /* text-align: center; 让子元素水平居中 */
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-右键菜单按钮样式-精准控制按钮垂直位置 */
- #hContext-Menu :is(input, button) {
- height: 26; /* 高度auto;height: 26px; */
- min-height: 16px; /* 最小高度 */
- font-size: 14px;
- background: #09090B;
- border: 1px solid #3D3D43;
- border-radius: 6px;
- color: #fff;
- padding: 4px 12px; /* 调整内边距 */
- margin: 0; /* 移除外边距。 按钮间距margin: 0 3px; */
- transition: all 0.1s ease; /* 过渡动画 transition: all 0.2s ease; */
- position: relative !important;
- }
- #hContext-Menu button {
- min-height: 32px !important;
- /* top: -233x !important; 微调垂直偏移量 */
- /* right: 35px !important; 微调水平偏移量 */
- }
- #hContext-Menu input {
- outline: none;
- min-height: 32px !important;
- top: -1.5px !important; /* 微调垂直偏移量 */
- /* right: 35px !important; 微调水平偏移量 */
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-悬停效果 */
- #hContext-Menu :is(input, button):hover {
- background: #222226; /* 悬停背景色 background: #222226; */
- border-color: #70A3F3; /* 悬停边框色 border-color: #70A3F3; */
- transform: scale(1.1); /* 轻微放大 transform: scale(1.1); */
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-点击效果 */
- #hContext-Menu :is(input, button):active {
- background: #2D2D32; /* 点击背景色 background: #2D2D32; */
- transform: scale(0.98); /* 轻微缩小 transform: scale(0.98); */
- }
- #hContext-Menu input:hover {
- outline: none;
- transform: scale(1) !important; /* 轻微放大 transform: scale(1.1); */
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-点击效果 */
- #hContext-Menu input:active {
- outline: none;
- transform: scale(0.99) !important; /* 轻微缩小 transform: scale(0.98); */
- }
- /* 2025-04-28 03:48:09 ArtsticH新增-文字标签和文字链接样式 */
- #hContext-Menu a.no-underline {
- color: #A1A1AA; /* 文字颜色 color: #A1A1AA; */
- text-decoration: none; /* 去除下划线 text-decoration: none; */
- margin-right: 8px; /* 右侧间距 margin-right: 8px; */
- /* display: inline-block !important; */
- /* vertical-align: middle !important; */
- /* line-height: 32px !important; 与按钮高度一致 */
- /* margin-right: 8px !important; */
- }
- /* UI缩放按钮专用样式 */
- #ui-scale-btn {
- width: 82px !important;
- min-width: 82px !important;
- max-width: 82px !important;
- white-space: nowrap;
- overflow: hidden;
-
- box-sizing: border-box;
-
- padding: 4px 0px !important;
- }
- /* 覆盖通用按钮的宽度设置 */
- #hContext-Menu button#ui-scale-btn {
- width: 80px !important;
- flex-shrink: 0; /* 禁止在flex布局中收缩 */
- }
- /* 2025-04-28 18:44:47 新增颜色按钮专用样式 */
- #hNodeAlignKit [id^="color"] {
- width: 24px !important;
- height: 24px !important;
- padding: 0px !important;
- margin: 0 2px !important;
- background: none !important;
- margin-top: 40px; /* 增加上边距,使颜色按钮位于基础按钮下方 */
- }
- #hNodeAlignKit [id^="color"] svg {
- display: block !important;
- width: 100% !important;
- height: 100% !important;
- transition: transform 0.1s ease;
- }
- #hNodeAlignKit [id^="color"]:hover svg {
- transform: scale(1.5);
- filter: drop-shadow(0 2px 4px rgba(255,255,255,0.2));
- }
- /* 2025-04-28 18:44:47 颜色按钮激活状态 */
- .hC_alignBtn.color-active {
- box-shadow: 0 0 0 2px #70A3F3;
- }
- /* 颜色选择器图标特殊样式 */
- #colorPick svg {
- position: relative;
- background: rgba(0,0,0,1); /* rgba(7,7,8,0.8); background: rgba(100,100,100,0.3); */
- border-radius: 50%; /* border-radius: 6px; */
- transition: transform 0.1s ease; /* 2025-04-28 18:44:47 颜色选择器图标动画 */
- }
- #colorPick:hover svg {
- transform: rotate(15deg);
- }
- /* 清除按钮特殊样式 */
- #colorClear {
- position: relative;
- }
- #colorClear::after {
- content: "×";
- position: absolute;
- color: #ff4444;
- font-size: 20px;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- transition: font-size 0.1s ease;
- }
- #colorClear:hover::after {
- font-size: 32px;
- }
- /* 2025-04-28 18:44:47 颜色按钮提示 .color-tooltip { */
- .color-hTooltip {
- position: absolute;
- bottom: -25px;
- background: #333;
- color: white;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 12px;
- white-space: nowrap;
- }
- `;
- document.head.appendChild(style);
- this.hMainToolbar = document.createElement('div');
- this.hMainToolbar.id = 'hNodeAlignKit';
- this.restorePosition();
- document.body.appendChild(this.hMainToolbar);
- const buttons = this.getButtons();
- buttons.forEach(btn => {
- if (btn.type === 'hC_dividerLine') {
- const hC_dividerLine = document.createElement('div');
- hC_dividerLine.classList.add('hC_dividerLine');
- this.hMainToolbar.appendChild(hC_dividerLine);
- } else {
- const button = document.createElement('button');
- button.id = btn.id;
- button.classList.add('hC_alignBtn');
- button.innerHTML = btn.svg;
- button.addEventListener('click', (event) => {
- btn.action.call(this, event);
- event.stopPropagation();
- });
- this.hMainToolbar.appendChild(button);
+
+ // 定位菜单
+ positionMenu() {
+ const menuContainer = document.getElementById('h6__hMenu'), targetContainer = document.getElementById('h2__hNodeAlignPro');
+ if (!menuContainer || !targetContainer || !this.container) return;
+ const targetRect = targetContainer.getBoundingClientRect(), position = this.calculateRelativePosition(targetRect, 'bottom', { x: 0, y: 0 });
+ menuContainer.style.left = `${position.left}px`; menuContainer.style.top = `${position.top - 42}px`;
}
- });
- this.hMainToolbar.addEventListener('mousedown', (e) => {
- const ishBtn_moveElement = e.target.closest('#hBtn_moveElement') !== null;
- const ishC_dividerLine = e.target.classList.contains('hC_dividerLine');
- if (ishBtn_moveElement || ishC_dividerLine) {
- this.onDragStart(e);
+
+ // 定位取色器
+ positionColorPicker() {
+ const colorPickerPanel = document.getElementById('Artstich_hColorPicker'), colorBar = document.getElementById('h1__hApBar1_Color');
+ if (!colorPickerPanel || !colorBar || !this.container) return;
+ const colorBarRect = colorBar.getBoundingClientRect(), position = this.calculateRelativePosition(colorBarRect, 'bottom', { x: 0, y: 0 });
+ colorPickerPanel.style.left = `${position.left}px`; colorPickerPanel.style.top = `${position.top}px`;
}
- });
- this.isInitialized = true;
- this.showContextMenu();
- const hTooltip = document.createElement('div');
- hTooltip.id = 'hTooltip';
- hTooltip.textContent = '右键可选按钮呈现模式';
- this.hMainToolbar.appendChild(hTooltip);
- if (!this.hasShownhTooltip) {
- this.hMainToolbar.addEventListener('mouseenter', () => {
- if (!this.hasShownhTooltip) {
+
+ /** * 通用定位方法 * @param {HTMLElement} popupElement - 弹出元素 * @param {HTMLElement} targetElement - 目标元素 * @param {String} align - 对齐方式 * @param {Object} offset - 偏移量 */
+ positionPopup(popupElement, targetElement, align = 'bottom', offset = { x: 0, y: 0 }) {
+ if (!popupElement || !targetElement || !this.container) return;
+ const targetRect = targetElement.getBoundingClientRect(), position = this.calculateRelativePosition(targetRect, align, offset);
+ popupElement.style.left = `${position.left}px`; popupElement.style.top = `${position.top}px`;
+ }
+ }
+
+ // 【== 菜单自动隐藏管理器 ==】
+ class __hMgr_MenuHide {
+ constructor() { this.menuContainer = document.getElementById('h6__hMenu'), this.init(); }
+ init() { if (!this.menuContainer) return; this.bindMenuButtons(), this.bindDropdownOptions(); }
+ bindMenuButtons() { const buttonSelectors = ['#hReset', '#hBugReport', '#hGuide', '#hBack']; buttonSelectors.forEach(selector => { const button = this.menuContainer.querySelector(selector); button && button.addEventListener('click', () => this.hideMenu()); }); }
+ bindDropdownOptions() { const options = this.menuContainer.querySelectorAll('.hCMP-hSel__option'); options.forEach(option => { option.addEventListener('click', () => this.hideMenu()); }); }
+ hideMenu() { if (this.menuContainer && this.menuContainer.style.display !== 'none') { this.menuContainer.style.display = 'none'; } }
+ showMenu() { if (this.menuContainer) { this.menuContainer.style.display = 'block'; } }
+ }
+
+ // 【== Actionbar联动管理器 ==】
+ const __hMgr_ACbar = {
+ linkMode: 0, linkOffset: 0, throttleTimer: null,
+
+ syncRunButtonPosition() {
+ if (this.linkMode !== 1) return;
+ const alignKit = document.getElementById('hNodeAlignKit'); const runKit = document.querySelector('.p-panel.actionbar');
+ if (alignKit && runKit) {
+ const alignRect = alignKit.getBoundingClientRect(), offset = this.linkOffset;
+ let left = alignRect.left + window.scrollX, top = alignRect.bottom + window.scrollY + offset;
+ runKit.style.position = 'fixed'; runKit.style.left = `${left}px`; runKit.style.top = `${top}px`; runKit.style.zIndex = '10000';
+ }
+ },
+ // 设置联动模式
+ setLinkMode(mode) {
+ this.linkMode = mode; const isLinked = mode === 1;
+ if (isLinked) {
+ this.syncRunButtonPosition();
+ document.addEventListener('mousemove', this.throttledSync.bind(this));
+ } else {
+ document.removeEventListener('mousemove', this.throttledSync.bind(this));
+ const runKit = document.querySelector('.p-panel.actionbar');
+ if (runKit) { runKit.style.left = ''; runKit.style.top = ''; runKit.style.position = ''; }
+ }
+ this.updateDropdownText(); this.saveModeToStorage();
+ },
+
+ throttledSync() { if (!this.throttleTimer) { this.throttleTimer = setTimeout(() => { this.syncRunButtonPosition(); this.throttleTimer = null; }, 16); } },
+ // 更新菜单文本
+ updateDropdownText() {
+ const dragBtn = document.querySelector('[data-target="hCMP-hSel__drag-options"]'), dragOption1 = document.querySelector('[data-value="hDragMode0_Link"]'), dragOption2 = document.querySelector('[data-value="hDragMode1_Split"]');
+ if (dragBtn) { dragBtn.textContent = this.linkMode === 1 ? '联 动' : '解 耦'; }
+ if (dragOption1 && dragOption2) { dragOption1.classList.toggle('selected', this.linkMode === 1); dragOption2.classList.toggle('selected', this.linkMode === 0); }
+ },
+ saveModeToStorage() { localStorage.setItem('NodeAlignProRunButtonLink', this.linkMode.toString()); },
+ loadModeFromStorage() { const saved = localStorage.getItem('NodeAlignProRunButtonLink'); if (saved) { this.linkMode = parseInt(saved); this.linkMode === 1 && setTimeout(() => this.syncRunButtonPosition(), 100); } return this.linkMode; }
+ };
+
+ // 【== 解耦式容器控制器 ==】
+ class __hController_hNAPKit {
+ // 核心容器元素 | 基础状态:缩放比例、坐标 | 拖拽状态:拖拽中标识、起始坐标、拖拽元素 | 边界检测:边界数据、宽度限制标识 | 执行初始化逻辑
+ constructor(container) { this.hNodeAlignKit = container; this.scale = 1.0, this.posX = 0, this.posY = 0; this.isDragging = false, this.dragStartX = 0, this.dragStartY = 0, this.dragStartPosX = 0, this.dragStartPosY = 0, this.dragElement = null; this.boundaryData = null, this.isWidthConstrained = false; this.init(); }
+ // 初始化:容器核心样式(固定定位、变换原点、性能优化);依次执行加载状态、绑定事件、边界检查、初始化缩放菜单文本
+ init() { this.hNodeAlignKit.style.position = 'fixed', this.hNodeAlignKit.style.transformOrigin = '0 0', this.hNodeAlignKit.style.willChange = 'transform'; this.loadState(), this.bindEvents(), this.enforceBoundaries(), this.updateScaleMenuText(); }
+ // 获取容器在屏幕上的边界(使用屏幕坐标):获取容器屏幕坐标矩形,返回屏幕坐标(left/right/top/bottom)、屏幕像素宽/高(已包含缩放)
+ getContainerBounds() { const rect = this.hNodeAlignKit.getBoundingClientRect(); return { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width, height: rect.height }; }
+ // 执行容器变换赋值(平移+缩放),保存状态、更新缩放菜单文本;联动模式(linkMode=1)下同步运行按钮位置
+ updateTransform() { this.hNodeAlignKit.style.transform = `translate(${Math.round(this.posX)}px, ${Math.round(this.posY)}px) scale(${this.scale})`; this.saveState(); this.updateScaleMenuText(); if (__hMgr_ACbar.linkMode === 1) __hMgr_ACbar.syncRunButtonPosition(); }
+
+ // 开始拖拽
+ startDrag(clientX, clientY, element = null) {
+ // 标记拖拽中;记录拖拽起始坐标、容器起始位置、拖拽元素;初始化边界数据(使用屏幕坐标),设置拖拽时的z-index
+ this.isDragging = true, this.dragStartX = clientX, this.dragStartY = clientY, this.dragStartPosX = this.posX, this.dragStartPosY = this.posY, this.dragElement = element; this.boundaryData = this.initBoundaryData(clientX, clientY), this.setDraggingZIndex();
+ element && (element.style.cursor = 'move');
+ }
+
+ // 拖拽移动
+ dragMove(clientX, clientY) {
+ if (!this.isDragging) return;
+ const deltaX = clientX - this.dragStartX, deltaY = clientY - this.dragStartY, { deltaX: limitedDeltaX, deltaY: limitedDeltaY } = this.applyBoundaryLimits(deltaX, deltaY);
+ this.posX = this.dragStartPosX + limitedDeltaX, this.posY = this.dragStartPosY + limitedDeltaY; this.updateTransform();
+ }
+
+ // 重置
+ reset() {
+ this.scale = 1.0; const originalSize = this.getContainerOriginalSize(), scaledWidth = originalSize.width * this.scale, scaledHeight = originalSize.height * this.scale, windowWidth = window.innerWidth, windowHeight = window.innerHeight;
+ this.posX = Math.max(0, (windowWidth - scaledWidth) / 2); this.posY = windowHeight * 0.2; this.updateTransform(), this.enforceBoundaries();
+ if (__hMgr_ACbar.linkMode === 1) setTimeout(() => __hMgr_ACbar.syncRunButtonPosition(), 100);
+ }
+
+ getContainerOriginalSize() { return { width: this.hNodeAlignKit.offsetWidth, height: this.hNodeAlignKit.offsetHeight }; }
+
+ // 初始化边界数据(在开始拖拽时调用)- 修正版:使用屏幕坐标
+ initBoundaryData(startClientX, startClientY) {
+ const viewportWidth = window.innerWidth, viewportHeight = window.innerHeight, containerBounds = this.getContainerBounds();
+ const screenDistance = { left: startClientX, right: viewportWidth - startClientX, top: startClientY, bottom: viewportHeight - startClientY };
+ const containerDistance = { left: startClientX - containerBounds.left, right: containerBounds.right - startClientX, top: startClientY - containerBounds.top, bottom: containerBounds.bottom - startClientY };
+ const safeDistance = { left: screenDistance.left - containerDistance.left, right: screenDistance.right - containerDistance.right, top: screenDistance.top - containerDistance.top, bottom: screenDistance.bottom - containerDistance.bottom };
+ return { startX: startClientX, startY: startClientY, safeDistance };
+ }
+
+ // 应用边界限制(在拖拽移动时调用)
+ applyBoundaryLimits(deltaX, deltaY) {
+ if (!this.boundaryData) return { deltaX, deltaY };
+ const { safeDistance } = this.boundaryData;
+ let limitedDeltaX = deltaX < 0 ? Math.max(deltaX, -safeDistance.left) : Math.min(deltaX, safeDistance.right);
+ let limitedDeltaY = deltaY < 0 ? Math.max(deltaY, -safeDistance.top) : Math.min(deltaY, safeDistance.bottom);
+ return { deltaX: limitedDeltaX, deltaY: limitedDeltaY };
+ }
+
+ // 停止拖拽
+ stopDrag() {
+ this.isDragging = false; this.boundaryData = null; this.dragElement && (this.dragElement.style.cursor = 'default', this.dragElement = null); this.resetZIndex(); this.enforceBoundaries();
+ if (__hMgr_ACbar.linkMode === 1) __hMgr_ACbar.syncRunButtonPosition();
+ }
+ // 强制容器在边界内(就近原则弹回)- 修正版:使用屏幕坐标
+ enforceBoundaries() {
+ const containerBounds = this.getContainerBounds(), viewportWidth = window.innerWidth, viewportHeight = window.innerHeight, originalSize = this.getContainerOriginalSize(), scaledWidth = originalSize.width * this.scale;
+ this.isWidthConstrained = scaledWidth > viewportWidth;
+
+ // 宽度受限:强制缩放为1+左对齐,修正垂直位置
+ if (this.isWidthConstrained) {
+ const currentTop = containerBounds.top; this.scale = 1.0, this.posX = 0; const containerTop = currentTop + (this.posY - currentTop); this.posY = containerTop;
+ this.updateTransform(); const newBounds = this.getContainerBounds();
+ let adjustY = newBounds.top < 0 ? -newBounds.top : (newBounds.bottom > viewportHeight ? viewportHeight - newBounds.bottom : 0); adjustY !== 0 && (this.posY += adjustY, this.updateTransform());
+ return;
+ }
+
+ let adjustX = 0, adjustY = 0;
+ // 左右边界修正
+ adjustX = containerBounds.left < 0 && containerBounds.right > viewportWidth ? (viewportWidth - (containerBounds.right - containerBounds.left)) / 2 - containerBounds.left : containerBounds.left < 0 ? -containerBounds.left : containerBounds.right > viewportWidth ? viewportWidth - containerBounds.right : 0;
+ // 上下边界修正
+ adjustY = containerBounds.top < 0 && containerBounds.bottom > viewportHeight ? (viewportHeight - (containerBounds.bottom - containerBounds.top)) / 2 - containerBounds.top : containerBounds.top < 0 ? -containerBounds.top : containerBounds.bottom > viewportHeight ? viewportHeight - containerBounds.bottom : 0;
+
+ if (adjustX === 0 && adjustY === 0) return;
+ this.posX += adjustX / this.scale, this.posY += adjustY / this.scale, this.updateTransform();
+ }
+
+ // 缩放 - 基于当前鼠标位置
+ zoom(deltaScale, clientX, clientY) {
+ const oldScale = this.scale;
+ let newScale = Math.max(0.1, Math.min(5, this.scale + deltaScale));
+ if (newScale === oldScale) return;
+
+ const originalSize = this.getContainerOriginalSize(), viewportWidth = window.innerWidth, scaledWidth = originalSize.width * newScale;
+ if (scaledWidth > viewportWidth) { newScale = 1.0; const currentPosY = this.posY; this.scale = newScale, this.posX = 0, this.posY = currentPosY; this.updateTransform(), this.enforceBoundaries(); return; }
+
+ const oldPosX = this.posX, oldPosY = this.posY; this.scale = newScale;
+ // 鼠标坐标存在时:计算缩放前鼠标在容器局部坐标系的位置,调整容器位置以保持鼠标指向同一局部位置(按缩放中心适配)
+ clientX !== undefined && clientY !== undefined && (() => { const localX = (clientX - oldPosX) / oldScale, localY = (clientY - oldPosY) / oldScale; this.posX = clientX - localX * newScale, this.posY = clientY - localY * newScale; })();
+
+ this.updateTransform(), this.enforceBoundaries();
+ if (__hMgr_ACbar.linkMode === 1) __hMgr_ACbar.syncRunButtonPosition();
+ }
+
+ zoomToScale(targetScale, clientX, clientY) { this.zoom(targetScale - this.scale, clientX, clientY); }
+
+ // 更新菜单缩放文字显示
+ updateScaleMenuText() {
+ // 缩放选项配置:value/scale/text映射
+ const scaleOptions = [{ value: 'hUIScale_0_5x', scale: 0.5, text: '0.5x' }, { value: 'hUIScale_0_75x', scale: 0.75, text: '0.75x' }, { value: 'hUIScale_1x', scale: 1.0, text: '1x' }, { value: 'hUIScale_1_25x', scale: 1.25, text: '1.25x' }, { value: 'hUIScale_1_5x', scale: 1.5, text: '1.5x' }, { value: 'hUIScale_2x', scale: 2.0, text: '2x' }];
+ // 找到与当前缩放比例最接近的选项
+ let closestOption = scaleOptions[0], minDiff = Math.abs(this.scale - closestOption.scale);
+ for (const option of scaleOptions) { const diff = Math.abs(this.scale - option.scale); diff < minDiff && (minDiff = diff, closestOption = option); }
+ // 更新菜单按钮文字(存在则赋值)
+ const scaleMenuBtn = document.querySelector('[data-target="hCMP-hSel__scale-options"]');
+ scaleMenuBtn && (scaleMenuBtn.textContent = closestOption.text);
+ // 更新选项选中状态:移除所有选中,匹配value则添加
+ const scaleOptionsContainer = document.getElementById('hCMP-hSel__scale-options');
+ scaleOptionsContainer && scaleOptionsContainer.querySelectorAll('.hCMP-hSel__option').forEach(option => {
+ option.classList.remove('selected');
+ option.getAttribute('data-value') === closestOption.value && option.classList.add('selected');
+ });
+ }
+
+ // 设置拖拽时的z-index:优先取CSS变量,无则基于基础z-index+10000
+ setDraggingZIndex() {
+ // 获取CSS变量--hZindex__dragging作为拖拽Z索引(去除首尾空格);获取--hZindex(缺省为10001)并转为整数作为基础Z索引
+ const draggingZIndex = getComputedStyle(document.documentElement).getPropertyValue('--hZindex__dragging').trim(), baseZIndex = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--hZindex') || 10001);
+ this.hNodeAlignKit.style.zIndex = draggingZIndex ? draggingZIndex : (baseZIndex + 10000).toString(); this.hNodeAlignKit.dataset.originalZIndex = this.hNodeAlignKit.style.zIndex;
+ }
+
+ // 重置z-index:有原始值则按规则恢复(含var则清空,否则用原始值),无则直接清空
+ resetZIndex() {
+ this.hNodeAlignKit.dataset.originalZIndex
+ ? (this.hNodeAlignKit.style.zIndex = this.hNodeAlignKit.dataset.originalZIndex.includes('var') ? '' : this.hNodeAlignKit.dataset.originalZIndex, delete this.hNodeAlignKit.dataset.originalZIndex)
+ : (this.hNodeAlignKit.style.zIndex = '');
+ }
+
+ saveState() { localStorage.setItem('NodeAlignProPosition', JSON.stringify({ x: this.posX, y: this.posY, scale: this.scale })); }
+
+ loadState() {
+ const saved = localStorage.getItem('NodeAlignProPosition');
+ if (saved) try {
+ const state = JSON.parse(saved);
+ this.scale = state.scale || 1.0, this.posX = state.x || 0, this.posY = state.y || 0;
+ this.updateTransform();
+ return true;
+ } catch (e) { hLog.error('加载位置状态失败:', e); return false; }
+
+ const originalSize = this.getContainerOriginalSize(), scaledWidth = originalSize.width * this.scale, scaledHeight = originalSize.height * this.scale, windowWidth = window.innerWidth, windowHeight = window.innerHeight;
+ this.posX = Math.max(0, (windowWidth - scaledWidth) / 2), this.posY = windowHeight * 0.2;
+ this.updateTransform();
+ return false;
+ }
+
+ // 绑定事件
+ bindEvents() {
+ // 拖拽手柄选择器:合并为单行,绑定鼠标按下/进入/离开事件
+ const dragHandles = ['#hBarMove', '.hBarDivider', '#hNAP-Title__LOGOAlign', '#hCPr__header'];
+ dragHandles.forEach(selector => {
+ document.querySelectorAll(selector).forEach(el => {
+ el.addEventListener('mousedown', (e) => { this.startDrag(e.clientX, e.clientY, el); e.stopPropagation(); e.preventDefault(); });
+ el.addEventListener('mouseenter', () => { !this.isDragging && (el.style.cursor = 'move'); });
+ el.addEventListener('mouseleave', () => { !this.isDragging && (el.style.cursor = 'default'); });
+ });
+ });
+ // 全局鼠标事件:移动时调用dragMove更新拖拽位置,松开时调用stopDrag停止拖拽,拖拽时阻止selectstart防止选中文本
+ document.addEventListener('mousemove', (e) => { this.dragMove(e.clientX, e.clientY); }); document.addEventListener('mouseup', () => { this.stopDrag(); }); document.addEventListener('selectstart', (e) => { this.isDragging && e.preventDefault(); });
+ window.addEventListener('resize', () => { this.enforceBoundaries(); if (__hMgr_ACbar.linkMode === 1) setTimeout(() => __hMgr_ACbar.syncRunButtonPosition(), 100); });
+ }
+ }
+
+ // 【== 基础几何元素SVG生成工具 ==】
+ const __hSvgFc_Elements = {
+ circle: (cx, cy, r, color, attrs = {}) => ` `${k}="${v}"`).join(' ')} />`,
+ rect: (x, y, width, height, color, attrs = {}) => ` `${k}="${v}"`).join(' ')} />`,
+ line: (x1, y1, x2, y2, color, attrs = {}) => ` `${k}="${v}"`).join(' ')} />`,
+ svgContainer: (content, width = 24, height = 24, className = 'hIconC') => ``,
+ externalSvg: (path, className = 'hIconC') => `
`
+ };
+
+ // 【== 颜色相关SVG组件生成器 ==】
+ const __hSvgFc_ColorSVG = {
+ colorCircle: (color, size = 24) => __hSvgFc_Elements.svgContainer(__hSvgFc_Elements.circle(size / 2, size / 2, size * 0.45, color), size, size),
+ plusIcon: (color = 'rgba(var(--hC_BW6_BrightGray), 0.7)', size = 24) => {
+ const half = size / 2, lineLength = size * 0.5, lineStart = half - lineLength / 2, lineEnd = half + lineLength / 2;
+ const horizontal = __hSvgFc_Elements.line(lineStart, half, lineEnd, half, color, { 'stroke-width': '2', 'stroke-linecap': 'round' });
+ const vertical = __hSvgFc_Elements.line(half, lineStart, half, lineEnd, color, { 'stroke-width': '2', 'stroke-linecap': 'round' });
+ const bgCircle = __hSvgFc_Elements.circle(half, half, size * 0.45, 'rgba(var(--hC_Bg), 0.5)');
+ return __hSvgFc_Elements.svgContainer(`${bgCircle}${horizontal}${vertical}`, size, size);
+ },
+ loveIcon: (color = `rgb(var(--hC_BW1_Black))`, size = 24) => {
+ const scale = 0.8, scaledSize = size * scale;
+ const svgContent = `
+
+
+
+
+ `;
+ return ``;
+ }
+ };
+
+ // 【== 颜色配置管理 ==】
+ const __hColorCfg = {
+ defaultColors: ['rgb(var(--hC1_Red))', 'rgb(var(--hC2_Orange))', 'rgb(var(--hC3_Yellow))', 'rgb(var(--hC4_Green))', 'rgb(var(--hC5_Cyan))', 'rgb(var(--hC6_Blue))', 'rgb(var(--hC7_Purple))'],
+ grayScaleColors: ['rgb(var(--hC_BW1_Black))', 'rgb(var(--hC_BW2_DarkGray))', 'rgb(var(--hC_BW3_DeepGray))', 'rgb(var(--hC_BW4_Gray))', 'rgb(var(--hC_BW5_LightGray))', 'rgb(var(--hC_BW6_BrightGray))', 'rgb(var(--hC_BW7_White))'],
+ buttonIds: ['hColor1_Red', 'hColor2_Orange', 'hColor3_Yellow', 'hColor4_Green', 'hColor5_Cyan', 'hColor6_Blue', 'hColor7_Purple']
+ };
+
+ // 【== 简化的禁用状态管理 ==】
+ const __hMgr_DisableState = {
+ disableElement: el => el && el.classList.add('disabled-state'), enableElement: el => el && el.classList.remove('disabled-state'),
+ clearAll: () => document.querySelectorAll('.disabled-state').forEach(el => el.classList.remove('disabled-state'))
+ };
+
+ // 【== UI更新工具 ==】
+ const __hUpdater_UI = {
+ updatePickBtnColor(color) {
+ const pickBtn = document.getElementById('hColorB_Pick');
+ // 元素+颜色存在时:设置背景色,按RGB亮度动态切换文字色(亮底暗字/暗底亮字)
+ pickBtn && color && (() => {
+ pickBtn.style.backgroundColor = color;
+ const rgb = color.match(/\d+/g);
+ rgb && (pickBtn.style.color = Math.round(0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) > 128 ? 'rgb(38,38,38)' : 'rgb(204,204,204)');
+ })();
+ },
+
+ // 这个方法只用于非颜色按钮的SVG颜色更新
+ updateButtonSvgColor(button, bgColor) {
+ // 颜色按钮直接返回,非颜色按钮按背景色亮度设置SVG/图标样式
+ if (!button || button.id?.startsWith('hColor')) return;
+ const brightness = __hUpdater_UI.calculateBrightness(bgColor), isLight = brightness > 128;
+ const svgColor = getComputedStyle(document.documentElement).getPropertyValue(isLight ? '--hC_hBtn_svg_B' : '--hC_hBtn_svg_W').trim();
+ button.style.color = svgColor, button.style.backgroundColor = bgColor;
+
+ // 遍历图标元素:设置SVG颜色/填充,图片滤镜(亮底暗滤镜/暗底亮滤镜)
+ const iconElement = button.querySelector('.hIcon');
+ iconElement && (() => {
+ iconElement.querySelectorAll('svg').forEach(svg => { svg.style.color = svgColor; svg.querySelectorAll('.hBtn_svg').forEach(innerSvg => { innerSvg.style.color = svgColor, innerSvg.style.fill = svgColor; }); });
+ iconElement.querySelectorAll('img.hIconC').forEach(img => { img.style.filter = isLight ? 'invert(0) brightness(0.3)' : 'invert(1) brightness(1.5)'; });
+ })();
+ },
+
+ calculateBrightness(color) {
+ const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
+ // 未匹配RGB则返回默认亮度128,否则计算加权亮度(R*0.299+G*0.587+B*0.114)并四舍五入
+ return rgbMatch ? Math.round(0.299 * parseInt(rgbMatch[1]) + 0.587 * parseInt(rgbMatch[2]) + 0.114 * parseInt(rgbMatch[3])) : 128;
+ },
+
+ restoreDefaultSvgColor(button) {
+ if (!button) return;
+ button.style.color = '', button.style.backgroundColor = '';
+ // 清除SVG内联样式(color/fill/stroke)+ 图片滤镜,恢复默认样式
+ button.querySelectorAll('svg').forEach(svg => { svg.style.color = ''; svg.querySelectorAll('.hBtn_svg').forEach(innerSvg => { innerSvg.style.color = '', innerSvg.style.fill = '', innerSvg.style.stroke = ''; }); });
+ button.querySelectorAll('img.hIconC').forEach(img => { img.style.filter = ''; });
+ }
+ };
+
+ // 【== ComfyUI 功能集成 ==】
+ // 【== ComfyUI 节点操作工具 ==】
+ const __hMgr_ComfyUINode = {
+ getComfyUIAppInstance() { return { canvas: LGraphCanvas.active_canvas, graph: LGraphCanvas.active_canvas ? LGraphCanvas.active_canvas.graph : null }; },
+ validateAppInstance(app) { return !!app && !!app.graph; },
+ getSelectedItems(app) { return [...this.getSelectedNodes(), ...this.getSelectedGroups(app)]; },
+ markCanvasDirty(graph) { graph.setDirtyCanvas(true, true); graph.afterChange && graph.afterChange(); },
+ isGroupActuallySelected(group, canvas) { return group.selected || (canvas?.selected_group && canvas.selected_group === group); },
+ resetSingleNodeColor(node) { if (node) { node.color = undefined; node.bgcolor = undefined; node.setDirtyCanvas && node.setDirtyCanvas(true); } },
+ getSelectedNodes() { return LGraphCanvas.active_canvas ? (LGraphCanvas.active_canvas.selected_nodes ? Object.values(LGraphCanvas.active_canvas.selected_nodes) : []) : []; },
+
+ // 获取选中的组:优先取选中组,无则遍历图谱组筛选选中项(异常返回空数组)
+ getSelectedGroups(app) {
+ try {
+ if (app.canvas?.selected_group) return [app.canvas.selected_group];
+ const groups = [];
+ app.graph?.groups && app.graph.groups.forEach(group => group.selected && groups.push(group));
+ return groups;
+ } catch (error) { hLog.error("获取选中组失败:", error); return []; }
+ },
+
+ // 解析CSS变量颜色值(仅在节点上色时使用):提取变量名→获取变量值→返回实际RGB值,解析失败/非变量格式则返回原值
+ resolveCSSColor(colorString) {
+ if (!colorString) return colorString;
+ if (colorString.includes('var(')) try {
+ const match = colorString.match(/var\(([^)]+)\)/), varName = match ? match[1].trim() : '';
+ const varValue = varName ? getComputedStyle(document.documentElement).getPropertyValue(varName).trim() : '';
+ colorString = varValue ? `rgb(${varValue})` : colorString;
+ } catch (error) { hLog.error('解析CSS变量颜色失败:', error, colorString); }
+ return colorString;
+ },
+
+ // 调整颜色亮度:解析十六进制RGB→计算亮度调整值→限制范围后返回新十六进制颜色
+ adjustColorBrightness(hex, percent) {
+ hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b);
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ if (!result) return '#000000';
+ let [, rStr, gStr, bStr] = result, amount = Math.round(2.55 * percent * 100);
+ let r = Math.min(255, Math.max(0, parseInt(rStr, 16) + amount)), g = Math.min(255, Math.max(0, parseInt(gStr, 16) + amount)), b = Math.min(255, Math.max(0, parseInt(bStr, 16) + amount));
+ return '#' + (r | 1 << 8).toString(16).slice(1) + (g | 1 << 8).toString(16).slice(1) + (b | 1 << 8).toString(16).slice(1);
+ },
+
+ // RGB转Hex:匹配RGB格式→转换为十六进制,非RGB/已为Hex则返回对应值,失败返回#000000
+ rgbToHex(rgb) {
+ if (!rgb) return '#000000';
+ if (rgb.startsWith('#')) return rgb;
+ const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
+ return match ? '#' + (1 << 24 | parseInt(match[1]) << 16 | parseInt(match[2]) << 8 | parseInt(match[3])).toString(16).slice(1) : '#000000';
+ },
+
+ // 通用应用颜色到单个节点:解析颜色→设置节点文字/背景色,笔记节点单独调整文字色
+ applyColorToSingleNode(node, color) {
+ if (!node || typeof node !== 'object') return;
+ try {
+ const resolvedColor = this.resolveCSSColor(color), hexColor = this.rgbToHex(resolvedColor);
+ node.color = this.adjustColorBrightness(hexColor, -0.1); node.bgcolor = hexColor;
+ node.setDirtyCanvas && node.setDirtyCanvas(true);
+ node.type === "Note" && (node.properties.text_color = this.adjustColorBrightness(hexColor, 0.2));
+ } catch (error) { hLog.error(`应用颜色到节点失败:`, error); }
+ },
+
+ // 重置节点颜色:重置选中节点/分组下节点的颜色,标记画布变更
+ resetNodesColor() {
+ try {
+ const app = this.getComfyUIAppInstance();
+ if (!this.validateAppInstance(app)) return;
+ const selected = this.getSelectedItems(app);
+ if (selected.length === 0) return;
+ selected.forEach(item => {
+ if (item.is_system) return;
+ item.pos !== undefined
+ ? this.resetSingleNodeColor(item)
+ : item.children && item.children.forEach(nodeId => this.resetSingleNodeColor(app.graph.getNodeById(nodeId)));
+ });
+ this.markCanvasDirty(app.graph);
+ } catch (error) { hLog.error("颜色重置失败:", error); }
+ },
+
+ // 设置节点颜色:根据颜色类型为选中节点/分组上色,标记画布变更
+ setNodesColor(colorType) {
+ try {
+ const app = this.getComfyUIAppInstance();
+ if (!this.validateAppInstance(app)) return hLog.warn("ComfyUI实例获取失败");
+ // 颜色映射 - 仅在节点上色时解析CSS变量
+ const colorMap = { 'hColor1_Red': 'rgb(var(--hC1_Red))', 'hColor2_Orange': 'rgb(var(--hC2_Orange))', 'hColor3_Yellow': 'rgb(var(--hC3_Yellow))', 'hColor4_Green': 'rgb(var(--hC4_Green))', 'hColor5_Cyan': 'rgb(var(--hC5_Cyan))', 'hColor6_Blue': 'rgb(var(--hC6_Blue))', 'hColor7_Purple': 'rgb(var(--hC7_Purple))' };
+ const color = colorType.startsWith('rgb') || colorType.startsWith('#') ? colorType : colorMap[colorType];
+ if (!color) return hLog.warn('--@hSetNodeColor_tip', `未定义的颜色类型: ${colorType}`);
+ const selectedNodes = this.getSelectedNodes(), selectedGroups = this.getSelectedGroups(app);
+ if (selectedNodes.length + selectedGroups.length === 0) return hLog.warn('--@hSetNodeColor_tip', "未选择任何节点或组");
+
+ selectedNodes.forEach(node => node.is_system || this.applyColorToSingleNode(node, color));
+ selectedGroups.forEach(group => {
+ const resolvedColor = this.resolveCSSColor(color);
+ group.color = this.rgbToHex(resolvedColor);
+ group.children && group.children.forEach(nodeID => this.applyColorToSingleNode(app.graph.getNodeById(nodeID), color));
+ });
+
+ this.markCanvasDirty(app.graph);
+ hLog.info('--@hSetNodeColor', `颜色 ${color} →已应用至 ${selectedNodes.length}节点(${selectedGroups.length}组)`);
+ } catch (error) { hLog.error('--@hSetNodeColor_err', "颜色设置失败:", error); }
+ },
+
+ // 【== 直接应用颜色值到选中节点:解析颜色后为选中节点/分组上色,标记画布变更 ==】
+ __hFc_Color2Nodes(colorValue) {
+ try {
+ const app = this.getComfyUIAppInstance();
+ if (!this.validateAppInstance(app)) return hLog.warn("ComfyUI实例获取失败");
+ const selectedNodes = this.getSelectedNodes(), selectedGroups = this.getSelectedGroups(app);
+ if (selectedNodes.length + selectedGroups.length === 0) return hLog.warn('--@hColorApply_tip',"请先选择节点或组");
+ const resolvedColor = this.resolveCSSColor(colorValue);
+
+ selectedNodes.forEach(node => node.is_system || this.applyColorToSingleNode(node, resolvedColor));
+ selectedGroups.length > 0 && hLog.log('--@hColorApply_OK',`节当前颜色${resolvedColor}已同步至→`, selectedNodes.length,'节点(', selectedGroups.length,'组)');
+ selectedGroups.forEach(group => {
+ this.isGroupActuallySelected(group, app.canvas)
+ ? (group.color = this.rgbToHex(resolvedColor), group.children && group.children.forEach(nodeID => this.applyColorToSingleNode(app.graph.getNodeById(nodeID), resolvedColor)))
+ : hLog.log('--@hColorApply_Skip','跳过未明确选中的群组', group);
+ });
+
+ this.markCanvasDirty(app.graph);
+ } catch (error) { hLog.error("颜色应用失败:", error); }
+ },
+
+ // 随机颜色应用到选中节点:生成统一随机RGB色并应用,返回应用的颜色(失败返回null)
+ applyRandomColorToSelectedNodes() {
+ try {
+ const app = this.getComfyUIAppInstance();
+ if (!this.validateAppInstance(app)) return null;
+ const selectedNodes = this.getSelectedNodes(), selectedGroups = this.getSelectedGroups(app);
+ if (selectedNodes.length + selectedGroups.length === 0) { hLog.warn('--@hColorRandom_tip', "请先选择节点或组!"); return null; }
+
+ const randomColor = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)}, ${Math.floor(Math.random() * 256)})`;
+ selectedNodes.forEach(node => node.is_system || this.applyColorToSingleNode(node, randomColor));
+ selectedGroups.forEach(group => {
+ this.isGroupActuallySelected(group, app.canvas)
+ ? (group.color = this.rgbToHex(randomColor), group.children && group.children.forEach(nodeID => this.applyColorToSingleNode(app.graph.getNodeById(nodeID), randomColor)))
+ : hLog.log('--@hColorRandom_Skip', '跳过未明确选中的群组', group);
+ });
+
+ this.markCanvasDirty(app.graph);
+ hLog.info('--@hColorRandom', `随机颜色${randomColor} →已应用至 ${selectedNodes.length}节点(${selectedGroups.length}组)`);
+ return randomColor;
+ } catch (error) { hLog.error('--@hColorErr', "随机颜色应用失败:", error); return null; }
+ }
+ };
+
+ // 【== 对齐功能集成 ==】
+ // 【== 通用对齐工具函数(复用重复逻辑:获取选中节点、画布脏状态、对齐/分布/等尺寸通用流程) ==】
+ const __hNAP_AlignFc = {
+ getSelectedNodes: () => __hMgr_ComfyUINode.getSelectedNodes(),
+ setCanvasDirty: () => { LGraphCanvas.active_canvas && LGraphCanvas.active_canvas.setDirty(true, true); },
+ // 通用对齐处理:axis(0=X轴/1=Y轴)、getReference(基准值计算回调)、updateNode(节点更新回调)
+ handleAlign: (e, axis, getReference, updateNode) => {
+ const nodes = __hNAP_AlignFc.getSelectedNodes();
+ if (nodes.length === 0) return;
+ const reference = getReference(nodes, e.altKey);
+ nodes.forEach(node => updateNode(node, reference));
+ __hNAP_AlignFc.setCanvasDirty();
+ },
+ // 通用分布处理:axis(0=X轴/1=Y轴)、handleDistribute(分布逻辑回调)
+ handleDistribute: (e, axis, handleDistribute) => {
+ const nodes = __hNAP_AlignFc.getSelectedNodes();
+ if (nodes.length <= 1) return;
+ nodes.sort((a, b) => a.pos[axis] - b.pos[axis]);
+ handleDistribute(nodes, e.altKey, axis);
+ __hNAP_AlignFc.setCanvasDirty();
+ },
+ // 通用等尺寸处理:dim(0=宽度/1=高度)、getTargetSize(目标尺寸计算回调)
+ handleEqualSize: (e, dim, getTargetSize) => {
+ const nodes = __hNAP_AlignFc.getSelectedNodes();
+ if (nodes.length === 0) return;
+ const targetSize = getTargetSize(nodes, e.altKey);
+ nodes.forEach(node => node.size[dim] = targetSize);
+ __hNAP_AlignFc.setCanvasDirty();
+ }
+ };
+
+ // 【== 节点对齐工具 ==】
+ const __hNAP_AlignTools = {
+ // 左对齐(X轴)| 垂直居中(X轴居中)| 右对齐(X轴)| 顶部对齐(Y轴)| 水平居中(Y轴居中)| 底部对齐(Y轴)
+ hBtnA_alignLeft(e) { __hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => isAlt ? Math.max(...nodes.map(n => n.pos[0])) : Math.min(...nodes.map(n => n.pos[0])), (n, ref) => n.pos[0] = ref); },
+ hBtnB_alignCenterV(e) {
+ __hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => isAlt
+ ? nodes.reduce((mn, n) => n.pos[1] < mn.pos[1] ? n : mn).pos[0] + nodes.reduce((mn, n) => n.pos[1] < mn.pos[1] ? n : mn).size[0] / 2
+ : (Math.min(...nodes.map(n => n.pos[0])) + Math.max(...nodes.map(n => n.pos[0] + n.size[0]))) / 2,
+ (n, ref) => n.pos[0] = ref - n.size[0] / 2);
+ },
+ hBtnC_alignRight(e) { __hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => isAlt ? Math.min(...nodes.map(n => n.pos[0] + n.size[0])) : Math.max(...nodes.map(n => n.pos[0] + n.size[0])), (n, ref) => n.pos[0] = ref - n.size[0]); },
+ hBtnD_alignTop(e) { __hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => isAlt ? Math.max(...nodes.map(n => n.pos[1])) : Math.min(...nodes.map(n => n.pos[1])), (n, ref) => n.pos[1] = ref); },
+ hBtnE_alignCenterH(e) {
+ __hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => isAlt
+ ? nodes.reduce((mn, n) => n.pos[0] < mn.pos[0] ? n : mn).pos[1] + nodes.reduce((mn, n) => n.pos[0] < mn.pos[0] ? n : mn).size[1] / 2
+ : (Math.min(...nodes.map(n => n.pos[1])) + Math.max(...nodes.map(n => n.pos[1] + n.size[1]))) / 2,
+ (n, ref) => n.pos[1] = ref - n.size[1] / 2);
+ },
+ hBtnF_alignButton(e) { __hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => isAlt ? Math.min(...nodes.map(n => n.pos[1] + n.size[1])) : Math.max(...nodes.map(n => n.pos[1] + n.size[1])), (n, ref) => n.pos[1] = ref - n.size[1]); },
+
+ // 水平分布 | 垂直分布
+ hBtnG_distributionH(e) {
+ __hNAP_AlignFc.handleDistribute(e, 0, (nodes, isAlt, axis) => {
+ let current = nodes[0].pos[axis];
+ isAlt
+ ? (() => { for (let i = 1; i < nodes.length; i++) { current += nodes[i - 1].size[axis] + 20; nodes[i].pos[axis] = current; } })()
+ : (() => {
+ const min = Math.min(...nodes.map(n => n.pos[axis])), max = Math.max(...nodes.map(n => n.pos[axis] + n.size[axis]));
+ const total = nodes.reduce((sum, n) => sum + n.size[axis], 0), spacing = (max - min - total) / (nodes.length - 1);
+ current = min;
+ nodes.forEach(n => { n.pos[axis] = current; current += n.size[axis] + spacing; });
+ })();
+ });
+ },
+ hBtnH_distributionV(e) {
+ __hNAP_AlignFc.handleDistribute(e, 1, (nodes, isAlt, axis) => {
+ let current = nodes[0].pos[axis];
+ isAlt
+ ? (() => { for (let i = 1; i < nodes.length; i++) { current += nodes[i - 1].size[axis] + 32 + 20; nodes[i].pos[axis] = current; } })()
+ : (() => {
+ const min = Math.min(...nodes.map(n => n.pos[axis])), max = Math.max(...nodes.map(n => n.pos[axis] + n.size[axis]));
+ const total = nodes.reduce((sum, n) => sum + n.size[axis], 0), spacing = (max - min - total) / (nodes.length - 1);
+ current = min;
+ nodes.forEach(n => { n.pos[axis] = current; current += n.size[axis] + spacing; });
+ })();
+ });
+ },
+
+ // 等宽 | 等高
+ hBtnI_equalWidth(e) { __hNAP_AlignFc.handleEqualSize(e, 0, (nodes, isAlt) => isAlt ? Math.min(...nodes.map(n => n.size[0])) : Math.max(...nodes.map(n => n.size[0]))); },
+ hBtnJ_equalHeight(e) { __hNAP_AlignFc.handleEqualSize(e, 1, (nodes, isAlt) => isAlt ? Math.min(...nodes.map(n => n.size[1])) : Math.max(...nodes.map(n => n.size[1]))); }
+ };
+
+ // 【== 颜色模块控制器 ==】
+ class __hColor_Module {
+ constructor() {
+ this.colorButtons = __hColorCfg.buttonIds.map(id => document.getElementById(id));
+ this.funcButtons = { clear: document.getElementById('hClear'), pick: document.getElementById('hPick'), random: document.getElementById('hRandom'), add: document.getElementById('hColorD_Add'), love: document.getElementById('hColorE_Love') };
+ this.hiddenColorPicker = document.getElementById('hiddenColorPicker'), this.colorBar = document.getElementById('h1__hApBar1_Color'), this.nodeAlignKit = document.getElementById('hNodeAlignKit'), this.colorPickerPanel = document.getElementById('Artstich_hColorPicker');
+
+ this.customColors = Array(7).fill(null); this.lockedColors = Array(7).fill(false);
+ this.activeKeys = new Set(); this.currentMode = 'default';
+ this.modeSwitchThrottle = { lastSwitchTime: 0, throttleMs: 50, pendingMode: null, timeoutId: null };
+ this.savedState = null; this.colorPickerOutsideClickHandler = null;
+
+ // 添加模式映射表
+ this.modeNames = {
+ 'default': '默认',
+ 'shift': '灰度色',
+ 'alt': '自定义色',
+ 'ctrl': 'Ctrl',
+ 'ctrl_alt': '颜色锁定/解锁',
+ 'ctrl_shift': 'Ctrl Shift',
+ 'alt_shift': 'Alt Shift'
+ };
+
+ // 添加按键名映射表
+ this.keyNames = {
+ 'default': '默认',
+ 'shift': 'Shift',
+ 'alt': '按【Ctrl Alt】可自行锁定/解锁需要的颜色',
+ 'ctrl': '开发中...',
+ 'ctrl_alt': '【鼠标点击任一色卡】则可锁定/解锁颜色 松开【Ctrl】可退出锁定/解锁模式',
+ 'ctrl_shift': '开发中...',
+ 'alt_shift': '开发中...'
+ };
+
+ this.init();
+ }
+ // 【新增】取色器统一重置方法
+ resetColorPicker() {
+ const defaultColor = { h: 240, s: 57, b: 49 }; // 默认颜色值 (h:240, s:57, b:49 对应 rgb(55, 55, 125))
+ // 方法1: 直接设置取色器颜色并触发更新
+ if (window.colorPicker) { window.colorPicker.currentColor = defaultColor; typeof window.colorPicker.updateAllUI === 'function' && window.colorPicker.updateAllUI(); }
+ }
+
+ reset() { this.customColors = Array(7).fill(null); this.lockedColors = Array(7).fill(false); this.activeKeys.clear(); this.currentMode = 'default'; this.clearThrottleTimer(); this.resetColorPicker(); this.updateUI(); hLog.error('--->颜色模块已重置<---'); }
+ // 【保持原有】初始化
+ init() { this.updateUI(); this.bindEvents(); }
+
+ // 【保持原有】更新UI
+ updateUI() { this.renderColorButtons(); this.updateButtonStates(); __hUpdater_UI.updatePickBtnColor(this.funcButtons.pick?.style.backgroundColor); }
+
+ // 【保持原有】更新按钮禁用状态
+ updateButtonStates() {
+ __hMgr_DisableState.clearAll();
+ const disableMap = { 'shift': ['clear', 'pick', 'random'], 'alt': ['pick'], 'ctrl_alt': ['clear', 'pick', 'random'], 'ctrl_shift': ['clear', 'pick', 'random'], 'alt_shift': ['clear', 'pick', 'random'] };
+ disableMap[this.currentMode]?.forEach(button => __hMgr_DisableState.disableElement(this.funcButtons[button]));
+ }
+
+ // 【保持原有】渲染颜色按钮
+ renderColorButtons() {
+ this.colorButtons.forEach((btn, index) => {
+ if (!btn) return;
+ btn.style.backgroundColor = ''; btn.style.color = '';
+ let content = '', colorValue = '', showCustomColorBtn = false;
+ // 根据当前模式显示
+ switch (this.currentMode) {
+ case 'default': colorValue = __hColorCfg.defaultColors[index]; content = __hSvgFc_ColorSVG.colorCircle(colorValue); break;
+ case 'shift': colorValue = __hColorCfg.grayScaleColors[index]; content = __hSvgFc_ColorSVG.colorCircle(colorValue); break;
+ case 'alt':
+ case 'ctrl_alt':
+ showCustomColorBtn = true; colorValue = this.customColors[index] || '';
+ content = this.customColors[index]
+ ? (this.lockedColors[index]
+ ? `${__hSvgFc_ColorSVG.colorCircle(colorValue)}
${__hSvgFc_ColorSVG.loveIcon()}
`
+ : __hSvgFc_ColorSVG.colorCircle(colorValue))
+ : __hSvgFc_ColorSVG.plusIcon();
+ break;
+ case 'ctrl':
+ case 'ctrl_shift':
+ case 'alt_shift': colorValue = __hColorCfg.defaultColors[index]; content = __hSvgFc_ColorSVG.colorCircle(colorValue); break;
+ default: colorValue = __hColorCfg.defaultColors[index]; content = __hSvgFc_ColorSVG.colorCircle(colorValue);
+ }
+ showCustomColorBtn ? btn.classList.add('custom-color-btn') : btn.classList.remove('custom-color-btn');
+ btn.innerHTML = content; btn.dataset.colorValue = colorValue;
+ });
+ }
+
+ // 绑定事件 - 移除鼠标范围检测
+ bindEvents() {
+ // 绑定全局键盘事件
+ document.addEventListener('keydown', e => this.handleKeyDown(e), { passive: false }); document.addEventListener('keyup', e => this.handleKeyUp(e));
+
+ // 窗口失焦/聚焦处理
+ window.addEventListener('blur', () => this.handleWindowBlur()); window.addEventListener('focus', () => this.handleWindowFocus());
+
+ // 【保持原有】颜色按钮事件绑定
+ this.colorButtons.forEach((btn, index) => {
+ if (!btn) return;
+ btn.addEventListener('click', () => this.handleColorButtonClick(index));
+ btn.addEventListener('mousedown', e => this.handleColorBtnMouseDown(e, index));
+ btn.addEventListener('dblclick', e => this.handleColorBtnDblClick(e, index));
+ btn.addEventListener('dragstart', e => e.preventDefault());
+ btn.addEventListener('selectstart', e => e.preventDefault());
+ });
+
+ // 【保持原有】功能按钮事件绑定
+ Object.values(this.funcButtons).forEach(btn => btn && (btn.addEventListener('dragstart', e => e.preventDefault()), btn.addEventListener('selectstart', e => e.preventDefault())));
+
+ // 【保持原有】功能按钮点击事件
+ this.funcButtons.pick?.addEventListener('click', e => this.handlePickBtnClick(e));
+ this.funcButtons.random?.addEventListener('click', () => this.handleRandomBtnClick());
+ this.funcButtons.clear?.addEventListener('click', () => this.handleClearBtnClick());
+
+ // 【保持原有】其他事件
+ this.hiddenColorPicker?.addEventListener('input', e => this.handleColorPickerChange(e));
+ this.colorBar?.addEventListener('dragstart', e => e.preventDefault());
+ this.colorBar?.addEventListener('selectstart', e => e.preventDefault());
+ this.nodeAlignKit?.addEventListener('dragstart', e => e.preventDefault());
+ this.nodeAlignKit?.addEventListener('selectstart', e => e.preventDefault());
+ }
+
+ // 处理窗口失焦 - 重置状态
+ handleWindowBlur() { this.savedState = { currentMode: this.currentMode }; this.activeKeys.clear(); this.currentMode = 'default'; this.clearThrottleTimer(); this.updateUI(); }
+
+ // 处理窗口聚焦
+ handleWindowFocus() { this.savedState && (this.currentMode = this.savedState.currentMode, this.savedState = null, this.updateUI()); }
+
+ // 处理按键按下
+ handleKeyDown(e) { const key = e.key.toLowerCase(); if (!['shift', 'alt', 'control', 'meta'].includes(key)) return; key === 'alt' && e.preventDefault(); this.activeKeys.add(key); this.switchModeSafely(this.determineMode()); }
+
+ // 处理按键松开
+ handleKeyUp(e) { const key = e.key.toLowerCase(); if (!['shift', 'alt', 'control', 'meta'].includes(key)) return; this.activeKeys.delete(key); this.switchModeSafely(this.determineMode()); }
+
+ // 模式判定逻辑
+ determineMode() {
+ const keys = Array.from(this.activeKeys);
+ return keys.includes('alt') && keys.includes('control') ? 'ctrl_alt' :
+ keys.includes('shift') && keys.includes('control') ? 'ctrl_shift' :
+ keys.includes('alt') && keys.includes('shift') ? 'alt_shift' :
+ keys.includes('alt') ? 'alt' :
+ keys.includes('shift') ? 'shift' :
+ keys.includes('control') ? 'ctrl' : 'default';
+ }
+
+ // 安全切换模式(带节流)
+ switchModeSafely(newMode) {
+ const now = Date.now(), timeSinceLastSwitch = now - this.modeSwitchThrottle.lastSwitchTime;
+ if (timeSinceLastSwitch < this.modeSwitchThrottle.throttleMs) {
+ this.modeSwitchThrottle.pendingMode = newMode; this.clearThrottleTimer();
+ this.modeSwitchThrottle.timeoutId = setTimeout(() => {
+ if (this.modeSwitchThrottle.pendingMode === newMode) { this.applyModeSwitch(newMode); this.modeSwitchThrottle.lastSwitchTime = Date.now(); this.modeSwitchThrottle.pendingMode = null; }
+ }, this.modeSwitchThrottle.throttleMs - timeSinceLastSwitch); return;
+ }
+ this.applyModeSwitch(newMode); this.modeSwitchThrottle.lastSwitchTime = now;
+ }
+
+ applyModeSwitch(newMode) {
+ if (newMode !== this.currentMode) {
+ // 退出当前模式的日志
+ if (this.currentMode !== 'default') {
+ const prevKeyName = this.keyNames[this.currentMode] || this.currentMode;
+ const prevModeName = this.modeNames[this.currentMode];
+ }
+
+ // 进入新模式的日志
+ if (newMode === 'default') {
+ hLog.debug('--@hColorMode', '已恢复【默认】模式');
+ } else {
+ const keyName = this.keyNames[newMode] || newMode;
+ const modeName = this.modeNames[newMode];
+
+ if (modeName) {
+ hLog.debug('--@hColorMode', `已进入【${modeName}】模式(${keyName})`);
+ } else {
+ hLog.debug('--@hColorMode', `(${keyName})开发中...`);
+ }
+ }
+
+ this.currentMode = newMode;
+ this.updateUI();
+ }
+ }
+ // 清除节流定时器
+ clearThrottleTimer() { this.modeSwitchThrottle.timeoutId && (clearTimeout(this.modeSwitchThrottle.timeoutId), this.modeSwitchThrottle.timeoutId = null); }
+
+ // ============ 以下是保持原有的颜色按钮功能代码 ============
+
+ // 【保持原有】颜色按钮点击事件
+ handleColorButtonClick(index) {
+ if (['ctrl_alt', 'ctrl_shift', 'alt_shift'].includes(this.currentMode)) return;
+ let colorValue;
+ switch (this.currentMode) {
+ case 'default':
+ const colorMap = { 0: 'hColor1_Red', 1: 'hColor2_Orange', 2: 'hColor3_Yellow', 3: 'hColor4_Green', 4: 'hColor5_Cyan', 5: 'hColor6_Blue', 6: 'hColor7_Purple' };
+ __hMgr_ComfyUINode.setNodesColor(colorMap[index]); colorValue = __hColorCfg.defaultColors[index]; break;
+ case 'shift':
+ colorValue = __hColorCfg.grayScaleColors[index]; __hMgr_ComfyUINode.__hFc_Color2Nodes(colorValue); break;
+ case 'alt':
+ colorValue = this.customColors[index];
+ colorValue ? __hMgr_ComfyUINode.__hFc_Color2Nodes(colorValue) : (this.toggleColorPicker(null), this.doubleClickedIndex = index); break;
+ default: return;
+ }
+ }
+
+ // 【保持原有】颜色按钮鼠标按下事件
+ handleColorBtnMouseDown(e, index) {
+ if (this.currentMode !== 'ctrl_alt') return;
+ e.preventDefault();
+ this.customColors[index] === null ? this.customColors[index] = this.getRandomColor() : this.lockedColors[index] = !this.lockedColors[index];
+ this.renderColorButtons();
+ }
+
+ // 【保持原有】颜色按钮双击事件
+ handleColorBtnDblClick(e, index) { e.preventDefault(); (this.currentMode === 'alt' && !this.lockedColors[index]) && (this.toggleColorPicker(e), this.doubleClickedIndex = index); }
+ // 【保持原有】取色按钮点击事件
+ handlePickBtnClick(e) { !this.funcButtons.pick.classList.contains('disabled-state') && this.toggleColorPicker(e); }
+
+ handleClearBtnClick() {
+ if (this.funcButtons.clear.classList.contains('disabled-state')) return;
+ this.funcButtons.random && (this.funcButtons.random.style.backgroundColor = '', __hUpdater_UI.restoreDefaultSvgColor(this.funcButtons.random));
+ this.resetColorPicker();
+ switch (this.currentMode) {
+ case 'default': __hMgr_ComfyUINode.resetNodesColor(); hLog.warn('--@hClearBtn', '已清除节点颜色(仅对选择的节点时生效)'); break;
+ case 'alt': // Alt模式:清除未锁定的自定义颜色 + 清除节点颜色
+ for (let i = 0; i < 7; i++) !this.lockedColors[i] && (this.customColors[i] = null); this.renderColorButtons(); __hMgr_ComfyUINode.resetNodesColor(); hLog.warn('--@hClearBtn', '已清除自定义颜色(Alt)(不含锁定色)'); break;
+ default: __hMgr_ComfyUINode.resetNodesColor();
+ }
+ }
+
+ // 【保持原有】随机按钮点击事件
+ handleRandomBtnClick() {
+ if (this.funcButtons.random.classList.contains('disabled-state')) return;
+ if (this.currentMode === 'default') {
+ const randomColor = __hMgr_ComfyUINode.applyRandomColorToSelectedNodes();
+ randomColor && (this.funcButtons.random.style.backgroundColor = randomColor, __hUpdater_UI.updateButtonSvgColor(this.funcButtons.random, randomColor));
+ return;
+ }
+ if (this.currentMode === 'alt') {
+ for (let i = 0; i < 7; i++) this.customColors[i] = !this.lockedColors[i] ? this.getRandomColor() : this.customColors[i];
+ this.renderColorButtons(); const availableColors = this.customColors.filter(color => !!color);
+ const targetColor = availableColors.length > 0 ? availableColors[Math.floor(Math.random() * availableColors.length)] : this.getRandomColor();
+ this.funcButtons.random.style.backgroundColor = targetColor; __hUpdater_UI.updateButtonSvgColor(this.funcButtons.random, targetColor);
+ const app = __hMgr_ComfyUINode.getComfyUIAppInstance();
+ if (!app || !app.graph) return;
+ const selectedNodes = __hMgr_ComfyUINode.getSelectedNodes().filter(node => !node.is_system);
+ const selectedGroups = __hMgr_ComfyUINode.getSelectedGroups(app);
+ if (selectedNodes.length === 0 && selectedGroups.length === 0) return;
+ if (availableColors.length === 0) { hLog.warn('没有可用颜色'); return; }
+ const colorTargets = [...selectedNodes.map(node => ({ type: 'node', target: node })), ...selectedGroups.map(group => ({ type: 'group', target: group }))];
+ if (!colorTargets.length) return;
+ const assignedColors = this.assignColorsWithMinDuplication(colorTargets, availableColors);
+ colorTargets.forEach((target, index) => {
+ const color = assignedColors[index]; if (!color) return;
+ if (target.type === 'node') { __hMgr_ComfyUINode.applyColorToSingleNode(target.target, color); }
+ else { const group = target.target; group.color = __hMgr_ComfyUINode.rgbToHex(color); group.children && group.children.forEach(nodeID => { const node = app.graph.getNodeById(nodeID); node && __hMgr_ComfyUINode.applyColorToSingleNode(node, color); }); }
+ });
+ app.graph.setDirtyCanvas(true, true); app.graph.afterChange && app.graph.afterChange();
+ return;
+ }
+ const color = this.getRandomColor(); this.funcButtons.random.style.backgroundColor = color; __hUpdater_UI.updateButtonSvgColor(this.funcButtons.random, color);
+ }
+
+ // 【保持原有】智能分配颜色方法
+ assignColorsWithMinDuplication(targets, colors) {
+ const colorCount = colors.length, targetCount = targets.length, assignedColors = [], shuffledColors = [...colors];
+ for (let i = shuffledColors.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1));[shuffledColors[i], shuffledColors[j]] = [shuffledColors[j], shuffledColors[i]]; }
+ if (targetCount <= colorCount) return assignedColors.push(...shuffledColors.slice(0, targetCount)), assignedColors;
+ const fullCycles = Math.floor(targetCount / colorCount), remainder = targetCount % colorCount;
+ for (let i = 0; i < fullCycles; i++) {
+ const cycleShuffled = [...shuffledColors];
+ for (let j = cycleShuffled.length - 1; j > 0; j--) { const k = Math.floor(Math.random() * (j + 1));[cycleShuffled[j], cycleShuffled[k]] = [cycleShuffled[k], cycleShuffled[j]]; }
+ assignedColors.push(...cycleShuffled);
+ }
+ return assignedColors.push(...shuffledColors.slice(0, remainder)), assignedColors;
+ }
+
+ // 【保持原有】取色器相关方法
+ toggleColorPicker(e) {
+ e?.stopPropagation();
+ const colorPickerPanel = this.colorPickerPanel;
+ if (!colorPickerPanel) return;
+ const isVisible = colorPickerPanel.style.display !== 'none';
+
+ isVisible ? this.hideColorPicker() : (() => {
+ colorPickerPanel.style.display = 'block';
+ this.initColorPickerPosition();
+ this.addColorPickerOutsideClickHandler();
+ // 确保取色器canvas立即初始化
+ setTimeout(() => {
+ window.colorPicker && window.colorPicker.updateAllUI ? window.colorPicker.updateAllUI() : void 0;
+ const canvas = document.getElementById('hCPr__mainPicker_colorCanvas');
+ canvas && (canvas.width === 0 || canvas.height === 0) ? (canvas.width = 220, canvas.height = 220, canvas.style.width = '220px', canvas.style.height = '220px') : void 0;
+ }, 10);
+ window.colorPicker && (() => { const { h, s, b } = window.colorPicker.currentColor, { r, g, b: rgbB } = window.colorPicker.hsbToRgb(h, s, b); this.currentPickerColor = `rgb(${r}, ${g}, ${rgbB})`; })();
+ })();
+ }
+
+ hideColorPicker() {
+ if (this.colorPickerPanel) {
+ this.colorPickerPanel.style.display = 'none';
+ try {
+ document.removeEventListener('mousemove', window.colorAreaDragHandler);
+ document.removeEventListener('mousemove', window.hueDragHandler);
+ document.removeEventListener('mousemove', window.saturationDragHandler);
+ document.removeEventListener('mousemove', window.brightnessDragHandler);
+ document.removeEventListener('mousemove', window.hueControlDragHandler);
+ window.colorPicker?.isDragging && Object.keys(window.colorPicker.isDragging).forEach(key => window.colorPicker.isDragging[key] = false);
+ } catch (e) { hLog.warn('清理取色器事件时出错:', e); }
+ }
+ this.removeColorPickerOutsideClickHandler();
+ (this.doubleClickedIndex !== undefined && this.currentPickerColor) && (this.customColors[this.doubleClickedIndex] = this.currentPickerColor, this.renderColorButtons(), this.doubleClickedIndex = undefined, this.currentPickerColor = null);
+ }
+
+ addColorPickerOutsideClickHandler() {
+ this.removeColorPickerOutsideClickHandler();
+ this.colorPickerOutsideClickHandler = (e) => {
+ const colorPickerPanel = this.colorPickerPanel;
+ if (!colorPickerPanel || colorPickerPanel.style.display === 'none') return;
+ let target = e.target, isClickInside = false;
+ while (target && target !== document) { if (target === colorPickerPanel) { isClickInside = true; break; } target = target.parentNode; }
+ const isPickButton = e.target === this.funcButtons.pick || (this.funcButtons.pick && this.funcButtons.pick.contains(e.target));
+ if (e.type === 'contextmenu') { e.preventDefault(); e.stopPropagation(); this.hideColorPicker(); return; }
+ (!isClickInside && !isPickButton) && this.hideColorPicker();
+ };
+ document.addEventListener('mousedown', this.colorPickerOutsideClickHandler, { capture: true });
+ document.addEventListener('contextmenu', this.colorPickerOutsideClickHandler, { capture: true });
+ if (this.colorPickerPanel) this.colorPickerPanel.addEventListener('contextmenu', (e) => { e.preventDefault(); e.stopPropagation(); this.hideColorPicker(); return false; }, { capture: true });
+ }
+
+ removeColorPickerOutsideClickHandler() {
+ this.colorPickerOutsideClickHandler && (document.removeEventListener('mousedown', this.colorPickerOutsideClickHandler, { capture: true }), document.removeEventListener('contextmenu', this.colorPickerOutsideClickHandler, { capture: true }), this.colorPickerOutsideClickHandler = null);
+ }
+
+ initColorPickerPosition() { this.colorPickerPanel && this.colorBar && window.__hMgr_PopEl__Position?.positionColorPicker(); }
+ handleColorPickerChange(e) {
+ this.state?.colorPickerCallback && (() => {
+ const rgbColor = `rgb(${[1, 3, 5].map(i => parseInt(e.target.value.slice(i, i + 2), 16)).join(',')})`;
+ this.state.colorPickerCallback(rgbColor); this.state.colorPickerCallback = null;
+ __hUpdater_UI.updateButtonSvgColor(this.funcButtons.pick, rgbColor);
+ })();
+ }
+
+ openColorPicker(callback) { this.colorPickerCallback = callback; this.hiddenColorPicker.click(); }
+ getRandomColor() { const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256); return `rgb(${r}, ${g}, ${b})`; }
+ }
+
+
+ // 【== SVG 图标系统 ==】
+ const __hSVG_Build = (content, { viewBox = "0 0 32 32" }) => ``;
+ const hSVGfc = {
+ Rect: ([x, y, w, h, rx = 0, ry = 0, cls = "hBtn_svg"]) => ``,
+ Circle: ([cx, cy, r = 2, cls = "hBtn_svg"]) => ``,
+ Path: ([d, cls = "hBtn_svg"]) => ``,
+ Polygon: ([points, cls = "hBtn_svg"]) => ``,
+ Polyline: ([points, cls = "hBtn_svg"]) => ``,
+ Polyline_Stroke: ([points, cls = "hBtn_stroke"]) => ``,
+ RectTrans: ([x, y, w, h, transform, rx = 0, ry = 0, cls = "hBtn_svg"]) => ``
+ };
+ const __hSVGkit = {
+ CloneLine: (element, count, spacing, direction = 'y', startPos = 0) => Array.from({ length: count }, (_, i) => { const offset = startPos + i * spacing; return element.replace(direction === 'y' ? /y="([^"]*)"/g : /x="([^"]*)"/g, (_, val) => `${direction}="${+val + offset}"`); }).join(''),
+ CloneMatrix: (element, rows, cols, rowSpacing, colSpacing, startX = 0, startY = 0) => Array.from({ length: rows }, (_, row) => Array.from({ length: cols }, (_, col) => element.replace(/cx="([^"]*)"/g, (_, cx) => `cx="${+cx + startX + col * colSpacing}"`).replace(/cy="([^"]*)"/g, (_, cy) => `cy="${+cy + startY + row * rowSpacing}"`)).join('')).join(''),
+ CloneRotate: (element, count, cx, cy, angleStep = 90) => element + Array.from({ length: count - 1 }, (_, i) => `${element}`).join(''),
+ BorderDots_FocusFrame: (w, h, size = 1, spacing = 2) => {
+ const dots = [
+ ...Array.from({ length: Math.ceil((w - 18) / spacing) }, (_, i) => hSVGfc.Rect([10 + i * spacing, 5, size, size])),
+ ...Array.from({ length: Math.ceil((w - 18) / spacing) }, (_, i) => hSVGfc.Rect([8 + i * spacing, h - 5, size, size])),
+ ...Array.from({ length: Math.ceil((h - 16) / spacing) }, (_, i) => hSVGfc.Rect([5, 8 + i * spacing, size, size])),
+ ...Array.from({ length: Math.ceil((h - 17) / spacing) }, (_, i) => hSVGfc.Rect([w - 5, 8 + i * spacing, size, size]))
+ ].join('');
+ const corners = [[5, 5, 4, 1], [5, 5, 1, 4], [w - 8, 5, 4, 1], [w - 5, 5, 1, 4], [5, h - 5, 4, 1], [5, h - 8, 1, 4], [w - 8, h - 5, 4, 1], [w - 5, h - 8, 1, 4]];
+ return dots + corners.map(corner => hSVGfc.Rect(corner)).join('');
+ },
+ CornerBadge_I: (pos = 'top-right') => { const [x, y] = pos === 'top-right' ? [21, 5] : [5, 5]; return [hSVGfc.Rect([x + 1, y, 2, 8]), hSVGfc.Rect([x, y, 4, 1]), hSVGfc.Rect([x, y + 7, 4, 1])].join(''); }
+ };
+ const __hSVGkit_Templates = {
+ hBtnV_barMove: () => __hSVGkit.CloneMatrix(hSVGfc.Circle([6, 6]), 3, 2, 10, 10, 5, 0),
+
+ hBtnA_alignLeft: () => [[10, 9, 12, 6], [10, 19, 18, 6], [6.5, 4, 1, 24]].map(hSVGfc.Rect).join(''),
+ hBtnB_alignCenterV: () => [[9, 9, 14, 6], [6, 19, 20, 6], [15.5, 4, 1, 24]].map(hSVGfc.Rect).join(''),
+ hBtnC_alignRight: () => [[11, 9, 12, 6], [5, 19, 18, 6], [25.5, 4, 1, 24]].map(hSVGfc.Rect).join(''),
+ hBtnD_alignTop: () => [[8, 9, 6, 12], [18, 9, 6, 18], [4, 5, 24, 1]].map(hSVGfc.Rect).join(''),
+ hBtnE_alignCenterH: () => [[18, 6, 6, 20], [8, 9, 6, 14], [4, 15, 24, 1]].map(hSVGfc.Rect).join(''),
+ hBtnF_alignButton: () => [[8, 11, 6, 12], [18, 5, 6, 18], [4, 26, 24, 1]].map(hSVGfc.Rect).join(''),
+
+ hBtnG_distEvenH: () => [[19, 4, 1, 6], [12, 4, 1, 6], [19, 12, 6, 11], [7, 12, 6, 15], [4, 7, 24, 1]].map(hSVGfc.Rect).join(''),
+ hBtnH_distEvenV: () => [[11, 7, 11, 6], [3, 12, 6, 1], [3, 19, 6, 1], [11, 19, 17, 6], [6.5, 4, 1, 24]].map(hSVGfc.Rect).join(''),
+ hBtnI_equalWidth: () => [[9, 8, 14, 6], [11, 18, 10, 6], [5, 4, 1, 24], [26, 4, 1, 24], [5, 21, 4, 1], [23, 21, 4, 1]].map(hSVGfc.Rect).join(''),
+ hBtnJ_equalHeight: () => [[17, 9, 6, 14], [7, 11, 6, 10], [4, 5, 24, 1], [4, 26, 24, 1], [10, 5, 1, 4], [10, 23, 1, 4]].map(hSVGfc.Rect).join(''),
+
+ hBtnK_hSelectMode: () => hSVGfc.RectTrans([4, 13, 17, 7, "translate(-4 29) rotate(-90)"]) + hSVGfc.RectTrans([16.5, 9.5, 9, 6, "translate(8.5 33.5) rotate(-90)"]) + __hSVGkit.BorderDots_FocusFrame(32, 32),
+ hBtnL_hGroupMode: () => {
+ const base = hSVGfc.RectTrans([4, 14, 15, 7, "translate(-6 29) rotate(-90)"]) + hSVGfc.RectTrans([18, 18, 6, 8, "translate(-1 43) rotate(-90)"]);
+ const dots = [
+ ...Array.from({ length: 10 }, (_, i) => hSVGfc.Rect([5, 8 + i * 2, 1, 1])),
+ ...Array.from({ length: 10 }, (_, i) => hSVGfc.Rect([7 + i * 2, 27, 1, 1])),
+ ...Array.from({ length: 10 }, (_, i) => hSVGfc.Rect([27, 26 - i * 2, 1, 1]))
+ ].join('');
+ const special = [[5, 5, 23, 1], [5, 7, 23, 1], [5, 6, 1, 1], [7, 6, 1, 1], [9, 6, 1, 1], [12, 6, 16, 1], [20, 10, 4, 1], [22, 13, 2, 1], [23, 13, 1, 3], [19, 11, 1, 4], [20, 15, 3, 1]];
+ return base + dots + special.map(hSVGfc.Rect).join('');
+ },
+
+ hBtnR_selectTool0: () => hSVGfc.Polygon(["10 27 10 5 26 20 16 20 10 27"]),
+ hBtnR_selectTool1: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + hSVGfc.Rect([21, 5, 2, 8]) + hSVGfc.RectTrans([21, 5, 2, 8, "translate(13 31) rotate(-90)"]),
+ hBtnR_selectTool2: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + hSVGfc.RectTrans([20, 3, 2, 8, "translate(14 28) rotate(-90)"]) + hSVGfc.RectTrans([22, 10, 2, 4, "translate(11 35) rotate(-90)"]),
+ hBtnR_selectTool3: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + hSVGfc.Rect([18, 5, 2, 4]) + hSVGfc.Rect([25, 10, 2, 5]) + hSVGfc.RectTrans([19, 4, 2, 4, "translate(14 26) rotate(-90)"]) + hSVGfc.RectTrans([24, 12, 2, 4, "translate(11 39) rotate(-90)"]),
+ hBtnR_selectTool4: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + hSVGfc.Rect([21, 11, 2, 2]) + hSVGfc.Rect([21, 5, 2, 2]) + hSVGfc.RectTrans([18, 8, 2, 2, "translate(10 28) rotate(-90)"]) + hSVGfc.RectTrans([24, 8, 2, 2, "translate(16 34) rotate(-90)"]),
+ hBtnR_selectTool5: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + hSVGfc.Rect([24, 11, 2, 2]) + hSVGfc.Rect([21, 8, 2, 2]) + hSVGfc.Rect([18, 5, 2, 2]) + hSVGfc.RectTrans([18, 11, 2, 2, "translate(7 31) rotate(-90)"]) + hSVGfc.RectTrans([24, 5, 2, 2, "translate(19 31) rotate(-90)"]),
+
+ hBtnS_renameTool: () => hSVGfc.Polygon(["5 24 5 14 12 21 8 21 5 24"]) + hSVGfc.RectTrans([8, 3, 2, 8, "translate(2 16) rotate(-90)"]) + hSVGfc.RectTrans([10, 10, 2, 4, "translate(-1 23) rotate(-90)"]) + [[18.9, 5, 4.2, 21], [17, 5, 8, 1], [17, 25, 8, 1]].map(hSVGfc.Rect).join(''),
+ hBtnS_renameToolA: () => hSVGfc.Polygon(["9 29 9 8 24 22 15 22 9 29"]) + __hSVGkit.CornerBadge_I('top-right') + [[21, 5, 4, 1], [21, 12, 4, 1]].map(hSVGfc.Rect).join(''),
+ hBtnT_magicTool: () => hSVGfc.RectTrans([4.08, 17.23, 23, 2, "translate(13.7 42.13) rotate(-135)"]) + hSVGfc.Polygon(["9.5,16.87 10.18,18.7 12,19.37 10.18,20.05 9.5,21.87 8.82,20.05 7,19.37 8.82,18.7 9.5,16.87"]) + hSVGfc.Polygon(["13.63 4.2 14.51 6.58 16.89 7.46 14.51 8.34 13.63 10.71 12.75 8.34 10.37 7.46 12.75 6.58 13.63 4.2"]) + hSVGfc.Polygon(["21.6 4.2 23.33 8.87 28 10.6 23.33 12.33 21.6 17 19.87 12.33 15.2 10.6 19.87 8.87 21.6 4.2"]),
+
+ hBtnV_barMenu: () => __hSVGkit.CloneLine(hSVGfc.Rect([6, 7, 20, 2]), 3, 8, 'y', 0),
+ hBtnV_barMenuA: () => __hSVGkit.CloneLine(hSVGfc.Rect([6, 6, 20, 1]), 3, 5, 'y', 0),
+ hBtnV_modeSwitch: () => hSVGfc.Polyline_Stroke(["4.28 6.16 8 2.43 11.72 6.16"]) + hSVGfc.Polyline_Stroke(["11.72 9.84 8 13.57 4.28 9.84"]),
+ hBtnV_shiftDown: () => hSVGfc.Polyline_Stroke(["24.49 6.76 16 15.24 7.51 6.76"], "hBtn_strokeRound"),
+ hBtnV_shiftUp: () => hSVGfc.Polyline_Stroke(["7.51 15.24 16 6.76 24.49 15.24"], "hBtn_strokeRound"),
+ hBtnV_switchDown: () => hSVGfc.Polyline_Stroke(["15.25 2.88 9.43 8.7 3.61 2.88"]),
+ hBtnV_switchDown_1: () => hSVGfc.Polyline_Stroke(["15.25 2.88 9.43 8.7 3.61 2.88"]),
+ hBtnV_switchDown_2: () => hSVGfc.Polyline_Stroke(["15.25 2.88 9.43 8.7 3.61 2.88"]),
+ hBtnV_switchDown_3: () => hSVGfc.Polyline_Stroke(["15.25 2.88 9.43 8.7 3.61 2.88"]),
+ hBtnV_switchDown_4: () => hSVGfc.Polyline_Stroke(["15.25 2.88 9.43 8.7 3.61 2.88"]),
+ hBtnV_switchUp: () => hSVGfc.Polyline_Stroke(["3.61 9.12 9.43 3.3 15.25 9.12"]),
+ hBtnV_switchUp_1: () => hSVGfc.Polyline_Stroke(["3.61 9.12 9.43 3.3 15.25 9.12"]),
+ hBtnV_switchUp_2: () => hSVGfc.Polyline_Stroke(["3.61 9.12 9.43 3.3 15.25 9.12"]),
+ hBtnV_switchUp_3: () => hSVGfc.Polyline_Stroke(["3.61 9.12 9.43 3.3 15.25 9.12"]),
+ hBtnV_switchUp_4: () => hSVGfc.Polyline_Stroke(["3.61 9.12 9.43 3.3 15.25 9.12"]),
+
+ hBtnX_barDivider: () => hSVGfc.Rect([9, 0, 1, 40]),
+ hBtnY_barLOGO_ApBall: () => hSVGfc.Path(["M26,5c.55,0,1,.45,1,1v9c0,3.52-2.61,6.44-6,6.92v-.97c0-.6.39-1.11.94-1.34,1.8-.76,3.06-2.54,3.06-4.61v-7.62c0-.15-.15-.28-.3-.24-2.68.6-4.7,3-4.7,5.86v14h-2v-14c0-4.42,3.58-8,8-8h0Z"]) + hSVGfc.Path(["M7,20.32v6.68h2v-4.89c0-.15-.07-.29-.19-.38-.57-.44-1.07-.94-1.51-1.5-.1-.13-.3-.06-.3.1Z"]) + hSVGfc.Path(["M8,5h0c-.55,0-1,.45-1,1v9c0,1.9.76,3.63,2,4.89,1.05,1.08,2.44,1.81,4,2.03v-.97c0-.6-.39-1.11-.94-1.34-1.8-.76-3.06-2.54-3.06-4.61v-7.62c0-.15.15-.28.3-.24,2.68.6,4.7,3,4.7,5.86v14h2v-14c0-4.42-3.58-8-8-8Z"]),
+ hBtnY_barLOGO_Color: () => hSVGfc.Path(["M26,5c.55,0,1,.45,1,1v9c0,3.52-2.61,6.44-6,6.92v-.97c0-.6.39-1.11.94-1.34,1.8-.76,3.06-2.54,3.06-4.61v-7.62c0-.15-.15-.28-.3-.24-2.68.6-4.7,3-4.7,5.86v14h-2v-14c0-4.42,3.58-8,8-8h0Z"]) + hSVGfc.Path(["M7,20.32v6.68h2v-4.89c0-.15-.07-.29-.19-.38-.57-.44-1.07-.94-1.51-1.5-.1-.13-.3-.06-.3.1Z"]) + hSVGfc.Path(["M8,5h0c-.55,0-1,.45-1,1v9c0,1.9.76,3.63,2,4.89,1.05,1.08,2.44,1.81,4,2.03v-.97c0-.6-.39-1.11-.94-1.34-1.8-.76-3.06-2.54-3.06-4.61v-7.62c0-.15.15-.28.3-.24,2.68.6,4.7,3,4.7,5.86v14h2v-14c0-4.42-3.58-8-8-8Z"]),
+ hBtnY_barLOGO_Title: () => hSVGfc.Path(["M26,5c.55,0,1,.45,1,1v9c0,3.52-2.61,6.44-6,6.92v-.97c0-.6.39-1.11.94-1.34,1.8-.76,3.06-2.54,3.06-4.61v-7.62c0-.15-.15-.28-.3-.24-2.68.6-4.7,3-4.7,5.86v14h-2v-14c0-4.42,3.58-8,8-8h0Z"]) + hSVGfc.Path(["M7,20.32v6.68h2v-4.89c0-.15-.07-.29-.19-.38-.57-.44-1.07-.94-1.51-1.5-.1-.13-.3-.06-.3.1Z"]) + hSVGfc.Path(["M8,5h0c-.55,0-1,.45-1,1v9c0,1.9.76,3.63,2,4.89,1.05,1.08,2.44,1.81,4,2.03v-.97c0-.6-.39-1.11-.94-1.34-1.8-.76-3.06-2.54-3.06-4.61v-7.62c0-.15.15-.28.3-.24,2.68.6,4.7,3,4.7,5.86v14h2v-14c0-4.42-3.58-8-8-8Z"]),
+ hBtnY_barLOGO: () => hSVGfc.Path(["M26,5c.55,0,1,.45,1,1v9c0,3.52-2.61,6.44-6,6.92v-.97c0-.6.39-1.11.94-1.34,1.8-.76,3.06-2.54,3.06-4.61v-7.62c0-.15-.15-.28-.3-.24-2.68.6-4.7,3-4.7,5.86v14h-2v-14c0-4.42,3.58-8,8-8h0Z"]) + hSVGfc.Path(["M7,20.32v6.68h2v-4.89c0-.15-.07-.29-.19-.38-.57-.44-1.07-.94-1.51-1.5-.1-.13-.3-.06-.3.1Z"]) + hSVGfc.Path(["M8,5h0c-.55,0-1,.45-1,1v9c0,1.9.76,3.63,2,4.89,1.05,1.08,2.44,1.81,4,2.03v-.97c0-.6-.39-1.11-.94-1.34-1.8-.76-3.06-2.54-3.06-4.61v-7.62c0-.15.15-.28.3-.24,2.68.6,4.7,3,4.7,5.86v14h2v-14c0-4.42-3.58-8-8-8Z"]),
+ hBtnZ_barResize: () => hSVGfc.Path(["M6,12H0C4.69,7.31,7.31,4.69,12,0h0v6c0,3.31-2.69,6-6,6Z"]),
+
+ hColorA_Clear: () => hSVGfc.Path(["M12,3c4.97,0,9,4.03,9,9s-4.03,9-9,9S3,16.97,3,12,7.03,3,12,3M12,2C6.49,2,2,6.49,2,12s4.49,10,10,10,10-4.49,10-10S17.51,2,12,2h0Z", "hBtn_full"]) + ``,
+ hColorB_Pick: () => {
+ const bar = hSVGfc.Rect([11.38, 3.25, 1.25, 5.62]);
+ return hSVGfc.Path(["M12,2c5.52,0,10,4.48,10,10s-4.48,10-10,10S2,17.52,2,12,6.48,2,12,2M12,1C5.93,1,1,5.93,1,12s4.93,11,11,11,11-4.93,11-11S18.07,1,12,1h0Z", "hBtn_full"]) + hSVGfc.Path(["M12,6.37c3.1,0,5.62,2.52,5.62,5.62s-2.52,5.62-5.62,5.62-5.62-2.52-5.62-5.62,2.52-5.62,5.62-5.62M12,5.12c-3.8,0-6.88,3.08-6.88,6.88s3.08,6.88,6.88,6.88,6.88-3.08,6.88-6.88-3.08-6.88-6.88-6.88h0Z", "hBtn_svg"]) + hSVGfc.Rect([10.75, 10.75, 2.5, 2.5]) + __hSVGkit.CloneRotate(bar, 4, 12, 12, 90);
+ },
+ hColorC_Random: () => hSVGfc.Path(["M12,2c5.52,0,10,4.48,10,10s-4.48,10-10,10S2,17.52,2,12,6.48,2,12,2M12,1C5.93,1,1,5.93,1,12s4.93,11,11,11,11-4.93,11-11S18.07,1,12,1h0Z", "hBtn_full"]) + hSVGfc.Path(["M12,19.4c-4.08,0-7.4-3.32-7.4-7.4,0-1.05.22-2.07.66-3.04l.43-.96,3.95,3.95-1.03,1.03-2.38-2.38c-.11.46-.17.93-.17,1.4,0,3.27,2.66,5.94,5.94,5.94.84,0,1.66-.18,2.44-.53l.6,1.33c-.97.44-1.99.66-3.04.66Z"]) + hSVGfc.Path(["M18.28,15.86l-3.86-3.86.91-.91,2.48,2.48c.14-.51.21-1.04.21-1.57,0-3.32-2.7-6.02-6.02-6.02-.85,0-1.69.18-2.47.54l-.53-1.17c.96-.43,1.97-.65,3-.65,4.03,0,7.31,3.28,7.31,7.31,0,1.04-.22,2.05-.65,3.01l-.38.85Z"]),
+
+ hColorD_Add: () => hSVGfc.Rect([5, 11, 14, 2]) + hSVGfc.Rect([11, 5, 2, 14]),
+ hColorE_Love: () => hSVGfc.Path(["M12,21.35l-1.45-1.32C5.4,15.36,2,12.28,2,8.5,2,5.42,4.42,3,7.5,3c1.74,0,3.41,.81,4.5,2.09C13.09,3.81,14.76,3,16.5,3,19.58,3,22,5.42,22,8.5c0,3.78-3.4,6.86-8.55,11.54l-1.45,1.31Z"], "hBtn_svg"),
+
+ colorCircle: (colorClass) => ``
+ };
+ ['hColor1_Red', 'hColor2_Orange', 'hColor3_Yellow', 'hColor4_Green', 'hColor5_Cyan', 'hColor6_Blue', 'hColor7_Purple', 'hColorBW1_Black', 'hColorBW2_DarkGray', 'hColorBW3_DeepGray', 'hColorBW4_Gray', 'hColorBW5_LightGray', 'hColorBW6_BrightGray', 'hColorBW7_White'].forEach(color => {
+ __hSVGkit_Templates[color] = () => __hSVGkit_Templates.colorCircle(color);
+ });
+ const __hSVG_IconCfg = {
+ // 对齐类按钮(空)
+ 'hBtnV_barMove': {}, 'hBtnA_alignLeft': {}, 'hBtnB_alignCenterV': {}, 'hBtnC_alignRight': {}, 'hBtnD_alignTop': {}, 'hBtnE_alignCenterH': {}, 'hBtnF_alignButton': {},
+ // 分布/等尺寸类按钮(空)
+ 'hBtnG_distEvenH': {}, 'hBtnH_distEvenV': {}, 'hBtnI_equalWidth': {}, 'hBtnJ_equalHeight': {},
+ // 选择/分组模式(空)
+ 'hBtnK_hSelectMode': {}, 'hBtnL_hGroupMode': {},
+ // 选择工具、重命名工具(空)
+ 'hBtnR_selectTool0': {}, 'hBtnR_selectTool1': {}, 'hBtnR_selectTool2': {}, 'hBtnR_selectTool3': {}, 'hBtnR_selectTool4': {}, 'hBtnR_selectTool5': {}, 'hBtnS_renameTool': {}, 'hBtnS_renameToolA': {}, 'hBtnT_magicTool': {},
+ // 侧边栏菜单/切换类
+ 'hBtnV_barMenu': {}, 'hBtnV_barMenuA': { viewBox: "0 0 32 22" }, 'hBtnV_shiftDown': { viewBox: "0 0 32 22" }, 'hBtnV_shiftUp': { viewBox: "0 0 32 22" }, 'hBtnV_modeSwitch': { viewBox: "0 0 16 16" },
+ 'hBtnV_switchDown': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchDown_1': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchDown_2': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchDown_3': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchDown_4': { viewBox: "0 0 18.86 12" },
+ 'hBtnV_switchUp': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchUp_1': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchUp_2': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchUp_3': { viewBox: "0 0 18.86 12" }, 'hBtnV_switchUp_4': { viewBox: "0 0 18.86 12" },
+ // 侧边栏分割线/LOGO/调整尺寸
+ 'hBtnX_barDivider': { viewBox: "0 0 19 40" },
+ 'hBtnY_barLOGO_ApBall': {}, 'hBtnY_barLOGO_Color': {}, 'hBtnY_barLOGO_Title': {}, 'hBtnY_barLOGO': {},
+ 'hBtnZ_barResize': { viewBox: "0 0 12 12" },
+ // 颜色工具
+ 'hColorA_Clear': { viewBox: "0 0 24 24" }, 'hColorB_Pick': { viewBox: "0 0 24 24" }, 'hColorC_Random': { viewBox: "0 0 24 24" },
+ // 彩色系
+ 'hColor1_Red': { viewBox: "0 0 24 24" }, 'hColor2_Orange': { viewBox: "0 0 24 24" }, 'hColor3_Yellow': { viewBox: "0 0 24 24" }, 'hColor4_Green': { viewBox: "0 0 24 24" }, 'hColor5_Cyan': { viewBox: "0 0 24 24" }, 'hColor6_Blue': { viewBox: "0 0 24 24" }, 'hColor7_Purple': { viewBox: "0 0 24 24" },
+ // 黑白灰系
+ 'hColorBW1_Black': { viewBox: "0 0 24 24" }, 'hColorBW2_DarkGray': { viewBox: "0 0 24 24" }, 'hColorBW3_DeepGray': { viewBox: "0 0 24 24" }, 'hColorBW4_Gray': { viewBox: "0 0 24 24" }, 'hColorBW5_LightGray': { viewBox: "0 0 24 24" }, 'hColorBW6_BrightGray': { viewBox: "0 0 24 24" }, 'hColorBW7_White': { viewBox: "0 0 24 24" }
+ };
+ function __hInit_AllIcons() { Object.entries(__hSVG_IconCfg).forEach(([id, config]) => { const element = document.getElementById(id); element && (element.innerHTML = __hSVG_Build(__hSVGkit_Templates[id]?.() || '', config)); }); }
+
+ // 【== 颜色选择器功能 ==】
+ // 【== 初始化颜色选择器 ==】
+ function __hInit_ColorPicker() {
+ // 【== 输入框管理器类 ==】
+ class __hMgr_InputBox {
+ constructor(){this.inputs={};this.init();}
+ init(){this.setupHSBInputs();this.setupRGBInputs();this.setupHexInput();}
+
+ // 通用输入框初始化(复用HSB/RGB重复逻辑)
+ setupCommonInputs(configs, inputHandler, changeHandler) {
+ configs.forEach(config => {
+ const input = document.getElementById(config.id);
+ if (!input) return;
+ this.inputs[config.id] = { ...config, element: input };
+ // 绑定通用事件(合并短行,字符数<180)
+ input.addEventListener('input', e => inputHandler(e, config)); input.addEventListener('change', e => changeHandler(e, config));
+ input.addEventListener('keydown', e => this.handleKeydown(e, config)); input.addEventListener('blur', e => this.handleBlur(e, config));
+ });
+ }
+
+ // 处理HSB输入框
+ setupHSBInputs() {
+ const hsbConfigs = [{ id: 'hCPr__HUE_input', min: 0, max: 360, step: 1, prop: 'h' }, { id: 'hCPr__S_input', min: 0, max: 100, step: 1, prop: 's' }, { id: 'hCPr__B_input', min: 0, max: 100, step: 1, prop: 'b' }];
+ this.setupCommonInputs(hsbConfigs, this.handleInput.bind(this), this.handleChange.bind(this));
+ }
+
+ // 处理RGB输入框
+ setupRGBInputs() {
+ const rgbConfigs = [{ id: 'hCPr__Input_R', min: 0, max: 255, step: 1 }, { id: 'hCPr__Input_G', min: 0, max: 255, step: 1 }, { id: 'hCPr__Input_B', min: 0, max: 255, step: 1 }];
+ this.setupCommonInputs(rgbConfigs, this.handleRGBInput.bind(this), this.handleRGBChange.bind(this));
+ }
+
+ // 处理十六进制输入框
+ setupHexInput() {
+ const hexInput = document.getElementById('hexInput2');
+ if (!hexInput) return;
+ this.inputs['hexInput2'] = { id: 'hexInput2', min: 0, max: 0xFFFFFF, step: 1, element: hexInput };
+ // 绑定十六进制专属事件
+ hexInput.addEventListener('input', (e) => this.handleHexInput(e)); hexInput.addEventListener('change', (e) => this.handleHexChange(e));
+ hexInput.addEventListener('keydown', (e) => this.handleHexKeydown(e)); hexInput.addEventListener('blur', (e) => this.handleHexBlur(e));
+ }
+
+ // 通用输入处理
+ handleInput(e, config) {
+ const input = e.target;
+ let value = input.value.trim();
+ if (value === '') { input.value = config.min; this.updateColorFromInput(config.id, config.min); return; }
+ const numValue = parseInt(value, 10);
+ if (isNaN(numValue)) { input.value = this.getCurrentValue(config.id); return; }
+ const clampedValue = Math.max(config.min, Math.min(config.max, numValue));
+ clampedValue !== numValue && (input.value = clampedValue);
+
+ this.updateColorFromInput(config.id, clampedValue);
+ }
+
+ handleChange(e, config) { this.handleInput(e, config); }
+ handleRGBInput(e, config) { this.handleInput(e, config); }
+ handleRGBChange(e, config) { this.handleInput(e, config); }
+
+ // 十六进制输入处理
+ handleHexInput(e) {
+ const input = e.target;
+ let value = input.value.trim().toUpperCase().replace(/^#/, '').replace(/[^0-9A-F]/g, '');
+ value.length > 6 && (value = value.substring(0, 6));
+ input.value = value;
+ value.length === 6 && this.updateColorFromHex(value);
+ }
+
+ handleHexChange(e) {
+ const input = e.target;
+ let value = input.value.trim().toUpperCase().replace(/^#/, '');
+ // 空值/补零/长度限制
+ value === '' && (value = '000000');
+ while (value.length < 6) value = '0' + value;
+ value.length > 6 && (value = value.substring(0, 6));
+ input.value = value;
+ this.updateColorFromHex(value);
+ }
+
+ // 键盘事件处理(方向键步进)
+ handleKeydown(e, config) {
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
+ e.preventDefault();
+ const currentValue = parseInt(e.target.value, 10) || config.min, step = config.step || 1, newValue = e.key === 'ArrowUp' ? Math.min(config.max, currentValue + step) : Math.max(config.min, currentValue - step);
+ e.target.value = newValue;
+ this.updateColorFromInput(config.id, newValue);
+ }
+ }
+
+ // 十六进制键盘事件(提取重复补零/范围逻辑)
+ handleHexKeydown(e) {
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
+ e.preventDefault();
+ let hexValue = e.target.value.trim().toUpperCase().replace(/^#/, ''); !hexValue || !/^[0-9A-F]{1,6}$/.test(hexValue) && (hexValue = '000000'); while (hexValue.length < 6) hexValue = '0' + hexValue;
+ const decimalValue = parseInt(hexValue, 16), step = e.key === 'ArrowUp' ? 1 : -1;
+ let newDecimalValue = Math.max(0, Math.min(0xFFFFFF, decimalValue + step)), newHexValue = newDecimalValue.toString(16).toUpperCase();
+ while (newHexValue.length < 6) newHexValue = '0' + newHexValue;
+ e.target.value = newHexValue; this.updateColorFromHex(newHexValue);
+ }
+ }
+
+ // 失去焦点事件(复用逻辑)
+ handleBlur(e, config) { this.handleChange(e, config); }
+ handleHexBlur(e) { this.handleHexChange(e); }
+
+ // 获取当前值(精简重复的hsbToRgb调用)
+ getCurrentValue(inputId) {
+ const config = this.inputs[inputId];
+ if (!config) return 0;
+
+ if (inputId === 'hexInput2') return currentColor ? hsbToHex(currentColor.h, currentColor.s, currentColor.b).substring(1) : '000000';
+ if (['hCPr__HUE_input', 'hCPr__S_input', 'hCPr__B_input'].includes(inputId)) return currentColor?.[inputId.split('__')[1].split('_')[0].toLowerCase()] || 0;
+ const rgb = currentColor ? hsbToRgb(currentColor.h, currentColor.s, currentColor.b) : { r: 0, g: 0, b: 0 };
+ return inputId === 'hCPr__Input_R' ? rgb.r : inputId === 'hCPr__Input_G' ? rgb.g : inputId === 'hCPr__Input_B' ? rgb.b : config.min;
+ }
+
+ // 从输入更新颜色(HSB/RGB)
+ updateColorFromInput(inputId, value) {
+ if (!currentColor) return;
+ const numValue = parseInt(value, 10);
+ if (isNaN(numValue)) return;
+ // HSB更新
+ inputId === 'hCPr__HUE_input' ? currentColor.h = Math.max(0, Math.min(360, numValue)) : inputId === 'hCPr__S_input' ? currentColor.s = Math.max(0, Math.min(100, numValue)) : inputId === 'hCPr__B_input' && (currentColor.b = Math.max(0, Math.min(100, numValue)));
+ // RGB更新
+ if (['hCPr__Input_R', 'hCPr__Input_G', 'hCPr__Input_B'].includes(inputId)) {
+ const r = parseInt(document.getElementById('hCPr__Input_R').value) || 0, g = parseInt(document.getElementById('hCPr__Input_G').value) || 0, b = parseInt(document.getElementById('hCPr__Input_B').value) || 0;
+ const hsb = rgbToHsb(r, g, b); currentColor.h = hsb.h; currentColor.s = hsb.s; currentColor.b = hsb.b;
+ }
+ updateAllUI();
+ }
+
+ // 从十六进制更新颜色
+ updateColorFromHex(hexValue) {
+ if (!hexValue || hexValue.length !== 6) return;
+ const r = parseInt(hexValue.substring(0, 2), 16), g = parseInt(hexValue.substring(2, 4), 16), b = parseInt(hexValue.substring(4, 6), 16), hsb = rgbToHsb(r, g, b);
+ currentColor.h = hsb.h; currentColor.s = hsb.s; currentColor.b = hsb.b; updateAllUI();
+ }
+
+ // 更新所有输入框的值
+ updateAllInputs() {
+ if (!currentColor) return;
+ // 更新HSB
+ this.inputs.hCPr__HUE_input && (this.inputs.hCPr__HUE_input.element.value = Math.round(currentColor.h));
+ this.inputs.hCPr__S_input && (this.inputs.hCPr__S_input.element.value = Math.round(currentColor.s)); this.inputs.hCPr__B_input && (this.inputs.hCPr__B_input.element.value = Math.round(currentColor.b));
+ // 更新RGB(仅调用一次hsbToRgb)
+ const rgb = hsbToRgb(currentColor.h, currentColor.s, currentColor.b);
+ this.inputs.hCPr__Input_R && (this.inputs.hCPr__Input_R.element.value = rgb.r); this.inputs.hCPr__Input_G && (this.inputs.hCPr__Input_G.element.value = rgb.g);
+ this.inputs.hCPr__Input_B && (this.inputs.hCPr__Input_B.element.value = rgb.b);
+ this.inputs.hexInput2 && (this.inputs.hexInput2.element.value = hsbToHex(currentColor.h, currentColor.s, currentColor.b).substring(1).toUpperCase());
+ }
+ }
+ let inputManager;
+ // 【== 输入框管理器类-结束 ==】
+
+ const ids = [
+ 'colorArea', 'hCPr__mainPicker_colorCanvas', 'colorCursor', 'hueSlider', 'hueCursor', 'hueHandle', 'hCPr__S_handle', 'hCPr__B_handle', 'hPreview__Node', 'hCPr__nodeMode',
+ 'hCPr__HUE_input', 'hCPr__S_input', 'hCPr__B_input', 'hexInput2', 'hCPr__Input_R', 'hCPr__Input_G', 'hCPr__Input_B', 'hCPr__HUE_sliderControl', 'hCPr__S_slider', 'hCPr__B_slider',
+ 'hCPr__S_clipContainer', 'hCPr__B_clipContainer', 'hueSliderTouchArea', 'hueTouchArea', 'hCPr__S_fill', 'hCPr__B_fill', 'hCPr__HUE_fill'
+ ];
+ // 初始化取色器核心变量(含SVG元素/hTips引用)、输入管理器及节流控制
+ const els = {}; ids.forEach(id => els[id] = document.getElementById(id));
+ els.nodeTitle = document.querySelector('#hCPr__nodeSvg .hPreview__Node-Title'), els.nodeMain = document.querySelector('#hCPr__nodeSvg .hPreview__Node-Main'), els.hCPr_hTips = document.querySelector('.hCPr__hTips');
+
+ let currentColor = { h: 240, s: 57, b: 49 }, colorApplyMode = 1, isDragging = { hue: false, saturation: false, brightness: false, colorArea: false };
+ const ctx = els.hCPr__mainPicker_colorCanvas.getContext('2d'); inputManager = new __hMgr_InputBox();
+ let applyColorThrottleTimer = null, lastAppliedColor = null;
+ // 应用颜色到选中节点
+ function __hFc_Color2Nodes() {
+ const { r, g, b } = hsbToRgb(currentColor.h, currentColor.s, currentColor.b), rgbColor = `rgb(${r}, ${g}, ${b})`;
+ if (lastAppliedColor === rgbColor) return;
+ lastAppliedColor = rgbColor;
+
+ const selectedNodes = __hMgr_ComfyUINode.getSelectedNodes(), app = __hMgr_ComfyUINode.getComfyUIAppInstance(), selectedGroups = __hMgr_ComfyUINode.getSelectedGroups(app);
+ (selectedNodes.length + selectedGroups.length > 0) && __hMgr_ComfyUINode.__hFc_Color2Nodes(rgbColor);
+ }
+
+ const throttleApplyColor = () => { clearTimeout(applyColorThrottleTimer); applyColorThrottleTimer = setTimeout(() => { __hFc_Color2Nodes(); applyColorThrottleTimer = null; }, 100); };
+
+ function updateAllUI() {
+ const { r, g, b } = hsbToRgb(currentColor.h, currentColor.s, currentColor.b);
+ updateColorArea(); updateHueSlider(); updateSliders(); updateNodePreview(); updateInputs(); updateColorCursorPosition(); __hUpdater_UI.updatePickBtnColor(`rgb(${r},${g},${b})`); throttleApplyColor();
+ }
+
+ // 【== 初始化canvas尺寸 ==】
+ function __hInit_CanvasSize() {
+ // 提取重复逻辑:设置canvas尺寸并更新颜色区域
+ const setCanvasDimensions = (canvas, w, h, usePercent = false) => { canvas.width = w; canvas.height = h; canvas.style.width = usePercent ? '100%' : `${w}px`; canvas.style.height = usePercent ? '100%' : `${h}px`; updateColorArea(); };
+ const canvas = els.hCPr__mainPicker_colorCanvas, colorArea = els.colorArea, defaultWidth = 220, defaultHeight = 220;
+ // 元素存在时执行初始化+延迟适配容器尺寸
+ canvas && colorArea && (canvas.style.display = 'block', setCanvasDimensions(canvas, defaultWidth, defaultHeight), setTimeout(() => {
+ const container = els.hCPr__mainPicker_colorAreaG || colorArea;
+ container && (() => {
+ const { width: cw, height: ch } = container.getBoundingClientRect();
+ cw > 0 && ch > 0 && (dw = Math.floor(cw), dh = Math.floor(ch), (dw !== defaultWidth || dh !== defaultHeight) && setCanvasDimensions(canvas, dw, dh, true));
+ })();
+ }, 50));
+ }
+
+ // 取色器显示时强制重新初始化Canvas尺寸
+ const ensureCanvasSize = () => {
+ if (!els.hCPr__mainPicker_colorCanvas || !els.colorArea) return;
+ __hInit_CanvasSize();
setTimeout(() => {
- hTooltip.style.display = 'block';
- this.hasShownhTooltip = true;
- }, 1000);
- }
- });
- this.hMainToolbar.addEventListener('mouseleave', () => {
- hTooltip.remove();
- });
- }
- let isPermanent = localStorage.getItem('NodeAlignerIsPermanent');
- if (isPermanent) {
- this.isPermanent = localStorage.getItem('NodeAlignerIsPermanent') == '1';
- this.isPermanent ? this.show() : this.hide();
- } else {
- this.show()
- }
- const linkRunBtn = this.contextMenu.querySelector('#link-run-btn');
- linkRunBtn.addEventListener('click', (e) => {
- e.stopPropagation();
- this.linkMode = (this.linkMode + 1) % 5;
- this.isRunButtonAligned = this.linkMode !== 0;
- linkRunBtn.style.backgroundColor = this.linkColors[this.linkMode];
- if (this.isRunButtonAligned) {
- this.syncRunButtonPosition();
- this.hMainToolbar.addEventListener('mousemove', this.syncRunButtonPosition.bind(this));
- } else {
- this.hMainToolbar.removeEventListener('mousemove', this.syncRunButtonPosition.bind(this));
- }
- });
- },
- createColorCircle(colorName) {
- return ColorSVGs.circle(this.ColorConfig.colors[colorName]);
- },
- createColorPickerIcon() {
- return `
-
- `;
- },
- setNodesColor(event, colorType) {
- try {
- const app = this.getComfyUIAppInstance();
- if (!app) {
- console.warn("ComfyUI实例获取失败");
- return;
- }
- const color = this.ColorConfig.colors[colorType];
- if (!color) {
- console.warn(`未定义的颜色类型: ${colorType}`);
- return;
- }
- const selectedNodes = this.getSelectedNodes(app);
- const selectedGroups = this.getSelectedGroups(app);
- if (selectedNodes.length + selectedGroups.length === 0) {
- console.warn("未选择任何节点或组");
- return;
- }
- selectedNodes.forEach(node => {
- if (node.is_system) return;
- this.applyColorToNode(node, color);
- });
- selectedGroups.forEach(group => {
- group.color = color;
- if (group.children) {
- group.children.forEach(nodeID => {
- const node = app.graph.getNodeById(nodeID);
- if (node) this.applyColorToNode(node, color);
- });
- }
- });
- app.graph.setDirtyCanvas(true, true);
- if (app.graph.afterChange) app.graph.afterChange();
- } catch (error) {
- console.error("颜色设置失败:", error);
- }
- },
- resetNodesColor() {
- try {
- const app = this.getComfyUIAppInstance();
- if (!app) return;
- const selected = [...this.getSelectedNodes(app), ...this.getSelectedGroups(app)];
- if (selected.length === 0) return;
- selected.forEach(item => {
- if (item.is_system) return;
- if (item.pos !== undefined) {
- item.color = undefined;
- item.bgcolor = undefined;
- if (item.setDirtyCanvas) item.setDirtyCanvas(true);
- } else if (item.children) {
- item.color = undefined;
- item.children.forEach(nodeId => {
- const node = app.graph.getNodeById(nodeId);
- if (node) {
- node.color = undefined;
- node.bgcolor = undefined;
- if (node.setDirtyCanvas) node.setDirtyCanvas(true);
- }
- });
- }
- });
- app.graph.setDirtyCanvas(true, true);
- if (app.graph.afterChange) app.graph.afterChange();
- } catch (error) {
- console.error("颜色重置失败:", error);
- }
- },
- openNativeColorPicker() {
- try {
- const app = this.getComfyUIAppInstance();
- if (!app) {
- console.warn("ComfyUI实例获取失败");
- return;
- }
- const selectedNodes = this.getSelectedNodes(app);
- const selectedGroups = this.getSelectedGroups(app);
- if (selectedNodes.length + selectedGroups.length === 0) {
- console.warn("请先选择节点或组");
- return;
- }
- const input = document.createElement('input');
- input.type = 'color';
- input.style.cssText = `
- position: fixed;
- opacity: 0;
- pointer-events: none;
- top: -100px;
- `;
- const lastColor = selectedNodes[0]?.bgcolor || selectedGroups[0]?.color || '#3355aa';
- input.value = this.rgbToHex(lastColor);
- input.addEventListener('change', (e) => {
- const color = e.target.value;
- selectedNodes.forEach(node => {
- if (node.is_system) return;
- this.applyColorToNode(node, color);
- });
- selectedGroups.forEach(group => {
- group.color = color;
- if (group.children) {
- group.children.forEach(nodeID => {
- const node = app.graph.getNodeById(nodeID);
- if (node) this.applyColorToNode(node, color);
- });
- }
- });
- app.graph.setDirtyCanvas(true, true);
- if (app.graph.afterChange) app.graph.afterChange();
- document.body.removeChild(input);
- });
- document.body.appendChild(input);
- input.click();
- } catch (error) {
- console.error("颜色选择器打开失败:", error);
- }
- },
- rgbToHex(rgb) {
- if (!rgb) return '#000000';
- if (rgb.startsWith('#')) return rgb;
- const match = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
- if (match) {
- return '#' +
- (1 << 24 |
- parseInt(match[1]) << 16 |
- parseInt(match[2]) << 8 |
- parseInt(match[3])
- ).toString(16).slice(1);
- }
- return '#000000';
- },
- getComfyUIAppInstance() {
- return {
- canvas: LGraphCanvas.active_canvas,
- graph: LGraphCanvas.active_canvas.graph
- };
- },
- getButtons() {
- return [{
- id: 'hBtn_moveElement',
- svg: hSvg_moveElement
- },
- {
- type: 'hC_dividerLine'
- },
- {
- id: 'hBtnA_alignLeft',
- svg: hSvgA_alignLeft,
- action: this.hBtnA_alignLeft.bind(this)
- },
- {
- id: 'hBtnB_alignCenterV',
- svg: hSvgB_alignCenterV,
- action: this.hBtnB_alignCenterV.bind(this)
- },
- {
- id: 'hBtnC_alignRight',
- svg: hSvgC_alignRight,
- action: this.hBtnC_alignRight.bind(this)
- },
- {
- type: 'hC_dividerLine'
- },
- {
- id: 'hBtnD_alignTop',
- svg: hSvgD_alignTop,
- action: this.hBtnD_alignTop.bind(this)
- },
- {
- id: 'hBtnE_alignCenterH',
- svg: hSvgE_alignCenterH,
- action: this.hBtnE_alignCenterH.bind(this)
- },
- {
- id: 'hBtnF_alignButton',
- svg: hSvgF_alignButton,
- action: this.hBtnF_alignButton.bind(this)
- },
- {
- type: 'hC_dividerLine'
- },
- {
- id: 'hBtnG_distributionH',
- svg: hSvgG_distributionH,
- action: this.hBtnG_distributionH.bind(this)
- },
- {
- id: 'hBtnH_distributionV',
- svg: hSvgH_distributionV,
- action: this.hBtnH_distributionV.bind(this)
- },
- {
- type: 'hC_dividerLine'
- },
- {
- id: 'hBtnI_equalWidth',
- svg: hSvgI_equalWidth,
- action: this.hBtnI_equalWidth.bind(this)
- },
- {
- id: 'hBtnJ_equalHeight',
- svg: hSvgJ_equalHeight,
- action: this.hBtnJ_equalHeight.bind(this)
- },
- {
- type: 'hC_dividerLine'
- },
- {
- id: 'colorRed',
- svg: this.createColorCircle('red'),
- action: (e) => this.setNodesColor(e, 'red')
- },
- {
- id: 'colorOrange',
- svg: this.createColorCircle('orange'),
- action: (e) => this.setNodesColor(e, 'orange')
- },
- {
- id: 'colorYellow',
- svg: this.createColorCircle('yellow'),
- action: (e) => this.setNodesColor(e, 'yellow')
- },
- {
- id: 'colorGreen',
- svg: this.createColorCircle('green'),
- action: (e) => this.setNodesColor(e, 'green')
- },
- {
- id: 'colorCyan',
- svg: this.createColorCircle('cyan'),
- action: (e) => this.setNodesColor(e, 'cyan')
- },
- {
- id: 'colorBlue',
- svg: this.createColorCircle('blue'),
- action: (e) => this.setNodesColor(e, 'blue')
- },
- {
- id: 'colorPurple',
- svg: this.createColorCircle('purple'),
- action: (e) => this.setNodesColor(e, 'purple')
- },
- {
- id: 'colorClear',
- svg: this.createColorCircle('clear'),
- action: this.resetNodesColor.bind(this)
- },
- {
- id: 'colorPick',
- svg: this.createColorPickerIcon(),
- action: () => this.openNativeColorPicker()
- },
- {
- id: 'hBtn_moveElement',
- svg: hSvg_moveElement
- },
- ];
- },
- showContextMenu() {
- this.contextMenu = document.createElement('div');
- this.contextMenu.id = 'hContext-Menu';
- this.contextMenu.style.display = 'none';
- this.contextMenu.innerHTML = `
-
-
-
-
-
-
- `;
- this.hMainToolbar.appendChild(this.contextMenu);
- this.hMainToolbar.addEventListener('contextmenu', (event) => {
- event.preventDefault();
- const isMenuVisible = this.contextMenu.style.display === 'block';
- const isClickInsideMenu = this.contextMenu.contains(event.target);
- if (isMenuVisible) {
- if (!isClickInsideMenu) {
- this.contextMenu.style.display = 'none';
- }
- } else {
- this.contextMenu.style.display = 'block';
- this.contextMenu.style.left = `${event.pageX}px`;
- this.contextMenu.style.top = `${event.pageY}px`;
- }
- });
- document.addEventListener('mousedown', (e) => {
- if (this.contextMenu.style.display === 'block' &&
- !this.contextMenu.contains(e.target) &&
- !this.hMainToolbar.contains(e.target)) {
- this.contextMenu.style.display = 'none';
- }
- });
- this.contextMenu.addEventListener('contextmenu', (e) => {
- e.preventDefault();
- this.contextMenu.style.display = 'none';
- });
- this.contextMenu.querySelector('#pin-mode').addEventListener('click', () => {
- this.contextMenu.style.display = 'none';
- this.isPermanent = true;
- localStorage.setItem('NodeAlignerIsPermanent', '1');
- this.restorePosition();
- });
- this.contextMenu.querySelector('#toggle-on-select').addEventListener('click', () => {
- this.hMainToolbar.style.display = 'none';
- this.contextMenu.style.display = 'none';
- this.isPermanent = false;
- localStorage.setItem('NodeAlignerIsPermanent', '0');
- });
- this.contextMenu.querySelector('#ui-scale-btn').addEventListener('click', (e) => {
- this.systemWideZoom = e.ctrlKey;
- this.toggleUIScale(e);
- e.stopPropagation();
- });
- },
- show() {
- this.isShow = true;
- this.hMainToolbar.style.display = 'flex';
- },
- hide() {
- this.isShow = false;
- this.hMainToolbar.style.display = 'none';
- },
- setPosition(x, y) {
- this.hMainToolbar.style.left = `${x}px`;
- this.hMainToolbar.style.top = `${y}px`;
- },
- toggleUIScale(e) {
- const isCtrlPressed = e ? e.ctrlKey : this.systemWideZoom;
- this.currentZoomIndex = (this.currentZoomIndex + 1) % this.zoomLevels.length;
- const scale = this.zoomLevels[this.currentZoomIndex];
- if (isCtrlPressed) {
- document.body.style.zoom = scale;
- this.hMainToolbar.style.transform = '';
- } else {
- this.hMainToolbar.style.transform = `scale(${scale})`;
- document.body.style.zoom = '';
- }
- // 直接更新按钮文本
- const scaleBtn = this.contextMenu.querySelector('#ui-scale-btn');
- if (scaleBtn) {
- scaleBtn.textContent = `UI缩放${scale}x`;
- }
- },
- onDragStart(e) {
- if (this.isDragging) return;
- const ishBtn_moveElement = e.target.closest('#hBtn_moveElement') !== null;
- const ishC_dividerLine = e.target.classList.contains('hC_dividerLine');
- if (!(ishBtn_moveElement || ishC_dividerLine)) return;
- this.isDragging = true;
- this.initialX = e.clientX;
- this.initialY = e.clientY;
- this.dragStartX = this.hMainToolbar.offsetLeft;
- this.dragStartY = this.hMainToolbar.offsetTop;
- document.addEventListener('mousemove', this.boundOnDragging);
- document.addEventListener('mouseup', this.boundOnDragEnd);
- this.isDragging = true;
- },
- onDragging(e) {
- if (this.isDragging) {
- const originalWidth = this.hMainToolbar.offsetWidth;
- const deltaX = e.clientX - this.initialX;
- const deltaY = e.clientY - this.initialY;
- let newLeft = this.dragStartX + deltaX;
- let newTop = this.dragStartY + deltaY;
- const containerRect = this.hMainToolbar.getBoundingClientRect();
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- if (newLeft < 0) {
- newLeft = 0;
- } else if (newLeft + containerRect.width > windowWidth) {
- newLeft = windowWidth - containerRect.width;
- }
- if (newTop < 0) {
- newTop = 0;
- } else if (newTop + containerRect.height > windowHeight) {
- newTop = windowHeight - containerRect.height;
- }
- this.hMainToolbar.style.width = `${originalWidth}px`;
- this.hMainToolbar.style.left = `${newLeft}px`;
- this.hMainToolbar.style.top = `${newTop}px`;
- this.hMainToolbar.style.left = `${newLeft}px`;
- this.hMainToolbar.style.top = `${newTop}px`;
- }
- },
- validateAndAdjustPosition(pos) {
- const containerRect = this.hMainToolbar.getBoundingClientRect();
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- const safeLeft = Math.max(0, Math.min(pos.left, windowWidth - containerRect.width));
- const safeTop = Math.max(0, Math.min(pos.top, windowHeight - containerRect.height));
- if (Math.abs(pos.left - safeLeft) > 20 || Math.abs(pos.top - safeTop) > 20) {
- return this.getCentralPosition();
- }
- return {
- left: safeLeft,
- top: safeTop
- };
- },
- getCentralPosition() {
- return {
- left: Math.max(0, window.innerWidth / 2 - this.hMainToolbar.offsetWidth / 2),
- top: 20
- };
- },
- onDragEnd() {
- this.isDragging = false;
- document.removeEventListener('mousemove', this.boundOnDragging);
- document.removeEventListener('mouseup', this.boundOnDragEnd);
- const rect = this.hMainToolbar.getBoundingClientRect();
- const originalPos = {
- left: rect.left,
- top: rect.top
- };
- const safePos = this.validateAndAdjustPosition(originalPos);
- this.hMainToolbar.style.left = `${safePos.left}px`;
- this.hMainToolbar.style.top = `${safePos.top}px`;
- localStorage.setItem('NodeAlignerhMainToolbarPosition', JSON.stringify({
- left: safePos.left,
- top: safePos.top
- }));
- this.hMainToolbar.style.width = 'fit-content';
- this.syncRunButtonPosition();
- },
- restorePosition() {
- try {
- const savedPosition = JSON.parse(localStorage.getItem('NodeAlignerhMainToolbarPosition'));
- if (!savedPosition) {
- this.setCentralPosition();
- return;
- }
- let pos = {
- left: savedPosition.left || window.innerWidth / 2 - this.hMainToolbar.offsetWidth / 2,
- top: savedPosition.top || 20
+ const canvas = els.hCPr__mainPicker_colorCanvas;
+ canvas && (canvas.width === 0 || canvas.height === 0) && (canvas.width = 220, canvas.height = 220, canvas.style.width = '220px', canvas.style.height = '220px', updateColorArea());
+ }, 100);
};
- const validatedPos = this.validateAndAdjustPosition(pos);
- this.hMainToolbar.style.left = `${validatedPos.left}px`;
- this.hMainToolbar.style.top = `${validatedPos.top}px`;
- localStorage.setItem('NodeAlignerhMainToolbarPosition', JSON.stringify(validatedPos));
- if (this.isRunButtonAligned) {
- this.syncRunButtonPosition();
+ // 延迟初始化Canvas+监听显示/resize适配/双击事件绑定
+ setTimeout(() => {
+ __hInit_CanvasSize();
+ const p = document.getElementById('Artstich_hColorPicker');
+ p && new MutationObserver(m => m.forEach(m => m.attributeName === 'style' && (d = p.style.display, (d === 'block' || d === '')) && setTimeout(ensureCanvasSize, 100))).observe(p, { attributes: !0 });
+ setTimeout(() => {
+ const previewNode = document.getElementById('hPreview__Node');
+ previewNode && (previewNode.addEventListener('dblclick', () => toggleColorApplyMode()), (svgElement = document.getElementById('hCPr__nodeSvg')) && svgElement.addEventListener('dblclick', e => (e.stopPropagation(), toggleColorApplyMode())));
+ }, 200);
+ }, 100);
+ window.addEventListener('resize', () => { els.colorPickerPanel?.style.display !== 'none' && setTimeout(__hInit_CanvasSize, 100); });
+ // 【== 初始化canvas尺寸:以确保正确渲染-结束 ==】
+
+ // 更新取色器颜色区域:尺寸适配+批量绘制优化性能
+ function updateColorArea() {
+ const c = els.hCPr__mainPicker_colorCanvas;
+ if (!c) return;
+
+ // 确保canvas有效尺寸:优先client尺寸→容器尺寸→固定220
+ let w = Math.floor(c.clientWidth), h = Math.floor(c.clientHeight);
+ (w === 0 || h === 0) && (() => {
+ const container = els.hCPr__mainPicker_colorAreaG || els.colorArea, rect = container?.getBoundingClientRect() || { width: 0, height: 0 };
+ w = rect.width > 0 ? Math.floor(rect.width) : 220; h = rect.height > 0 ? Math.floor(rect.height) : 220;
+ c.width = w; c.height = h;
+ })();
+
+ (c.width !== w || c.height !== h) && (c.width = w, c.height = h);
+
+ // 宽高有效时绘制颜色区域(合并短变量+精简循环内代码)
+ const { width: w1, height: h1 } = c;
+ w1 > 0 && h1 > 0 && (() => {
+ ctx.clearRect(0, 0, w1, h1);
+ const imgData = ctx.createImageData(w1, h1), d = imgData.data, t = w1 * h1;
+ for (let i = 0; i < t; i++) {
+ const x = i % w1, y = Math.floor(i / w1), idx = i * 4;
+ const { r, g, b: rb } = hsbToRgb(currentColor.h, x / w1 * 100, (1 - y / h1) * 100);
+ d[idx] = r; d[idx + 1] = g; d[idx + 2] = rb; d[idx + 3] = 255;
+ }
+ ctx.putImageData(imgData, 0, 0);
+ })();
}
- const linkRunBtn = this.contextMenu?.querySelector('#link-run-btn');
- if (linkRunBtn) {
- linkRunBtn.style.backgroundColor = this.linkColors[this.linkMode];
+
+ const updateHueSlider = () => (els.hueCursor.style.top = `${currentColor.h / 3.6}%`, els.hueHandle.style.left = `${currentColor.h / 360 * 100}%`);
+
+ function updateSliders() {
+ els.hCPr__S_handle.style.left = `${currentColor.s}%`; els.hCPr__S_clipContainer.style.width = `${currentColor.s}%`;
+ els.hCPr__S_fill.style.background = `linear-gradient(to right, ${hsbToHex(currentColor.h, 0, currentColor.b)}, ${hsbToHex(currentColor.h, 100, currentColor.b)})`;
+ els.hCPr__B_handle.style.left = `${currentColor.b}%`; els.hCPr__B_clipContainer.style.width = `${currentColor.b}%`;
+ els.hCPr__B_fill.style.background = `linear-gradient(to right, ${hsbToHex(currentColor.h, currentColor.s, 0)}, ${hsbToHex(currentColor.h, currentColor.s, 100)})`;
+ updateHueSliderAppearance();
}
- } catch (e) {
- console.warn('位置信息异常,重置到默认位置:', e);
- this.setCentralPosition();
- }
- },
- setCentralPosition() {
- const pos = this.getCentralPosition();
- this.hMainToolbar.style.left = `${pos.left}px`;
- this.hMainToolbar.style.top = `${pos.top}px`;
- localStorage.setItem('NodeAlignerhMainToolbarPosition', JSON.stringify(pos));
- },
- getSelectedNodes() {
- if (!LGraphCanvas.active_canvas) return [];
- const selectedNodesObj = LGraphCanvas.active_canvas.selected_nodes;
- return selectedNodesObj ? Object.values(selectedNodesObj) : [];
- },
- adjustColorBrightness(hex, percent) {
- hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => '#' + r + r + g + g + b + b);
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
- if (!result) return '#000000';
- let r = parseInt(result[1], 16);
- let g = parseInt(result[2], 16);
- let b = parseInt(result[3], 16);
- const amount = Math.round(2.55 * percent * 100);
- r = Math.min(255, Math.max(0, r + amount));
- g = Math.min(255, Math.max(0, g + amount));
- b = Math.min(255, Math.max(0, b + amount));
- return '#' +
- (r | 1 << 8).toString(16).slice(1) +
- (g | 1 << 8).toString(16).slice(1) +
- (b | 1 << 8).toString(16).slice(1);
- },
- applyColorToNode(node, color) {
- if (!node || typeof node !== 'object') return;
- try {
- node.color = this.adjustColorBrightness(color, -0.1);
- node.bgcolor = color;
- if (node.setDirtyCanvas) {
- node.setDirtyCanvas(true);
+
+ function updateHueSliderAppearance() {
+ const hueColors = [];
+ for (let i = 0; i <= 12; i++) hueColors.push(hsbToHex(i / 12 * 360, currentColor.s, currentColor.b));
+ els.hCPr__HUE_fill.style.background = `linear-gradient(to right, ${hueColors.join(', ')})`;
}
- if (node.type === "Note") {
- node.properties.text_color = this.adjustColorBrightness(color, 0.2);
+
+ // 更新节点预览:补全缺失元素→设置SVG填充色→更新按钮色+模式提示
+ function updateNodePreview() {
+ (!els.nodeTitle || !els.nodeMain) && (els.nodeTitle = document.querySelector('#hCPr__nodeSvg .hPreview__Node-Title'), els.nodeMain = document.querySelector('#hCPr__nodeSvg .hPreview__Node-Main'));
+ const color = hsbToHex(currentColor.h, currentColor.s, currentColor.b), rgb = hsbToRgb(currentColor.h, currentColor.s, currentColor.b);
+ els.nodeTitle && (els.nodeTitle.style.fill = color), els.nodeMain && (els.nodeMain.style.fill = colorApplyMode === 0 ? '#353535' : color);
+ __hUpdater_UI.updatePickBtnColor(`rgb(${rgb.r},${rgb.g},${rgb.b})`);
+ els.hCPr__nodeMode && (els.hCPr__nodeMode.textContent = colorApplyMode === 0 ? '仅标题' : '整体色');
}
- } catch (error) {
- console.error(`应用颜色到节点失败:`, error);
- }
- },
- getSelectedGroups(app) {
- try {
- if (app.canvas?.selected_group) {
- return [app.canvas.selected_group];
+
+ // 更新颜色输入框:优先用输入框管理器,无则回退手动更新HSB/RGB/HEX
+ function updateInputs() {
+ // 优先使用输入框管理器更新
+ inputManager && inputManager.updateAllInputs() || (() => {
+ // 回退逻辑:手动更新HSB/RGB/HEX输入框
+ els.hCPr__HUE_input && (els.hCPr__HUE_input.value = Math.round(currentColor.h)); els.hCPr__S_input && (els.hCPr__S_input.value = Math.round(currentColor.s)); els.hCPr__B_input && (els.hCPr__B_input.value = Math.round(currentColor.b));
+ const { r, g, b } = hsbToRgb(currentColor.h, currentColor.s, currentColor.b);
+ els.hCPr__Input_R && (els.hCPr__Input_R.value = r); els.hCPr__Input_G && (els.hCPr__Input_G.value = g); els.hCPr__Input_B && (els.hCPr__Input_B.value = b);
+ els.hexInput2 && (els.hexInput2.value = hsbToHex(currentColor.h, currentColor.s, currentColor.b).substring(1).toUpperCase());
+ })();
}
- const groups = [];
- if (app.graph?.groups) {
- for (const group of app.graph.groups) {
- if (group.selected) {
- groups.push(group);
+
+ // 更新colorCursor位置,确保基于canvas本身的像素坐标系
+ function updateColorCursorPosition() {
+ const c = els.hCPr__mainPicker_colorCanvas;
+ if (!els.colorArea || !els.colorCursor || !ctx || !c) return;
+ const { width: w, height: h } = c, x = currentColor.s / 100 * w, y = (1 - currentColor.b / 100) * h;
+ els.colorCursor.style.left = `${Math.max(0, Math.min(x, w))}px`, els.colorCursor.style.top = `${Math.max(0, Math.min(y, h))}px`;
+ }
+
+ function addEventListeners() {
+ const dragHandlers = [colorAreaDragHandler, hueDragHandler, saturationDragHandler, brightnessDragHandler, hueControlDragHandler];
+ els.colorArea.addEventListener('mousedown', startDrag('colorArea', colorAreaDragHandler)), els.hueSliderTouchArea.addEventListener('mousedown', startDrag('hue', hueDragHandler));
+ addSliderEventListeners(), addInputEventListeners(), addCopyEventListeners();
+
+ // 为hPreview__Node及其子元素绑定双击事件
+ const previewNode = els.hPreview__Node;
+ previewNode && (
+ previewNode.addEventListener('dblclick', e => (e.preventDefault(), e.stopPropagation(), toggleColorApplyMode())),
+ previewNode.querySelectorAll('*').forEach(el => el.addEventListener('dblclick', e => (e.preventDefault(), e.stopPropagation(), toggleColorApplyMode(), false)))
+ );
+
+ document.addEventListener('mouseup', () => { onGlobalMouseUp(); dragHandlers.forEach(h => document.removeEventListener('mousemove', h)); });
+ }
+
+ function startDrag(type, handler) {
+ return e => {
+ e.preventDefault(), isDragging[type] = !0;
+ const moveHandler = moveEvent => isDragging[type] && handler(moveEvent);
+ document.addEventListener('mousemove', moveHandler);
+ const cleanup = () => {
+ isDragging[type] = !1;
+ document.removeEventListener('mousemove', moveHandler);
+ document.removeEventListener('mouseup', cleanup);
+ [colorAreaDragHandler, hueDragHandler, saturationDragHandler, brightnessDragHandler, hueControlDragHandler].forEach(h => document.removeEventListener('mousemove', h));
+ };
+ document.addEventListener('mouseup', cleanup), handler(e);
+ };
+ }
+
+ function onGlobalMouseUp() {
+ Object.keys(isDragging).forEach(key => isDragging[key] = !1);
+ // 清理全局mousemove事件,忽略移除错误
+ [colorAreaDragHandler, hueDragHandler, saturationDragHandler, brightnessDragHandler, hueControlDragHandler].forEach(h => { try { document.removeEventListener('mousemove', h); } catch (e) { } });
+ }
+
+ // colorArea拖拽处理 - 确保光标位置和颜色值准确联动
+ function colorAreaDragHandler(e) {
+ if (!isDragging.colorArea) return;
+ const c = els.hCPr__mainPicker_colorCanvas;
+ if (!c) return;
+ const r = c.getBoundingClientRect(), relX = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width)), relY = Math.max(0, Math.min(1, (e.clientY - r.top) / r.height));
+ currentColor.s = relX * 100, currentColor.b = (1 - relY) * 100;
+ updateAllUI();
+ }
+
+ // 色相滑块拖拽处理
+ function hueDragHandler(e) {
+ if (isDragging.hue) { const rect = els.hueSlider.getBoundingClientRect(); let y = Math.max(0, Math.min(rect.height, e.clientY - rect.top)); currentColor.h = (y / rect.height) * 360; els.hueCursor.style.top = `${y}px`; updateAllUI(); }
+ }
+
+ function saturationDragHandler(e) { if (!isDragging.saturation) return; e.preventDefault(); updateSliderFromMouseMove(e, els.hCPr__S_slider, els.hCPr__S_handle, 'saturation'); }
+ function brightnessDragHandler(e) { if (!isDragging.brightness) return; e.preventDefault(); updateSliderFromMouseMove(e, els.hCPr__B_slider, els.hCPr__B_handle, 'brightness'); }
+ function hueControlDragHandler(e) { if (isDragging.hue) updateHueFromControlSlider(e); }
+
+ Object.assign(window, {colorAreaDragHandler,hueDragHandler,saturationDragHandler,brightnessDragHandler,hueControlDragHandler});
+
+ // 确保滑块位置计算准确
+ function updateSliderFromMouseMove(e, slider, handle, type) {
+ const rect = slider.getBoundingClientRect();
+ if (!rect || rect.width <= 0) return hLog.warn('滑块rect无效:', type, rect);
+ const x = Math.max(0, Math.min(100, (e.clientX - rect.left) / rect.width * 100));
+ handle.style.left = `${x}%`;
+ const clipContainerMap={saturation:els.hCPr__S_clipContainer,brightness:els.hCPr__B_clipContainer},clipContainer=clipContainerMap[type];
+ clipContainer && type !== 'hue' && (clipContainer.style.width = `${x}%`); type === 'hue' ? (currentColor.h = (x / 100) * 360, updateColorArea(), updateHueSlider())
+ : type === 'saturation' ? currentColor.s = x : type === 'brightness' && (currentColor.b = x);
+ updateAllUI();
+ }
+
+ function updateSliderFromClick(e, slider, handle, type) {
+ const rect = slider.getBoundingClientRect();
+ let x = Math.max(0, Math.min(100, (e.clientX - rect.left) / rect.width * 100));
+ handle.style.left = `${x}%`;
+ const clipContainer = document.getElementById(`${type}ClipContainer`);
+ clipContainer && type !== 'hue' && (clipContainer.style.width = `${x}%`);
+ // 根据类型更新颜色属性,色相需额外更新色区和色相滑块UI
+ type === 'hue' ? (currentColor.h = (x / 100) * 360, updateColorArea(), updateHueSlider()) : type === 'saturation' ? currentColor.s = x : type === 'brightness' && (currentColor.b = x);
+ updateAllUI();
+ }
+
+ function updateHueFromControlSlider(e) {
+ const rect = els.hCPr__HUE_sliderControl.getBoundingClientRect();
+ let x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
+ currentColor.h = (x / rect.width) * 360, updateAllUI();
+ }
+
+ function addSliderEventListeners() {
+ els.hueHandle.addEventListener('mousedown', e => { e.stopPropagation(); startDrag('hue', hueControlDragHandler)(e); });
+ els.hueTouchArea.addEventListener('mousedown', e => { updateHueFromControlSlider(e); startDrag('hue', hueControlDragHandler)(e); });
+ els.hCPr__HUE_sliderControl.addEventListener('click', e => updateHueFromControlSlider(e));
+ addSliderEventListener(els.hCPr__S_slider, els.hCPr__S_handle, 'saturation');
+ addSliderEventListener(els.hCPr__B_slider, els.hCPr__B_handle, 'brightness');
+ }
+
+ // 确保事件绑定正确
+ function addSliderEventListener(slider, handle, type) {
+ if (!slider || !handle) return;
+ const getDragHandler = e => startDrag(type, window[`${type}DragHandler`])(e);
+ handle.addEventListener('mousedown', e => { e.stopPropagation(); e.preventDefault(); getDragHandler(e); });
+ const ta = document.getElementById(`${type}TouchArea`);
+ ta && ta.addEventListener('mousedown', e => { e.preventDefault(); updateSliderFromClick(e, slider, handle, type); getDragHandler(e); });
+ slider.addEventListener('mousedown', e => { e.preventDefault(); updateSliderFromClick(e, slider, handle, type); getDragHandler(e); });
+ }
+
+ function addCopyEventListeners() {
+ document.querySelectorAll('.copy-btn').forEach(button => button.addEventListener('click', () => {
+ const textToCopy = button.classList.contains('rgb-copy-btn') ? `${els.hCPr__Input_R.value}, ${els.hCPr__Input_G.value}, ${els.hCPr__Input_B.value}` : document.getElementById(button.getAttribute('data-target')).value;
+ navigator.clipboard.writeText(textToCopy).then(()=>{const origHtml=button.innerHTML;button.innerHTML='✓',setTimeout(()=>button.innerHTML=origHtml,1500);hLog.info('已复制到剪贴板:', textToCopy);}).catch(err=>hLog.error('复制失败:',err));
+ }));
+ }
+ // 切换颜色应用模式:安全检查→切换模式→更新预览/提示/视觉反馈
+ function toggleColorApplyMode() {
+ if (!els.hCPr__nodeMode) return;
+ colorApplyMode = colorApplyMode === 0 ? 1 : 0; updateNodePreview();
+ els.hCPr__nodeMode.textContent = colorApplyMode === 0 ? '仅标题' : '整体色';
+ els.hCPr__nodeMode.style.backgroundColor = colorApplyMode === 0 ? 'rgb(var(--hC_BW3_DeepGray))' : 'rgb(var(--hC_CPr0__PurpleStd))';
+ }
+ function hsbToRgb(h, s, b) {
+ s /= 100, b /= 100;
+ let r, g, bv, i = Math.floor(h / 60), f = h / 60 - i;
+ let p = b * (1 - s), q = b * (1 - f * s), t = b * (1 - (1 - f) * s);
+ switch (i % 6) {
+ case 0: r = b; g = t; bv = p; break; case 1: r = q; g = b; bv = p; break; case 2: r = p; g = b; bv = t; break;
+ case 3: r = p; g = q; bv = b; break; case 4: r = t; g = p; bv = b; break; case 5: r = b; g = p; bv = q; break;
}
- }
+ return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(bv * 255) };
}
- return groups;
- } catch (error) {
- console.error("获取选中组失败:", error);
- return [];
- }
- },
- groupNodesByCoordinate(nodes, axis, tolerance = 1000) {
- if (nodes.length < 4) {
- return [nodes];
- }
- const groups = [];
- const otherAxis = 1 - axis;
- nodes.sort((a, b) => a.pos[axis] - b.pos[axis]);
- const gaps = [];
- for (let i = 1; i < nodes.length; i++) {
- gaps.push(nodes[i].pos[axis] - (nodes[i - 1].pos[axis] + nodes[i - 1].size[axis]));
- }
- const avgGap = gaps.reduce((sum, gap) => sum + gap, 0) / gaps.length;
- const stdDevGap = Math.sqrt(gaps.reduce((sum, gap) => sum + Math.pow(gap - avgGap, 2), 0) / gaps.length);
- const threshold = avgGap + stdDevGap;
- let currentGroup = [nodes[0]];
- for (let i = 1; i < nodes.length; i++) {
- const gap = nodes[i].pos[axis] - (nodes[i - 1].pos[axis] + nodes[i - 1].size[axis]);
- const otherAxisDiff = Math.abs(nodes[i].pos[otherAxis] - nodes[i - 1].pos[otherAxis]);
- if (gap <= threshold && otherAxisDiff <= tolerance) {
- currentGroup.push(nodes[i]);
- } else {
- groups.push(currentGroup);
- currentGroup = [nodes[i]];
+
+ function rgbToHsb(r, g, b) {
+ r /= 255, g /= 255, b /= 255;
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min;
+ let h = 0, s = max === 0 ? 0 : delta / max, v = max;
+ max === min ? h = 0 : max === r ? h = (g - b) / delta + (g < b ? 6 : 0) : max === g ? h = (b - r) / delta + 2 : max === b && (h = (r - g) / delta + 4);
+ h = Math.round(h * 60), s = Math.round(s * 100), v = Math.round(v * 100);
+ return { h, s, b: v };
}
- }
- groups.push(currentGroup);
- return groups;
- },
- hBtnA_alignLeft() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length === 0) {
- const canvas = LGraphCanvas.active_canvas;
- if (canvas) {
- canvas.ds.offset = [80, 140];
- canvas.ds.scale = 1;
- canvas.setDirty(true, true);
+
+ function hsbToHex(h,s,b){const rgb=hsbToRgb(h,s,b);return `#${rgb.r.toString(16).padStart(2,'0')}${rgb.g.toString(16).padStart(2,'0')}${rgb.b.toString(16).padStart(2,'0')}`.toUpperCase();}
+ window.colorPicker = { currentColor, hsbToRgb, updateAllUI }; updateAllUI(), addEventListeners();
+
+ // 窗口失焦清理拖拽状态和事件
+ window.addEventListener('blur', () => {
+ const isDragging = window.colorPicker?.isDragging;
+ if (isDragging) Object.keys(isDragging).forEach(key => isDragging[key] = !1);
+ [colorAreaDragHandler, hueDragHandler, saturationDragHandler, brightnessDragHandler, hueControlDragHandler].forEach(h => { if (h) try { document.removeEventListener('mousemove', h); } catch (e) { } });
+ });
+ }
+
+ // 【== 主界面初始化 ==】
+ function __hInit_MainInterface() {
+ window.__hMgr_PopEl__Position && window.__hMgr_PopEl__Position.positionMenu();
+ // 获取核心DOM元素,缺失则终止
+ const menuContainer = document.getElementById('h6__hMenu'), menuButton = document.getElementById('hNAP-Title__MenuA'), container = document.getElementById('hNodeAlignKit');
+ if (!menuContainer || !menuButton || !container) return;
+
+ document.addEventListener('click', e => !menuContainer.contains(e.target) && e.target !== menuButton && (menuContainer.style.display = 'none'));
+ document.getElementById('hBack')?.addEventListener('click', __hMenu_Toggle);
+ container.addEventListener('contextmenu', e => (e.preventDefault(), __hMenu_Toggle())), menuButton.addEventListener('click', __hMenu_Toggle);
+ __hBind_hAlignBtn(), __hBind_hMenuBtn();
+ }
+
+ // 【== 切换菜单显示 ==】
+ function __hMenu_Toggle(e) {
+ e && e.stopPropagation();
+ const menuContainer = document.getElementById('h6__hMenu');
+ if (!menuContainer) return;
+ const isVisible = menuContainer.style.display !== 'none';
+ menuContainer.style.display = isVisible ? 'none' : 'block';
+ !isVisible && window.__hMgr_PopEl__Position && window.__hMgr_PopEl__Position.positionMenu();
+ }
+
+ // 【== 绑定对齐按钮事件 ==】
+ function __hBind_hAlignBtn() {
+ document.getElementById('hAlignLeft').addEventListener('click', (e) => __hNAP_AlignTools.hBtnA_alignLeft(e));
+ document.getElementById('hAlignCenterV').addEventListener('click', (e) => __hNAP_AlignTools.hBtnB_alignCenterV(e));
+ document.getElementById('hAlignRight').addEventListener('click', (e) => __hNAP_AlignTools.hBtnC_alignRight(e));
+ document.getElementById('hAlignTop').addEventListener('click', (e) => __hNAP_AlignTools.hBtnD_alignTop(e));
+ document.getElementById('hAlignCenterH').addEventListener('click', (e) => __hNAP_AlignTools.hBtnE_alignCenterH(e));
+ document.getElementById('hAlignBottom').addEventListener('click', (e) => __hNAP_AlignTools.hBtnF_alignButton(e));
+ document.getElementById('hDistEvenH').addEventListener('click', (e) => __hNAP_AlignTools.hBtnG_distributionH(e));
+ document.getElementById('hDistEvenV').addEventListener('click', (e) => __hNAP_AlignTools.hBtnH_distributionV(e));
+ document.getElementById('hEqualWidth').addEventListener('click', (e) => __hNAP_AlignTools.hBtnI_equalWidth(e));
+ document.getElementById('hEqualHeight').addEventListener('click', (e) => __hNAP_AlignTools.hBtnJ_equalHeight(e));
+ }
+
+ // 【== 绑定右键菜单按钮事件:重置插件/反馈Bug/查看指南 ==】
+ function __hBind_hMenuBtn() {
+ document.getElementById('hReset').addEventListener('click', __hReset__hNAP_State);
+ const openLinkAndHideMenu=(url)=>{window.open(url,'_blank');window.__hMgr_MenuHide&&window.__hMgr_MenuHide.hideMenu();};
+ // 绑定Bug反馈/指南按钮事件
+ document.getElementById('hBugReport').addEventListener('click', () => openLinkAndHideMenu('https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro/issues'));
+ document.getElementById('hGuide').addEventListener('click', () => openLinkAndHideMenu('https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro'));
+ const backBtn = document.getElementById('hBack');
+ if (backBtn) backBtn.addEventListener('click', () => { window.__hMgr_MenuHide && window.__hMgr_MenuHide.hideMenu(); });
+ }
+
+ // 【== 重置插件状态 - 增强版 ==】
+ function __hReset__hNAP_State() {
+ // 1. 重置总容器位置+缩放(1x)
+ window.containerController && (window.containerController.reset(), window.containerController.zoomToScale(1.0, window.innerWidth / 2, window.innerHeight / 2));
+
+ // 2. 显示总容器
+ const container = document.getElementById('hNodeAlignKit'); container && (container.style.display = 'block');
+
+ // 3. 重置颜色模块(这会自动重置取色器、自定义颜色等所有颜色相关状态)
+ window.__hColor_Module && window.__hColor_Module.reset();
+
+ // 4. 重置联动模式为解耦
+ __hMgr_ACbar.setLinkMode(0);
+
+ // 5. 重置显示模式为常驻
+ window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.reset();
+
+ // 6. 重置所有下拉菜单选项
+ __hrReset__hMenu_Selections();
+
+ // 7. 确保容器变换完成(立即执行,无需延迟)
+ window.containerController && window.containerController.updateTransform();
+
+ // 8. 隐藏菜单
+ window.__hMgr_MenuHide ? window.__hMgr_MenuHide.hideMenu() : (() => { const menuContainer = document.getElementById('h6__hMenu'); menuContainer && (menuContainer.style.display = 'none'); })();
+ hLog.log('NodeAlignPro 已完全重置为默认状态');
+ }
+
+ // 【== 重置下拉菜单选择 ==】
+ function __hrReset__hMenu_Selections() {
+ // 复用函数1:处理双选项下拉菜单重置(拖拽/显示模式)
+ const resetTwoOptionDropdown = (btn, opt1, opt2, text, selectOpt) => {
+ if (!btn || !opt1 || !opt2) return;
+ btn.textContent = text;
+ selectOpt === 1 ? (opt1.classList.add('selected'), opt2.classList.remove('selected')) : (opt1.classList.remove('selected'), opt2.classList.add('selected'));
+ };
+
+ // 复用函数2:处理多选项下拉菜单重置(缩放/工作模式)
+ const resetMultiOptionDropdown = (btn, optionsObj, targetOptKey, text) => {
+ if (!btn || !optionsObj[targetOptKey]) return;
+ btn.textContent = text;
+ Object.values(optionsObj).forEach(opt => opt && opt.classList.remove('selected'));
+ optionsObj[targetOptKey].classList.add('selected');
+ };
+
+ // 1. 重置拖拽方式为解耦
+ const dragBtn = document.querySelector('[data-target="hCMP-hSel__drag-options"]'), dragOption1 = document.querySelector('[data-value="hDragMode0_Link"]'), dragOption2 = document.querySelector('[data-value="hDragMode1_Split"]');
+ resetTwoOptionDropdown(dragBtn, dragOption1, dragOption2, '解 耦', 2);
+
+ // 2. 重置UI缩放为1x
+ const scaleBtn = document.querySelector('[data-target="hCMP-hSel__scale-options"]'),
+ scaleOptions = {
+ 'hUIScale_0_5x': document.querySelector('[data-value="hUIScale_0_5x"]'), 'hUIScale_0_75x': document.querySelector('[data-value="hUIScale_0_75x"]'), 'hUIScale_1x': document.querySelector('[data-value="hUIScale_1x"]'),
+ 'hUIScale_1_25x': document.querySelector('[data-value="hUIScale_1_25x"]'), 'hUIScale_1_5x': document.querySelector('[data-value="hUIScale_1_5x"]'), 'hUIScale_2x': document.querySelector('[data-value="hUIScale_2x"]')
+ };
+ resetMultiOptionDropdown(scaleBtn, scaleOptions, 'hUIScale_1x', '1x');
+
+ // 3. 重置工作模式为对齐
+ const modeBtn = document.querySelector('[data-target="hCMP-hSel__mode-options"]'),
+ modeOptions = {
+ 'hApBar0_apBall': document.querySelector('[data-value="hApBar0_apBall"]'), 'hApBar1_Color': document.querySelector('[data-value="hApBar1_Color"]'), 'hApBar2_Align': document.querySelector('[data-value="hApBar2_Align"]'),
+ 'hApBar3_StdH': document.querySelector('[data-value="hApBar3_StdH"]'), 'hApBar4_ProH': document.querySelector('[data-value="hApBar4_ProH"]')
+ };
+ resetMultiOptionDropdown(modeBtn, modeOptions, 'hApBar2_Align', '-对 齐-');
+
+ // 4. 重置显示模式为常驻显示(通过__hMgr_DisplayMode)
+ window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.updateMenuButtonText();
+ }
+
+ // 【== 下拉菜单功能 ==】
+ function __hInit_hMenu__Dropdown() {
+ let hActiveDropdown = null, hHoverTimeout = null, hIsInDropdownArea = false;
+ const hCustomSelects = document.querySelectorAll('.hCMP-hSel .hMenu-btn'), hDropdownContainers = document.querySelectorAll('.hCMP-hSel');
+ // 关闭所有下拉菜单:移除激活态并添加淡出动画
+ const closeAllHDropdowns = () => { document.querySelectorAll('.hCMP-hSel__options').forEach(option => (option.classList.remove('active'), option.classList.add('fade-out'))); hActiveDropdown = null; };
+ // 打开指定下拉菜单:先关闭所有,50ms后激活目标菜单
+ const openHDropdown = (targetId) => { closeAllHDropdowns(); setTimeout(() => { const target = document.getElementById(targetId); target && (target.classList.remove('fade-out'), target.classList.add('active'), hActiveDropdown = targetId); }, 50); };
+ // 绑定下拉菜单按钮点击事件:切换菜单显隐(当前激活则关闭,否则打开)
+ hCustomSelects.forEach(select => select.addEventListener('click', e => { e.stopPropagation(); const targetId = select.getAttribute('data-target'); hActiveDropdown === targetId ? closeAllHDropdowns() : openHDropdown(targetId); }));
+ // 绑定下拉选项点击事件:更新按钮文本、标记选中项、关闭菜单并处理选择逻辑
+ document.querySelectorAll('.hCMP-hSel__option').forEach(option => option.addEventListener('click', function () {
+ const optionText = this.textContent, optionsContainer = this.parentElement, selectBtn = optionsContainer.previousElementSibling;
+ selectBtn.textContent = optionText;
+ optionsContainer.querySelectorAll('.hCMP-hSel__option').forEach(opt => opt.classList.remove('selected')), this.classList.add('selected');
+ closeAllHDropdowns();
+ __hMenu_Selection(this.getAttribute('data-value'));
+ window.__hMgr_MenuHide && window.__hMgr_MenuHide.hideMenu();
+ }));
+
+ // 绑定下拉容器鼠标进出事件:hover时保持菜单打开,离开200ms后关闭
+ hDropdownContainers.forEach(container => {
+ container.addEventListener('mouseenter', () => { hIsInDropdownArea = true; hHoverTimeout && (clearTimeout(hHoverTimeout), hHoverTimeout = null); hActiveDropdown && openHDropdown(container.querySelector('.hMenu-btn').getAttribute('data-target')); });
+ container.addEventListener('mouseleave', () => { hIsInDropdownArea = false; hHoverTimeout = setTimeout(() => hActiveDropdown && !hIsInDropdownArea && closeAllHDropdowns(), 200); });
+ });
+ const menuContainer = document.getElementById('h6__hMenu');
+ menuContainer.addEventListener('mouseenter', () => { hIsInDropdownArea = true; hHoverTimeout && (clearTimeout(hHoverTimeout), hHoverTimeout = null); });
+ menuContainer.addEventListener('mouseleave', () => { hIsInDropdownArea = false; hHoverTimeout = setTimeout(() => hActiveDropdown && !hIsInDropdownArea && closeAllHDropdowns(), 300); });
+ document.addEventListener('click', e => !e.target.closest('.hCMP-hSel') && !e.target.closest('.hCMP-hSel__options') && closeAllHDropdowns());
+ document.querySelectorAll('.hCMP-hSel__options').forEach(option => option.addEventListener('click', e => e.stopPropagation()));
+ document.addEventListener('keydown', e => e.key === 'Escape' && hActiveDropdown && (closeAllHDropdowns(), e.stopPropagation()));
+ menuContainer.addEventListener('contextmenu', function (e) { closeAllHDropdowns(); });
+ }
+
+ const scaleMapping = { 'hUIScale_0_5x': 0.5, 'hUIScale_0_75x': 0.75, 'hUIScale_1x': 1.0, 'hUIScale_1_25x': 1.25, 'hUIScale_1_5x': 1.5, 'hUIScale_2x': 2.0 };
+
+ // 【== 显示模式管理器 ==】
+ class __hMgr_DisplayMode {
+ constructor() { this.isPermanent = true; this.canvasClickListener = null; this.loadModeFromStorage(); }
+
+ // 重置方法
+ reset() {
+ this.isPermanent = true;
+ this.saveModeToStorage();
+ const container = document.getElementById('hNodeAlignKit');
+ container && (container.style.display = 'block');
+ const canvas = document.querySelector('canvas#graph-canvas');
+ canvas && this.canvasClickListener && (canvas.removeEventListener('click', this.canvasClickListener), this.canvasClickListener = null);
+ this.updateMenuButtonText();
}
- } else {
- const groups = this.groupNodesByCoordinate(selectedNodes, 0);
- groups.forEach(group => {
- const leftMost = Math.min(...group.map(node => node.pos[0]));
- group.forEach(node => {
- node.pos[0] = leftMost;
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- hBtnC_alignRight() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length > 0) {
- const groups = this.groupNodesByCoordinate(selectedNodes, 0);
- groups.forEach(group => {
- const rightMost = Math.max(...group.map(node => node.pos[0] + node.size[0]));
- group.forEach(node => {
- node.pos[0] = rightMost - node.size[0];
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- hBtnD_alignTop() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length > 0) {
- const groups = this.groupNodesByCoordinate(selectedNodes, 1);
- groups.forEach(group => {
- const topMost = Math.min(...group.map(node => node.pos[1]));
- group.forEach(node => {
- node.pos[1] = topMost;
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- hBtnF_alignButton() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length > 0) {
- const groups = this.groupNodesByCoordinate(selectedNodes, 1);
- groups.forEach(group => {
- const bottomMost = Math.max(...group.map(node => node.pos[1] + node.size[1]));
- group.forEach(node => {
- node.pos[1] = bottomMost - node.size[1];
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- hBtnE_alignCenterH() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length > 0) {
- const groups = this.groupNodesByCoordinate(selectedNodes, 1);
- groups.forEach(group => {
- const centerY = this.calculateCenterInGroup(group, 1);
- group.forEach(node => {
- node.pos[1] = centerY - node.size[1] / 2;
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- hBtnB_alignCenterV() {
- const selectedNodes = this.getSelectedNodes();
- if (selectedNodes.length > 0) {
- const groups = this.groupNodesByCoordinate(selectedNodes, 0);
- groups.forEach(group => {
- const centerX = this.calculateCenterInGroup(group, 0);
- group.forEach(node => {
- node.pos[0] = centerX - node.size[0] / 2;
- });
- });
- }
- LGraphCanvas.active_canvas.setDirty(true, true);
- },
- calculateCenterInGroup(group, axis) {
- const minCoord = Math.min(...group.map(node => node.pos[axis]));
- const maxCoord = Math.max(...group.map(node => node.pos[axis] + node.size[axis]));
- return (minCoord + maxCoord) / 2;
- },
- hBtnG_distributionH() {
- const nodes = this.getSelectedNodes();
- const axis = 0;
- if (nodes.length > 1) {
- nodes.sort((a, b) => a.pos[axis] - b.pos[axis]);
- const min = Math.min(...nodes.map(node => node.pos[axis]));
- const max = Math.max(...nodes.map(node => node.pos[axis] + node.size[axis]));
- const totalSize = nodes.reduce((sum, node) => sum + node.size[axis], 0);
- const spacing = (max - min - totalSize) / (nodes.length - 1);
- let current = min;
- nodes.forEach(node => {
- node.pos[axis] = current;
- current += node.size[axis] + spacing;
- });
- LGraphCanvas.active_canvas.setDirty(true, true);
- }
- },
- hBtnH_distributionV() {
- const nodes = this.getSelectedNodes();
- if (nodes.length > 1) {
- const axis = 1;
- const otherAxis = 0;
- const tolerance = 100;
- const minSpacing = 20;
- const columns = [];
- nodes.forEach(node => {
- let foundColumn = null;
- for (let column of columns) {
- const columnX = column[0].pos[otherAxis];
- if (Math.abs(columnX - node.pos[otherAxis]) <= tolerance) {
- foundColumn = column;
- break;
- }
- }
- if (foundColumn) {
- foundColumn.push(node);
- } else {
- columns.push([node]);
- }
- });
- const columnHeights = columns.map(column => {
- const minY = Math.min(...column.map(node => node.pos[axis]));
- const maxY = Math.max(...column.map(node => node.pos[axis] + node.size[axis]));
- return maxY - minY;
- });
- const maxColumnHeight = Math.max(...columnHeights);
- columns.forEach((column, columnIndex) => {
- if (column.length > 1) {
- column.sort((a, b) => a.pos[axis] - b.pos[axis]);
- const minY = Math.min(...column.map(node => node.pos[axis]));
- const totalSize = column.reduce((sum, node) => sum + node.size[axis], 0);
- let spacing = (maxColumnHeight - totalSize) / (column.length - 1);
- spacing = Math.max(spacing, minSpacing);
- let currentY = column[0].pos[axis];
- column.forEach((node, idx) => {
- if (idx === 0) {
- currentY += node.size[axis] + spacing;
- } else {
- node.pos[axis] = currentY;
- currentY += node.size[axis] + spacing;
- }
- node.pos[otherAxis] = column[0].pos[otherAxis];
+
+ // 常驻显示模式
+ setPermanentMode() {
+ this.isPermanent = true;
+ this.saveModeToStorage();
+ const container = document.getElementById('hNodeAlignKit');
+ container && (container.style.display = 'block');
+ const canvas = document.querySelector('canvas#graph-canvas');
+ canvas && this.canvasClickListener && (canvas.removeEventListener('click', this.canvasClickListener), this.canvasClickListener = null);
+ this.updateMenuButtonText();
+ }
+
+ // 跟随选框模式
+ setFollowingMode() {
+ this.isPermanent = false;
+ this.saveModeToStorage();
+ const container = document.getElementById('hNodeAlignKit');
+ container && (container.style.display = 'none');
+ this.setupCanvasListener();
+ this.updateMenuButtonText();
+ }
+ setupCanvasListener() {
+ const canvas = document.querySelector('canvas#graph-canvas');
+ if (!canvas) { setTimeout(() => this.setupCanvasListener(), 500); return; }
+ this.canvasClickListener = (event) => { const selectedNodesCount = this.getSelectedNodesCount(); selectedNodesCount >= 2 ? this.showAtPosition(event.clientX, event.clientY) : this.hide(); };
+ canvas.addEventListener('click', this.canvasClickListener);
+ }
+
+ getSelectedNodesCount() { if (!LGraphCanvas?.active_canvas?.selected_nodes) return 0; return Object.keys(LGraphCanvas.active_canvas.selected_nodes || {}).length; }
+
+ showAtPosition(clientX, clientY) {
+ const container = document.getElementById('hNodeAlignKit');
+ if (!container || !window.containerController) return;
+
+ container.style.display = 'block';
+ const originalSize = window.containerController.getContainerOriginalSize(); const scaledWidth = originalSize.width * window.containerController.scale; const scaledHeight = originalSize.height * window.containerController.scale;
+ let newLeft = clientX - scaledWidth / 2; let newTop = clientY + 20;
+ const windowWidth = window.innerWidth; const windowHeight = window.innerHeight;
+
+ if (newLeft < 0) newLeft = 0; if (newLeft + scaledWidth > windowWidth) newLeft = windowWidth - scaledWidth; if (newTop < 0) newTop = 0; if (newTop + scaledHeight > windowHeight) newTop = windowHeight - scaledHeight;
+
+ window.containerController.posX = newLeft; window.containerController.posY = newTop; window.containerController.updateTransform();
+ setTimeout(() => { window.containerController && window.containerController.enforceBoundaries(); }, 10);
+ }
+
+ hide() { const container = document.getElementById('hNodeAlignKit'); container && (container.style.display = 'none'); }
+
+ updateMenuButtonText() {
+ const menuBtn = document.querySelector('[data-target="hCMP-hSel__display-options"]');
+ menuBtn && (menuBtn.textContent = this.isPermanent ? '常驻显示' : '跟随选框');
+ const optionsContainer = document.getElementById('hCMP-hSel__display-options');
+ optionsContainer && optionsContainer.querySelectorAll('.hCMP-hSel__option').forEach(opt => {
+ opt.classList.remove('selected');
+ (this.isPermanent && opt.getAttribute('data-value') === 'hDispMode0_Always') || (!this.isPermanent && opt.getAttribute('data-value') === 'hDispMode1_Follow') && opt.classList.add('selected');
});
- } else if (column.length === 1) {
- const node = column[0];
- }
- });
- LGraphCanvas.active_canvas.setDirty(true, true);
- }
- },
- hBtnI_equalWidth() {
- this.equalSize(0);
- },
- hBtnJ_equalHeight() {
- this.equalSize(1);
- },
- equalSize(axis) {
- const nodes = this.getSelectedNodes();
- if (nodes.length > 0) {
- const maxSize = Math.max(...nodes.map(node => node.size[axis]));
- nodes.forEach(node => {
- node.size[axis] = maxSize;
- });
- LGraphCanvas.active_canvas.setDirty(true, true);
- }
- }
- };
-
- function pollForCanvas() {
- const canvas = document.querySelector('canvas#graph-canvas');
- if (canvas) {
- ButtonManager.init();
- canvas.addEventListener('click', function(event) {
- if (!ButtonManager.isPermanent) {
- const selectedNodes = ButtonManager.getSelectedNodes();
- if (selectedNodes.length >= 2) {
- ButtonManager.show();
- ButtonManager.setPosition(event.layerX - 240, event.layerY - 20);
- } else {
- ButtonManager.hide();
- }
}
- });
- } else {
- setTimeout(pollForCanvas, 1000);
+
+ saveModeToStorage() { localStorage.setItem('NodeAlignProDisplayMode', this.isPermanent ? 'permanent' : 'following'); }
+ loadModeFromStorage() { const savedMode = localStorage.getItem('NodeAlignProDisplayMode'); savedMode !== null && (this.isPermanent = savedMode === 'permanent', this.updateMenuButtonText()); }
}
- }
- pollForCanvas();
\ No newline at end of file
+
+ window.__hMgr_DisplayMode = new __hMgr_DisplayMode();
+/* // 【== 创建对应的处理函数 ==】
+ function setPermanentDisplayMode() { window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setPermanentMode(); }
+ function setFollowingDisplayMode() { window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setFollowingMode(); } */
+ // 【== 显示模式管理器-结束 ==】
+
+
+ // 【== 处理下拉菜单选择 ==】
+ const __hMenu_Selection = (value) => {
+ switch (value) {
+ case 'hDragMode1_Split':
+ __hMgr_ACbar.setLinkMode(0);
+ break;
+ case 'hDragMode0_Link':
+ __hMgr_ACbar.setLinkMode(1);
+ break;
+ case 'hUIScale_0_5x':
+ case 'hUIScale_0_75x':
+ case 'hUIScale_1x':
+ case 'hUIScale_1_25x':
+ case 'hUIScale_1_5x':
+ case 'hUIScale_2x':
+ const targetScale = scaleMapping[value],
+ containerRect = document.getElementById('hNodeAlignKit').getBoundingClientRect();
+ const centerX = containerRect.left + containerRect.width / 2,
+ centerY = containerRect.top + containerRect.height / 2;
+ window.containerController && targetScale && (() => {
+ window.containerController.zoomToScale(targetScale, centerX, centerY);
+ })();
+ break;
+ // 【修复】使用__hMgr_DisplayMode的方法
+ case 'hDispMode0_Always':
+ window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setPermanentMode();
+ break;
+ case 'hDispMode1_Follow':
+ window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setFollowingMode();
+ break;
+ }
+ };
+
+ // 【== 初始化流程 ==】
+ const __hInit_hNAP = () => {
+ // 初始化日志管理器
+ window.__hLogManager = new __hLogManager();
+ hLog.info('初始化ComfyUI_EasyKitHT_NodeAlignPro插件完毕, 等待DOM加载...');
+
+ const container = __hCreateHTML();
+ document.body.appendChild(container);
+
+ // 延迟100ms等待DOM渲染完成后初始化核心逻辑
+ setTimeout(() => {
+ hLog.debug('NodeAlignPro核心组件初始化完毕! 请等待其它插件加载...');
+ // 初始化各类管理器
+ window.containerController = new __hController_hNAPKit(container),
+ window.__hMgr_PopEl__Position = new __hMgr_PopEl__Position(),
+ window.__hMgr_PopEl__Position.init(container),
+ window.__hMgr_MenuHide = new __hMgr_MenuHide();
+ __hInit_AllIcons(), __hInit_MainInterface(), __hInit_hMenu__Dropdown();
+ window.__hColor_Module = new __hColor_Module();
+ __hInit_ColorPicker();
+
+ // 初始化显示模式管理器
+ window.__hMgr_DisplayMode = new __hMgr_DisplayMode();
+
+ // 从存储加载设置
+ const savedDisplayMode = localStorage.getItem('NodeAlignProDisplayMode');
+ if (savedDisplayMode === 'following') {
+ window.__hMgr_DisplayMode.setFollowingMode(); hLog.info('显示模式: 跟随选框');
+ } else {
+ window.__hMgr_DisplayMode.setPermanentMode(); hLog.info('显示模式: 常驻显示');
+ }
+
+ hLog.log('NodeAlignPro 插件初始化完成');
+ setTimeout(() => {
+ __hMgr_ACbar.loadModeFromStorage(); hLog.info('联动模式: 已禁用');
+ __hMgr_ACbar.linkMode === 1 && __hMgr_ACbar.syncRunButtonPosition(); hLog.info('联动模式: 已启用');
+ }, 500);
+ }, 100);
+ };
+
+ document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', __hInit_hNAP) : __hInit_hNAP();
+ window.addEventListener('beforeunload', () => { window.nodeSelectionListener && LGraphCanvas.active_canvas?.graph && LGraphCanvas.active_canvas.graph.off('selection', window.nodeSelectionListener); });
+
+ // 【== 控制台日志 ==】
+ document.addEventListener('DOMContentLoaded', () => { hLog.info('DOM资源加载完毕,正在初始化 hNodeAlignPro 主面板...'); });
+ window.addEventListener('load', () => { hLog.info('所有资源加载完毕,正在初始化 hNodeAlignPro 其余功能...'); });
+})();
\ No newline at end of file
diff --git a/web/color_svgs.js b/web/color_svgs.js
deleted file mode 100644
index 455c43f..0000000
--- a/web/color_svgs.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// ComfyUI_EasyKitHT_NodeAlignPro/web/color_svgs.js
-export const ColorSVGs = {
- circle: (color) => `
-
- `,
-
- colorPicker: `
-
- `
-
-};
\ No newline at end of file