Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33446f9233 | ||
|
|
5f75ea9c77 | ||
|
|
ce1038edec | ||
|
|
49e9cc69b5 | ||
|
|
792704cc73 |
@@ -1,3 +1,4 @@
|
||||

|
||||
# EasyKit-NodeAlign-Pro [↙切换中文] 🎨
|
||||
|
||||
[](https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro/stargazers) [](LICENSE) [](README_ZH.md) [](README.md)
|
||||
@@ -16,10 +17,8 @@ Features an original real-time preview color adjustment/picking component based
|
||||
**The new v2.0 is fully adapted with an i18n multilingual configuration file, natively supporting languages like Chinese, English (and can be extended in the future to support more languages such as Russian, French, etc.), making it more user-friendly for friends around the world.**
|
||||
While preserving the UI and interaction habits of the previous version as much as possible, the interface interaction logic has been fine-tuned, and the UI continues to be iteratively updated (see images below).
|
||||
|
||||
#### ✅ New UI Showcase
|
||||
|
||||

|
||||
#### ✅【Update 260125】: Added ComfyUI menu, new i18n multilingual adaptation (now integrated into the official menu page)👇
|
||||

|
||||

|
||||
|
||||
#### If you like it, feel free to give it a **⭐Star**! Your encouragement is my greatest motivation!
|
||||
@@ -116,7 +115,6 @@ git fetch --all && git reset --hard origin/main
|
||||
|
||||
---
|
||||
|
||||

|
||||
#### 🚀 Quick Start (Brief Guide)
|
||||
After successful installation, basic operations (for reference).
|
||||
|
||||
@@ -184,23 +182,6 @@ After successful installation, basic operations (for reference).
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><b>⚙️ Technical Features...</b></summary>
|
||||
|
||||
#### Architecture Design
|
||||
- **🌐 Native Development**: Implemented from scratch using native JS+CSS.
|
||||
- **🔒 Zero Dependencies**: No reliance on any third-party frameworks/libraries or existing code snippets.
|
||||
- **📴 Fully Offline Operation**: Runs entirely locally, no network resources required, ensuring security and reliability for private deployment.
|
||||
- **📄 Single-File Characteristic**: Core features developed in a centralized single file. While somewhat lengthy, it facilitates centralized management and debugging.
|
||||
- **🧩 Modular Design**: Color, Alignment, and UI Controller are separated.
|
||||
- **🏷️ Standardized Naming**: Uses a custom naming convention for clear distinction.
|
||||
- **💻 Responsive Layout**: Adapts to different screen resolutions.
|
||||
- **⚡ Performance Optimized**: Throttling and debouncing applied where necessary.
|
||||
- **💾 State Persistence**: Plugin settings are automatically cached for next use.
|
||||
- **✅ Truly Open Source / No Obfuscation**: Necessary comments and variable/parameter names are fully preserved; code is not obfuscated for pseudo-open-source.
|
||||
|
||||
</details>
|
||||
|
||||
## 🔄 Changelog
|
||||
|
||||
### v2.1.0 (Latest 2.1 Release)
|
||||
|
||||
+3
-22
@@ -1,3 +1,4 @@
|
||||

|
||||
# EasyKit-NodeAlign-Pro [↙切换中文] 🎨
|
||||
|
||||
[](https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro/stargazers) [](LICENSE) [](README_ZH.md) [](README.md)
|
||||
@@ -5,7 +6,7 @@
|
||||
## 📋 项目简介
|
||||
|
||||
**EasyKit-NodeAlign-Pro** 由设计师独立开发(原名称:ComfyUI_EasyKitHT_NodeAlign Pro):
|
||||
for omfyUI node 2.0: 用于管理 ComfyUI 中节点布局和配色方案的必备插件,具备专业的对齐功能和实时节点取色器 **已适配ComfyUI Node2.0** 。功能能涵盖对齐分布、实时节点取色器、调色盘、7 种预设颜色、灰度/自定义模式以及一键反向对齐等功能,提供全新对齐工具和专业的节点色彩管理系统。内置对齐工具的布局更符合设业通用的操作逻辑和体验,多种上色模式的上色工具更合理。(原生支持多语言UI界面显示)
|
||||
**v2.1.13:🔥本插件首发支持-最新版ComfyUI Node2.0节点。首创基于 Node2.0 的对齐算法(创新性适配)**: 用于管理 ComfyUI 中节点布局和配色方案的必备插件,具备专业的对齐功能和实时节点取色器 **已适配ComfyUI Node2.0** 。功能能涵盖对齐分布、实时节点取色器、调色盘、7 种预设颜色、灰度/自定义模式以及一键反向对齐等功能,提供全新对齐工具和专业的节点色彩管理系统。内置对齐工具的布局更符合设业通用的操作逻辑和体验,多种上色模式的上色工具更合理。(原生支持多语言UI界面显示)
|
||||
原创基于节点的实时预览调色/取色组件,方便快速节点取色/上色。该插件旨在提升ComfyUI用户的工作效率,通过直观的界面和丰富的功能简化节点布局与颜色管理流程。
|
||||
|
||||
---
|
||||
@@ -15,10 +16,8 @@ for omfyUI node 2.0: 用于管理 ComfyUI 中节点布局和配色方案的必
|
||||
**新版v2.0全新适配i18n多语言配置文件,原生支持中文、English等语言(后续视情况还可扩展支持更多诸如俄语、法语等语言),对世界各地的国际友人更友好。**
|
||||
同样在尽可能保留上一版UI及交互习惯的前提下,微调界面交互逻辑,继续更新迭代UI(详见下图)。
|
||||
|
||||
#### ✅ 新版UI界面展示
|
||||
|
||||

|
||||
#### ✅【260125更新】:新增ComfyUI菜单、新增i18n多语言适配 (现以集成到官方菜单页面)👇
|
||||

|
||||

|
||||
|
||||
#### 如果觉得还可以,欢迎帮忙点下**⭐Star**,您的鼓励就是我最大的动力~
|
||||
@@ -114,10 +113,8 @@ comfy node install easykit-node-align
|
||||
```
|
||||
git fetch --all && git reset --hard origin/main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||

|
||||
#### 🚀 快速开始(简要指南)
|
||||
成功安装NodeAlignPro之后,基础操作(仅供参考)
|
||||
|
||||
@@ -183,22 +180,6 @@ git fetch --all && git reset --hard origin/main
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><b>⚙️ 技术特性...</b></summary>
|
||||
|
||||
#### 架构设计
|
||||
- **🌐原生开发**:使用原生JS+CSS从零实现
|
||||
- **🔒零依赖**:不依赖任何第三方框架/库或现有代码段
|
||||
- **📴全离线运行**:全本地化运行,不依赖任何网络资源,确保本地化私有部署的安全可靠性
|
||||
- **📄单文件特性**:核心单文件集中开发,虽整体略显冗长,但方便集中管理,和调试维护
|
||||
- **🧩模块化设计**:颜色、对齐、UI控制器分离
|
||||
- **🏷️规范化命名**:自定义了一套自己风格的命名,做明显区分
|
||||
- **💻响应式布局**:自适应不同屏幕分辨率
|
||||
- **⚡性能优化**:必要环节的节流和防抖处理
|
||||
- **💾状态持久化**:对插件的设置自动缓存,下次使用可直接延续上次的设置
|
||||
- **✅真开源/不混淆**:完整保留必要的注释和变量参数名,不混淆代码进行伪开源
|
||||
|
||||
</details>
|
||||
|
||||
## 🔄 更新日志
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
|
||||
# 版本定义
|
||||
# __version__ = "2.1.12"
|
||||
# __version__ = "2.1.13"
|
||||
# VERSION = __version__
|
||||
|
||||
# 节点注册
|
||||
@@ -14,4 +14,4 @@ WEB_DIRECTORY = os.path.join(os.path.dirname(__file__), 'web')
|
||||
|
||||
# 当前目录设置
|
||||
# current_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
NODE_JS = ["web/hNodeAlignPro.js", "web/hNodeAlignPro_settings.js"] # , "web/hNodeAlignPro_n2.js"
|
||||
NODE_JS = ["web/hNodeAlignPro.js", "web/hNodeAlignPro_cfg.js"] # , "web/hNodeAlignPro_settings.js", "web/hNodeAlignPro_n2.js"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 220 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 179 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
+1
-1
@@ -2,7 +2,7 @@
|
||||
name = "easykit-node-align"
|
||||
# name = "easykitht_nodealignpro"
|
||||
description = "Professional alignment & real-time node color picker. A must-have plugin for managing node layout and color schemes in ComfyUI. Features a real-time color picker, alignment, 7 preset colors, grayscale/custom modes, and one-click reverse alignment."
|
||||
version = "2.1.12"
|
||||
version = "2.1.13"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
+20
-9
@@ -67,7 +67,8 @@
|
||||
hSelKit_DisplayMode: {cn: '显示模式:', en: 'Display Mode'},
|
||||
Setting_DragMode: {cn: '拖拽方式 (Drag Mode)', en: 'Drag Mode'},
|
||||
Setting_UIScale: {cn: 'UI缩放 (UI Scale)', en: 'UI Scale'},
|
||||
Setting_WorkMode: {cn: '工作模式 (Work Mode)', en: 'Work Mode'},
|
||||
Setting_WorkMode: {cn: '工作模式 (Work Mode):可开启新版Node2.0对齐模式', en: 'Work Mode'},
|
||||
hTooltip_WorkMode: { cn: '工作模式:切换插件工作模式,可开启新版Node2.0对齐模式', en: 'Work Mode: Switch plugin working mode (sync with right-click menu). Enable new Node2.0 alignment mode.' },
|
||||
Setting_DisplayMode: {cn: '显示模式 (Display Mode)', en: 'Display Mode'},
|
||||
Setting_AlignBtnColor: {cn: '对齐按钮颜色 (Align Button Color)', en: 'Align Button Color'},
|
||||
Setting_ToolbarBgColor: {cn: '工具栏背景色 (Toolbar Background Color)', en: 'Toolbar Background Color'},
|
||||
@@ -78,23 +79,33 @@
|
||||
// 选项标签文字
|
||||
Option_Lang_CN: {cn: '中文', en: '中文'},
|
||||
Option_Lang_EN: {cn: 'English', en: 'English'},
|
||||
Option_Lang_Auto: {cn: '自动(Auto)', en: 'Auto'},
|
||||
hSelKit_DragSplit: {cn: '解 耦', en: 'Split'},
|
||||
Option_Drag_Split: {cn: '解 耦(Split)', en: 'Split'},
|
||||
hSelKit_DragSplit2: {cn: '解 耦(Split)', en: 'Split'},
|
||||
hSelKit_DragLink: {cn: '联 动', en: 'Link'},
|
||||
Option_Drag_Link: {cn: '联 动(Link)', en: 'Link'},
|
||||
hSelKit_DragLink2: {cn: '联 动(Link)', en: 'Link'},
|
||||
|
||||
|
||||
hSelKit_APBall: {cn: 'AP球', en: 'AP Ball'},
|
||||
hSelKit_StdBar: {cn: '标 准', en: 'Standard'},
|
||||
hSelKit_AlignAuto: {cn: '自动', en: 'Auto'},
|
||||
hSelKit_AlignAuto2: {cn: '自动(Auto)', en: 'Auto'},
|
||||
hSelKit_ColorBar: {cn: '色 卡', en: 'ColorBar'},
|
||||
// hSelKit_StdBar: {cn: '标 准', en: 'Standard'},
|
||||
hSelKit_ColorBar2: {cn: '色卡(ColorBar)', en: 'ColorBar'},
|
||||
hSelKit_AlignBar: {cn: '传统对齐', en: 'AlignStd'},
|
||||
hSelKit_AlignBar2: {cn: '传统对齐(AlignStd)', en: 'AlignStd'},
|
||||
hSelKit_Node2: {cn: 'Node2.0', en: 'Node2.0'},
|
||||
hSelKit_ProBar: {cn: '专 业', en: 'Pro'},
|
||||
// Option_Work_Align: {cn: '-对 齐-', en: '-Align-'},
|
||||
hSelKit_AlignBar: {cn: '对 齐', en: 'AlignKit'},
|
||||
Option_Display_Always: {cn: '常驻显示', en: 'Always'},
|
||||
Option_Display_Follow: {cn: '跟随选框', en: 'Follow+'},
|
||||
|
||||
hSelKit_Always: {cn: '常驻显示', en: 'Always'},
|
||||
hSelKit_Always2: {cn: '常驻显示(Always)', en: 'Always'},
|
||||
hSelKit_Follow: {cn: '跟随选框', en: 'Follow+'},
|
||||
hSelKit_Follow2: {cn: '跟随选框(Follow+)', en: 'Follow+'},
|
||||
hNodePreview_Tips: {cn: '👆双击切换上色模式:', en: '👆Dbl-Click this: '},
|
||||
Option_Color_Whole: {cn: '整体色', en: 'Whole Color'},
|
||||
Option_Color_Whole2: {cn: '整体色(Whole)', en: 'Whole Color'},
|
||||
Option_Color_TitleOnly: {cn: '仅标题', en: 'Title Only'},
|
||||
Option_Color_TitleOnly2: {cn: '仅标题(Title)', en: 'Title Only'},
|
||||
|
||||
Debug_Tips: {cn: 'v2.0.3_rc新版功能:按Shift、Alt、Ctrl Alt切换不同色卡模式...\nAlt+对齐按钮:对齐到“反向基准”节点^_^(右键菜单>【新版说明】隐藏本提示)', en: 'v2.0.3_rc: Use Shift/Alt/Ctrl+Alt to switch Alignment Mode...\nAlt+Align button: align to reversed reference node ^_^ (HideThis: Right-click > New Tips)'},
|
||||
Aria_ClearColor: {cn: '清除颜色', en: 'Clear Color'},
|
||||
Aria_Pick: {cn: '取色', en: 'Pick Color'},
|
||||
|
||||
+681
-34
@@ -395,6 +395,10 @@
|
||||
`;
|
||||
const styleSheet = document.createElement('style'); styleSheet.textContent = styles; document.head.appendChild(styleSheet);
|
||||
|
||||
// 默认隐藏 Node2 面板(若旧版单独脚本仍在加载并创建面板)
|
||||
const __hHide_Node2Panel = () => { try { const p = document.getElementById('hNodeAlignPro_n2_Panel'); if (p) p.style.display = 'none'; } catch (e) { } };
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', __hHide_Node2Panel); else __hHide_Node2Panel();
|
||||
|
||||
// 【== 全局颜色转换管理器 ==】
|
||||
class __hColorConvert {
|
||||
/** 将HSB/HSV颜色转换为RGB对象 @param {number} h - 色相 (0-360) @param {number} s - 饱和度 (0-100) @param {number} b - 亮度/明度 (0-100) @returns {{r: number, g: number, b: number}} RGB对象 */
|
||||
@@ -700,17 +704,18 @@
|
||||
<div class="hCMP-hSel__option" data-value="hUIScale_0_5x">0.5x</div><div class="hCMP-hSel__option" data-value="hUIScale_0_75x">0.75x</div><div class="hCMP-hSel__option selected" data-value="hUIScale_1x">1x</div><div class="hCMP-hSel__option" data-value="hUIScale_1_25x">1.25x</div><div class="hCMP-hSel__option" data-value="hUIScale_1_5x">1.5x</div><div class="hCMP-hSel__option" data-value="hUIScale_2x">2x</div></div></div></div>
|
||||
<div class="hCMP__hSelKit">
|
||||
<label class="hSelKit-label" data-i18n="hSelKit_WorkMode">工作模式</label><div class="hCMP-hSel">
|
||||
<div class="hMenu-btn" data-target="hCMP-hSel__mode-options" data-i18n="hSelKit_AlignBar">对 齐</div>
|
||||
<div class="hMenu-btn" data-target="hCMP-hSel__mode-options" data-i18n="hSelKit_AlignBar">传统对齐</div>
|
||||
<div class="hCMP-hSel__options" id="hCMP-hSel__mode-options">
|
||||
<div class="hCMP-hSel__option" data-value="hApBar0_apBall" style="opacity: 0.3; cursor: not-allowed;" data-i18n="hSelKit_APBall">AP球</div>
|
||||
<div class="hCMP-hSel__option" data-value="hApBar1_Color" style="opacity: 0.3; cursor: not-allowed;" data-i18n="hSelKit_ColorBar">色 卡</div>
|
||||
<div class="hCMP-hSel__option selected" data-value="hApBar2_Align" data-i18n="hSelKit_AlignBar">-对 齐-</div>
|
||||
<!-- <div class="hCMP-hSel__option" data-value="hApBar0_apBall" style="opacity: 0.3; cursor: not-allowed;" data-i18n="hSelKit_APBall">AP球</div> -->
|
||||
<!-- <div class="hCMP-hSel__option" data-value="hApBar0_AlignAuto" data-i18n="hSelKit_AlignAuto">自 动</div> -->
|
||||
<!-- <div class="hCMP-hSel__option" data-value="hApBar1_Color" data-i18n="hSelKit_ColorBar">色 卡</div> -->
|
||||
<div class="hCMP-hSel__option selected" data-value="hApBar2_Align" data-i18n="hSelKit_AlignBar">传统对齐</div>
|
||||
<div class="hCMP-hSel__option" data-value="hApBar2_Node2" data-i18n="hSelKit_Node2">Node2.0</div>
|
||||
<!-- <div class="hCMP-hSel__option" data-value="hApBar3_StdH" style="opacity: 0.3; cursor: not-allowed;" data-i18n="hSelKit_StdBar">标 准</div> -->
|
||||
<div class="hCMP-hSel__option" data-value="hApBar4_ProH" style="opacity: 0.3; cursor: not-allowed;" data-i18n="hSelKit_ProBar">专 业</div>
|
||||
</div></div></div>
|
||||
<div class="hCMP__hSelKit">
|
||||
<label class="hSelKit-label" data-i18n="hSelKit_DisplayMode">显示模式</label><div class="hCMP-hSel"><div class="hMenu-btn" data-target="hCMP-hSel__display-options" data-i18n="Option_Display_Always">常驻显示</div><div class="hCMP-hSel__options" id="hCMP-hSel__display-options"><div class="hCMP-hSel__option selected" data-value="hDispMode0_Always" data-i18n="Option_Display_Always">常驻显示</div><div class="hCMP-hSel__option" data-value="hDispMode1_Follow" data-i18n="Option_Display_Follow">跟随选框</div></div></div></div>
|
||||
<label class="hSelKit-label" data-i18n="hSelKit_DisplayMode">显示模式</label><div class="hCMP-hSel"><div class="hMenu-btn" data-target="hCMP-hSel__display-options" data-i18n="hSelKit_Always">常驻显示</div><div class="hCMP-hSel__options" id="hCMP-hSel__display-options"><div class="hCMP-hSel__option selected" data-value="hDispMode0_Always" data-i18n="hSelKit_Always">常驻显示</div><div class="hCMP-hSel__option" data-value="hDispMode1_Follow" data-i18n="hSelKit_Follow">跟随选框</div></div></div></div>
|
||||
<div>
|
||||
<button class="hMenu-btn hMenu-btnReset" id="hReset" data-i18n="hMenu_ResetAll">一键重置</button>
|
||||
<button class="hMenu-btn" id="hBugReport" data-i18n="hMenu_BugReport">bug反馈</button>
|
||||
@@ -1132,47 +1137,624 @@
|
||||
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(); } // 通用等尺寸处理:dim(0=宽度/1=高度)、getTargetSize(目标尺寸计算回调)
|
||||
};
|
||||
|
||||
// 【== Node2.0 兼容层:选择性使用 Node2 API 或回退到传统实现 ==】
|
||||
// 对齐逻辑选择:'align' = 原始 hNodeAlignPro 逻辑,'node2' = Node2.0 专用逻辑,'color' = 仅色卡,'auto' = 自动
|
||||
window.__hAlignLogicMode = window.__hAlignLogicMode || localStorage.getItem('hNodeAlignPro_Logic') || 'align';
|
||||
|
||||
// 将工作模式/对齐逻辑的变更同步到右键菜单显示与面板可见性
|
||||
function __hSync_WorkModeUI(modeKey) {
|
||||
try {
|
||||
const modeBtn = document.querySelector('[data-target="hCMP-hSel__mode-options"]');
|
||||
const 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"]'),
|
||||
'hApBar2_Node2': document.querySelector('[data-value="hApBar2_Node2"]'),
|
||||
'hApBar3_StdH': document.querySelector('[data-value="hApBar3_StdH"]'),
|
||||
'hApBar4_ProH': document.querySelector('[data-value="hApBar4_ProH"]')
|
||||
};
|
||||
|
||||
// 统一将modeKey映射到菜单项key(支持设置面板的值或legacy逻辑值)
|
||||
let targetKey = 'hApBar2_Align';
|
||||
if (modeKey === 'hApBar2_Node2' || modeKey === 'node2') targetKey = 'hApBar2_Node2';
|
||||
else if (modeKey === 'hApBar1_Color' || modeKey === 'color') targetKey = 'hApBar1_Color';
|
||||
else if (modeKey === 'hAlign_Auto' || modeKey === 'hApBar0_AlignAuto' || modeKey === 'auto') targetKey = 'hApBar0_AlignAuto';
|
||||
else targetKey = 'hApBar2_Align';
|
||||
|
||||
// 更新按钮文本
|
||||
try {
|
||||
if (modeBtn) {
|
||||
const txt = (function (k) {
|
||||
switch (k) {
|
||||
case 'hApBar1_Color': return (window.hLanguage && typeof window.hLanguage.t === 'function') ? window.hLanguage.t('hSelKit_ColorBar') : '色 卡';
|
||||
case 'hApBar2_Node2': return (window.hLanguage && typeof window.hLanguage.t === 'function') ? window.hLanguage.t('hSelKit_Node2') : 'Node2.0';
|
||||
case 'hApBar0_apBall': return (window.hLanguage && typeof window.hLanguage.t === 'function') ? window.hLanguage.t('hSelKit_AlignAuto') : '自 动';
|
||||
default: return (window.hLanguage && typeof window.hLanguage.t === 'function') ? window.hLanguage.t('hSelKit_AlignBar') : '传统对齐';
|
||||
}
|
||||
})(targetKey);
|
||||
modeBtn.textContent = txt;
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
// 更新选中样式
|
||||
Object.values(modeOptions).forEach(opt => opt && opt.classList.remove('selected'));
|
||||
modeOptions[targetKey] && modeOptions[targetKey].classList.add('selected');
|
||||
|
||||
// 根据模式仅控制对齐面板(h2__hNodeAlignPro)的显隐:
|
||||
// 色卡模式 -> 隐藏对齐面板;其余模式 -> 显示对齐面板
|
||||
const alignPanel = document.getElementById('h2__hNodeAlignPro');
|
||||
if (targetKey === 'hApBar1_Color') {
|
||||
if (alignPanel) alignPanel.style.display = 'none';
|
||||
localStorage.setItem('hNodeAlignPro_Logic', 'color');
|
||||
} else {
|
||||
if (alignPanel) alignPanel.style.display = 'block';
|
||||
// 将node2/align/auto逻辑以更通用的标识存储到 legacy 键,供自动检测使用
|
||||
if (targetKey === 'hApBar2_Node2') localStorage.setItem('hNodeAlignPro_Logic', 'node2');
|
||||
else if (targetKey === 'hApBar0_AlignAuto') localStorage.setItem('hNodeAlignPro_Logic', 'auto');
|
||||
else localStorage.setItem('hNodeAlignPro_Logic', 'align');
|
||||
}
|
||||
} catch (e) { console.warn('同步工作模式UI失败:', e); }
|
||||
}
|
||||
|
||||
function __hSetAlignLogicMode(mode) { try { window.__hAlignLogicMode = mode; localStorage.setItem('hNodeAlignPro_Logic', mode); console.log('hNodeAlignPro: 对齐逻辑切换为', mode); __hSync_WorkModeUI(mode); } catch (e) { } }
|
||||
window.__hSetAlignLogicMode = __hSetAlignLogicMode;
|
||||
|
||||
function __hIsNode2Mode() {
|
||||
try {
|
||||
const toggleByLabel = document.querySelector('input.p-toggleswitch-input[aria-label="Nodes 2.0"]');
|
||||
const settingsWrapper = document.getElementById('Comfy.VueNodes.Enabled');
|
||||
const toggleBySettings = settingsWrapper ? settingsWrapper.querySelector('input[type="checkbox"], input.p-toggleswitch-input') : null;
|
||||
// 优先尊重用户在右键菜单中选择的逻辑(若用户已选择,则覆盖自动检测)
|
||||
if (window.__hAlignLogicMode === 'node2') return true;
|
||||
if (window.__hAlignLogicMode === 'align') return false;
|
||||
const uiToggle = toggleByLabel || toggleBySettings;
|
||||
if (uiToggle) return !!uiToggle.checked;
|
||||
const hasAppCanvas = !!(window.app && window.app.canvas);
|
||||
const hasLegacyCanvas = !!(typeof LGraphCanvas !== 'undefined' && LGraphCanvas.active_canvas);
|
||||
const hasNode2Apis = hasAppCanvas && (
|
||||
typeof window.app.canvas.repositionNodesVueMode === 'function' ||
|
||||
Array.isArray(window.app.canvas.selectedItems) ||
|
||||
(window.app.canvas.selectedItems && typeof window.app.canvas.selectedItems === 'object')
|
||||
);
|
||||
return hasAppCanvas && !hasLegacyCanvas && hasNode2Apis;
|
||||
} catch (e) { return false; }
|
||||
}
|
||||
|
||||
// Node2 helper: 获取选中节点(优先 Vue 组件/Node2 API),回退到 LGraphCanvas.selected_nodes
|
||||
function __hNode2_getSelectedNodes() {
|
||||
try {
|
||||
const app = window.app;
|
||||
if (app && app.canvas && app.canvas.selectedItems) {
|
||||
const items = Array.from(app.canvas.selectedItems || []);
|
||||
const nodes = [];
|
||||
for (const item of items) {
|
||||
if (!item) continue;
|
||||
if (item.constructor && item.constructor.name === 'LGraphNode') nodes.push(item);
|
||||
else if (item.node && item.node.constructor && item.node.constructor.name === 'LGraphNode') nodes.push(item);
|
||||
else nodes.push(item);
|
||||
}
|
||||
if (nodes.length > 0) return nodes;
|
||||
}
|
||||
if (typeof LGraphCanvas !== 'undefined' && LGraphCanvas.active_canvas && LGraphCanvas.active_canvas.selected_nodes) return Object.values(LGraphCanvas.active_canvas.selected_nodes);
|
||||
} catch (e) { console.warn('Node2 getSelectedNodes error', e); }
|
||||
return [];
|
||||
}
|
||||
// Node2 helper: 可靠的节点位置/尺寸访问器与设置函数(兼容 Vue 组件 / LGraphNode)
|
||||
function __hNode2_getNodePos(node) {
|
||||
if (!node) return { x: 0, y: 0 };
|
||||
if (Array.isArray(node.pos) && node.pos.length >= 2) return { x: node.pos[0], y: node.pos[1] };
|
||||
if (node.node && Array.isArray(node.node.pos)) return { x: node.node.pos[0], y: node.node.pos[1] };
|
||||
if (typeof node.x === 'number' && typeof node.y === 'number') return { x: node.x, y: node.y };
|
||||
return { x: node.pos?.[0] || 0, y: node.pos?.[1] || 0 };
|
||||
}
|
||||
|
||||
function __hNode2_getNodeSize(node) {
|
||||
if (!node) return { w: 0, h: 0 };
|
||||
if (Array.isArray(node.size) && node.size.length >= 2) return { w: node.size[0], h: node.size[1] };
|
||||
if (node.node && Array.isArray(node.node.size)) return { w: node.node.size[0], h: node.node.size[1] };
|
||||
if (typeof node.width === 'number' && typeof node.height === 'number') return { w: node.width, h: node.height };
|
||||
return { w: node.size?.[0] || node.width || 0, h: node.size?.[1] || node.height || 0 };
|
||||
}
|
||||
|
||||
function __hNode2_setNodeSize(node, w, h) {
|
||||
if (!node) return false;
|
||||
if (typeof node.setSize === 'function') { node.setSize([w, h]); return true; }
|
||||
if (typeof node.resize === 'function') { node.resize(w, h); return true; }
|
||||
if (typeof node.updateSize === 'function') { node.updateSize(w, h); return true; }
|
||||
if (typeof node.$emit === 'function') { node.$emit('resize', w, h); return true; }
|
||||
if (node.node && typeof node.node.setSize === 'function') { node.node.setSize([w, h]); return true; }
|
||||
if (node.size && node.size.length === 2) { node.size[0] = w; node.size[1] = h; return true; }
|
||||
if (typeof node.width === 'number') { node.width = w; node.height = h; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
function __hNode2_getBoundaryNodes(nodes) {
|
||||
if (!nodes || nodes.length === 0) return null;
|
||||
let top = null, right = null, bottom = null, left = null;
|
||||
for (const node of nodes) {
|
||||
const p = __hNode2_getNodePos(node);
|
||||
const s = __hNode2_getNodeSize(node);
|
||||
if (!top || p.y < __hNode2_getNodePos(top).y) top = node;
|
||||
if (!bottom || (p.y + s.h) > (__hNode2_getNodePos(bottom).y + __hNode2_getNodeSize(bottom).h)) bottom = node;
|
||||
if (!left || p.x < __hNode2_getNodePos(left).x) left = node;
|
||||
if (!right || (p.x + s.w) > (__hNode2_getNodePos(right).x + __hNode2_getNodeSize(right).w)) right = node;
|
||||
}
|
||||
return { top, right, bottom, left };
|
||||
}
|
||||
|
||||
// 手动分布(与 n2 文件实现保持一致),返回包含 newSize 的结果数组
|
||||
function __hNode2_manualDistributeNodes(nodes, isVertical) {
|
||||
if (!nodes || nodes.length < 2) return [];
|
||||
const sortedNodes = [...nodes].sort((a, b) => {
|
||||
const pa = __hNode2_getNodePos(a);
|
||||
const pb = __hNode2_getNodePos(b);
|
||||
return isVertical ? pa.y - pb.y : pa.x - pb.x;
|
||||
});
|
||||
const minPos = isVertical ? Math.min(...sortedNodes.map(n => __hNode2_getNodePos(n).y)) : Math.min(...sortedNodes.map(n => __hNode2_getNodePos(n).x));
|
||||
const maxPos = isVertical ? Math.max(...sortedNodes.map(n => __hNode2_getNodePos(n).y + __hNode2_getNodeSize(n).h)) : Math.max(...sortedNodes.map(n => __hNode2_getNodePos(n).x + __hNode2_getNodeSize(n).w));
|
||||
const totalSize = isVertical ? sortedNodes.reduce((sum, n) => sum + __hNode2_getNodeSize(n).h, 0) : sortedNodes.reduce((sum, n) => sum + __hNode2_getNodeSize(n).w, 0);
|
||||
const spacing = (maxPos - minPos - totalSize) / (sortedNodes.length - 1);
|
||||
const result = [];
|
||||
let currentPos = minPos;
|
||||
for (let i = 0; i < sortedNodes.length; i++) {
|
||||
const node = sortedNodes[i];
|
||||
const p = __hNode2_getNodePos(node);
|
||||
const s = __hNode2_getNodeSize(node);
|
||||
const newPos = isVertical ? { x: p.x, y: currentPos } : { x: currentPos, y: p.y };
|
||||
result.push({ node: node, newPos: newPos, newSize: [s.w, s.h] });
|
||||
currentPos += (isVertical ? s.h : s.w) + spacing;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 执行对齐(调用 alignNodes 或 LGraphCanvas.alignNodes),并确保返回项包含 newSize
|
||||
function __hNode2_performAlignment(nodes, direction) {
|
||||
try {
|
||||
const app = window.app;
|
||||
if (!nodes || nodes.length === 0) return false;
|
||||
let result;
|
||||
if (typeof alignNodes === 'function') {
|
||||
result = alignNodes(nodes, direction);
|
||||
} else if (typeof LGraphCanvas !== 'undefined' && LGraphCanvas.alignNodes) {
|
||||
const selectedNodesObj = {};
|
||||
nodes.forEach((node, idx) => selectedNodesObj[idx] = node);
|
||||
LGraphCanvas.alignNodes(selectedNodesObj, direction);
|
||||
result = nodes.map(node => ({ node: node, newPos: { x: __hNode2_getNodePos(node).x, y: __hNode2_getNodePos(node).y } }));
|
||||
} else {
|
||||
console.error('未找到对齐相关的 API,无法执行:' + direction);
|
||||
return false;
|
||||
}
|
||||
result = (result || []).map(item => {
|
||||
const entry = (item && item.node) ? item : { node: item, newPos: undefined };
|
||||
const s = __hNode2_getNodeSize(entry.node);
|
||||
if (!entry.newSize) entry.newSize = [s.w, s.h];
|
||||
if (!entry.newPos && entry.node) entry.newPos = __hNode2_getNodePos(entry.node);
|
||||
return entry;
|
||||
});
|
||||
if (app && app.canvas && typeof app.canvas.repositionNodesVueMode === 'function') {
|
||||
app.canvas.repositionNodesVueMode(result);
|
||||
if (app.canvas.setDirty) app.canvas.setDirty();
|
||||
return true;
|
||||
} else {
|
||||
console.error('repositionNodesVueMode 方法不可用');
|
||||
return false;
|
||||
}
|
||||
} catch (e) { console.warn('Node2 performAlignment error', e); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// 执行分布(调用 distributeNodes 或手动计算),并确保 newSize
|
||||
function __hNode2_performDistribution(nodes, isVertical) {
|
||||
try {
|
||||
const app = window.app;
|
||||
if (!nodes || nodes.length < 2) return false;
|
||||
let result;
|
||||
if (typeof distributeNodes === 'function') {
|
||||
result = distributeNodes(nodes, isVertical);
|
||||
} else {
|
||||
result = __hNode2_manualDistributeNodes(nodes, isVertical);
|
||||
}
|
||||
result = (result || []).map(item => {
|
||||
const entry = (item && item.node) ? item : { node: item, newPos: undefined };
|
||||
const s = __hNode2_getNodeSize(entry.node);
|
||||
if (!entry.newSize) entry.newSize = [s.w, s.h];
|
||||
if (!entry.newPos && entry.node) entry.newPos = __hNode2_getNodePos(entry.node);
|
||||
return entry;
|
||||
});
|
||||
if (app && app.canvas && typeof app.canvas.repositionNodesVueMode === 'function') {
|
||||
app.canvas.repositionNodesVueMode(result);
|
||||
if (app.canvas.setDirty) app.canvas.setDirty();
|
||||
return true;
|
||||
} else {
|
||||
console.error('repositionNodesVueMode 方法不可用');
|
||||
return false;
|
||||
}
|
||||
} catch (e) { console.warn('Node2 performDistribution error', e); }
|
||||
return false;
|
||||
}
|
||||
|
||||
// 【== 节点对齐工具 ==】
|
||||
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); },
|
||||
/* hBtnA_alignLeft(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
const isAlt = !!(e && e.altKey);
|
||||
const dir = isAlt ? 'right' : 'left';
|
||||
if (__hNode2_performAlignment(nodes, dir)) return;
|
||||
}
|
||||
__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);
|
||||
}, */
|
||||
// 左对齐(X轴)
|
||||
hBtnA_alignLeft(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最右侧节点的左边缘,否则使用最左侧节点的左边缘
|
||||
const refNode = isAlt ? nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.x + __hNode2_getNodeSize(node).w) > (maxPos.x + __hNode2_getNodeSize(maxNode).w) ? node : maxNode;
|
||||
}, nodes[0]) : nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.x < minPos.x ? node : minNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refX = __hNode2_getNodePos(refNode).x;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: refX, y: __hNode2_getNodePos(node).y },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最右侧节点的左边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最右侧节点
|
||||
const rightmostNode = nodes.reduce((maxNode, node) =>
|
||||
(node.pos[0] + node.size[0]) > (maxNode.pos[0] + maxNode.size[0]) ? node : maxNode
|
||||
, nodes[0]);
|
||||
return rightmostNode.pos[0];
|
||||
}
|
||||
return Math.min(...nodes.map(n => n.pos[0]));
|
||||
}, (n, ref) => n.pos[0] = ref);
|
||||
},
|
||||
|
||||
// 水平居中(X轴居中)
|
||||
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);
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 修复:Alt+点击时以最左侧节点的中心坐标为基准进行水平居中对齐
|
||||
const ref = isAlt
|
||||
? nodes.reduce((mn, n) => n.pos[0] < mn.pos[0] ? n : mn).pos[0] + nodes.reduce((mn, n) => n.pos[0] < mn.pos[0] ? 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;
|
||||
const result = nodes.map(n => ({ node: n, newPos: { x: ref - n.size[0] / 2, y: n.pos[1] } }));
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
__hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => {
|
||||
// 修复:Alt+点击时以最左侧节点的中心坐标为基准进行水平居中对齐
|
||||
if (isAlt) {
|
||||
const leftmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[0] < minNode.pos[0] ? node : minNode
|
||||
, nodes[0]);
|
||||
return leftmostNode.pos[0] + leftmostNode.size[0] / 2;
|
||||
}
|
||||
return (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); },
|
||||
|
||||
// 右对齐(X轴)
|
||||
/* hBtnC_alignRight(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
const isAlt = !!(e && e.altKey);
|
||||
const dir = isAlt ? 'left' : 'right';
|
||||
if (__hNode2_performAlignment(nodes, dir)) return;
|
||||
}
|
||||
__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]);
|
||||
}, */
|
||||
// 右对齐(X轴)
|
||||
hBtnC_alignRight(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最左侧节点的右边缘,否则使用最右侧节点的右边缘
|
||||
const refNode = isAlt ? nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.x < minPos.x ? node : minNode;
|
||||
}, nodes[0]) : nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.x + __hNode2_getNodeSize(node).w) > (maxPos.x + __hNode2_getNodeSize(maxNode).w) ? node : maxNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refX = __hNode2_getNodePos(refNode).x + __hNode2_getNodeSize(refNode).w;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: refX - __hNode2_getNodeSize(node).w, y: __hNode2_getNodePos(node).y },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最左侧节点的右边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最左侧节点
|
||||
const leftmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[0] < minNode.pos[0] ? node : minNode
|
||||
, nodes[0]);
|
||||
return leftmostNode.pos[0] + leftmostNode.size[0];
|
||||
}
|
||||
return Math.max(...nodes.map(n => n.pos[0] + n.size[0]));
|
||||
}, (n, ref) => n.pos[0] = ref - n.size[0]);
|
||||
},
|
||||
|
||||
// 顶对齐(Y轴)
|
||||
/* hBtnD_alignTop(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
const isAlt = !!(e && e.altKey);
|
||||
const dir = isAlt ? 'bottom' : 'top';
|
||||
if (__hNode2_performAlignment(nodes, dir)) return;
|
||||
}
|
||||
__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);
|
||||
}, */
|
||||
|
||||
// 顶对齐(Y轴)
|
||||
hBtnD_alignTop(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最底部节点的上边缘,否则使用最顶部节点的上边缘
|
||||
const refNode = isAlt ? nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.y + __hNode2_getNodeSize(node).h) > (maxPos.y + __hNode2_getNodeSize(maxNode).h) ? node : maxNode;
|
||||
}, nodes[0]) : nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.y < minPos.y ? node : minNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refY = __hNode2_getNodePos(refNode).y;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: __hNode2_getNodePos(node).x, y: refY },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最底部节点的上边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最底部节点
|
||||
const bottommostNode = nodes.reduce((maxNode, node) =>
|
||||
(node.pos[1] + node.size[1]) > (maxNode.pos[1] + maxNode.size[1]) ? node : maxNode
|
||||
, nodes[0]);
|
||||
return bottommostNode.pos[1];
|
||||
}
|
||||
return Math.min(...nodes.map(n => n.pos[1]));
|
||||
}, (n, ref) => n.pos[1] = ref);
|
||||
},
|
||||
|
||||
// 垂直居中(Y轴居中)
|
||||
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);
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 修复:Alt+点击时以最顶部节点的中心坐标为基准进行垂直居中对齐
|
||||
const ref = isAlt
|
||||
? nodes.reduce((mn, n) => n.pos[1] < mn.pos[1] ? n : mn).pos[1] + nodes.reduce((mn, n) => n.pos[1] < mn.pos[1] ? 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;
|
||||
const result = nodes.map(n => ({ node: n, newPos: { x: n.pos[0], y: ref - n.size[1] / 2 } }));
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
__hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => {
|
||||
// 修复:Alt+点击时以最顶部节点的中心坐标为基准进行垂直居中对齐
|
||||
if (isAlt) {
|
||||
const topmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[1] < minNode.pos[1] ? node : minNode
|
||||
, nodes[0]);
|
||||
return topmostNode.pos[1] + topmostNode.size[1] / 2;
|
||||
}
|
||||
return (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);
|
||||
},
|
||||
|
||||
// 底对齐(Y轴)
|
||||
/* hBtnF_alignButton(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
const isAlt = !!(e && e.altKey);
|
||||
const dir = isAlt ? 'top' : 'bottom';
|
||||
if (__hNode2_performAlignment(nodes, dir)) return;
|
||||
}
|
||||
__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]);
|
||||
}, */
|
||||
// 底对齐(Y轴)
|
||||
hBtnF_alignButton(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最顶部节点的下边缘,否则使用最底部节点的下边缘
|
||||
const refNode = isAlt ? nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.y < minPos.y ? node : minNode;
|
||||
}, nodes[0]) : nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.y + __hNode2_getNodeSize(node).h) > (maxPos.y + __hNode2_getNodeSize(maxNode).h) ? node : maxNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refY = __hNode2_getNodePos(refNode).y + __hNode2_getNodeSize(refNode).h;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: __hNode2_getNodePos(node).x, y: refY - __hNode2_getNodeSize(node).h },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最顶部节点的下边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最顶部节点
|
||||
const topmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[1] < minNode.pos[1] ? node : minNode
|
||||
, nodes[0]);
|
||||
return topmostNode.pos[1] + topmostNode.size[1];
|
||||
}
|
||||
return Math.max(...nodes.map(n => n.pos[1] + n.size[1]));
|
||||
}, (n, ref) => n.pos[1] = ref - n.size[1]);
|
||||
},
|
||||
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) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (isAlt) {
|
||||
if (nodes && nodes.length) {
|
||||
// 修复:Alt+点击时以最左侧节点为基准,固定20单位间距
|
||||
const leftmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[0] < minNode.pos[0] ? node : minNode
|
||||
, nodes[0]);
|
||||
let current = leftmostNode.pos[0];
|
||||
|
||||
// 按原始顺序排序(保持分布前的左右先后顺序)
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.pos[0] - b.pos[0]);
|
||||
|
||||
for (let i = 1; i < sortedNodes.length; i++) {
|
||||
current += sortedNodes[i - 1].size[0] + 20;
|
||||
sortedNodes[i].pos[0] = current;
|
||||
}
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
const result = sortedNodes.map(n => ({ node: n, newPos: { x: n.pos[0], y: n.pos[1] } }));
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (__hNode2_performDistribution(nodes, false)) return;
|
||||
}
|
||||
}
|
||||
__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])), 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; });
|
||||
})();
|
||||
if (isAlt) {
|
||||
// 修复:Alt+点击时以最左侧节点为基准,固定20单位间距
|
||||
const leftmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[axis] < minNode.pos[axis] ? node : minNode
|
||||
, nodes[0]);
|
||||
let current = leftmostNode.pos[axis];
|
||||
|
||||
// 按原始顺序排序(保持分布前的左右先后顺序)
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.pos[axis] - b.pos[axis]);
|
||||
|
||||
for (let i = 1; i < sortedNodes.length; i++) {
|
||||
current += sortedNodes[i - 1].size[axis] + 20;
|
||||
sortedNodes[i].pos[axis] = current;
|
||||
}
|
||||
} else {
|
||||
const min = Math.min(...nodes.map(n => n.pos[axis])),
|
||||
max = Math.max(...nodes.map(n => n.pos[axis] + n.size[axis])),
|
||||
total = nodes.reduce((sum, n) => sum + n.size[axis], 0),
|
||||
spacing = (max - min - total) / (nodes.length - 1);
|
||||
let current = min;
|
||||
nodes.forEach(n => { n.pos[axis] = current; current += n.size[axis] + spacing; });
|
||||
}
|
||||
});
|
||||
},
|
||||
hBtnH_distributionV(e) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (isAlt) {
|
||||
if (nodes && nodes.length) {
|
||||
// 修复:Alt+点击时以最顶部节点为基准,固定20单位间距
|
||||
const topmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[1] < minNode.pos[1] ? node : minNode
|
||||
, nodes[0]);
|
||||
let current = topmostNode.pos[1];
|
||||
|
||||
// 按原始顺序排序(保持分布前的上下先后顺序)
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.pos[1] - b.pos[1]);
|
||||
|
||||
for (let i = 1; i < sortedNodes.length; i++) {
|
||||
current += sortedNodes[i - 1].size[1] + 30 + 20;
|
||||
sortedNodes[i].pos[1] = current;
|
||||
}
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
const result = sortedNodes.map(n => ({ node: n, newPos: { x: n.pos[0], y: n.pos[1] } }));
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (__hNode2_performDistribution(nodes, true)) return;
|
||||
}
|
||||
}
|
||||
__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])), 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; });
|
||||
})();
|
||||
if (isAlt) {
|
||||
// 修复:Alt+点击时以最顶部节点为基准,固定20单位间距
|
||||
const topmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[axis] < minNode.pos[axis] ? node : minNode
|
||||
, nodes[0]);
|
||||
let current = topmostNode.pos[axis];
|
||||
|
||||
// 按原始顺序排序(保持分布前的上下先后顺序)
|
||||
const sortedNodes = [...nodes].sort((a, b) => a.pos[axis] - b.pos[axis]);
|
||||
|
||||
for (let i = 1; i < sortedNodes.length; i++) {
|
||||
current += sortedNodes[i - 1].size[axis] + 30 + 20;
|
||||
sortedNodes[i].pos[axis] = current;
|
||||
}
|
||||
} else {
|
||||
const min = Math.min(...nodes.map(n => n.pos[axis])),
|
||||
max = Math.max(...nodes.map(n => n.pos[axis] + n.size[axis])),
|
||||
total = nodes.reduce((sum, n) => sum + n.size[axis], 0),
|
||||
spacing = (max - min - total) / (nodes.length - 1);
|
||||
let current = min;
|
||||
nodes.forEach(n => { n.pos[axis] = current; current += n.size[axis] + spacing; });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1940,6 +2522,17 @@
|
||||
window.containerController && (window.containerController.reset(), window.containerController.zoomToScale(1.0, window.innerWidth / 2, window.innerHeight / 2));
|
||||
const container = document.getElementById('hNodeAlignKit'); container && (container.style.display = 'block'); window.__hColor_Module && window.__hColor_Module.reset();
|
||||
__hMgr_ACbar.setLinkMode(0); window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.reset(); __hrReset__hMenu_Selections();
|
||||
try {
|
||||
// 清除与 NodeAlignPro 相关的本地存储项,确保完全回到默认状态
|
||||
const keysToClear = ['NodeAlignPro_ShowOperationLog', 'NodeAlignPro_WorkMode', 'NodeAlignPro_AlignButtonColor', 'NodeAlignPro_ToolbarBgColor', 'NodeAlignPro_ToolbarOpacity', 'NodeAlignPro_NewVersionTips', 'NodeAlignPro_LinkMode', 'NodeAlignProPosition', 'NodeAlignProRunButtonLink', 'NodeAlignProDisplayMode', 'NodeAlignPro_ColorApplyMode', 'hNodeAlignPro_Logic'];
|
||||
keysToClear.forEach(k => localStorage.removeItem(k));
|
||||
// 强制恢复默认对齐逻辑与UI
|
||||
if (typeof __hSetAlignLogicMode === 'function') __hSetAlignLogicMode('align');
|
||||
// 让设置管理器重新加载(若存在)以同步状态
|
||||
if (window.NodeAlignProSettingsManager && typeof window.NodeAlignProSettingsManager.loadSettingsFromStorage === 'function') {
|
||||
window.NodeAlignProSettingsManager.loadSettingsFromStorage();
|
||||
}
|
||||
} catch (e) { console.warn('重置时清理本地存储失败:', e); }
|
||||
window.containerController && window.containerController.updateTransform(); window.__hMgr_MenuHide ? window.__hMgr_MenuHide.hideMenu() : (() => { const menuContainer = document.getElementById('h6__hMenu'); menuContainer && (menuContainer.style.display = 'none'); })();
|
||||
hLog.log('<font color=#70A3F3>NodeAlignPro 已完全重置为默认状态</font>');
|
||||
}
|
||||
@@ -1966,7 +2559,7 @@
|
||||
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"]')
|
||||
'hApBar2_Node2': document.querySelector('[data-value="hApBar2_Node2"]'), 'hApBar3_StdH': document.querySelector('[data-value="hApBar3_StdH"]'), 'hApBar4_ProH': document.querySelector('[data-value="hApBar4_ProH"]')
|
||||
};
|
||||
resetMultiOptionDropdown(modeBtn, modeOptions, 'hApBar2_Align', h_i18n('hSelKit_AlignBar','对 齐'));
|
||||
// resetMultiOptionDropdown(modeBtn, modeOptions, 'hApBar2_Align', '对 齐');h_i18n('hSelKit_DragSplit','解 耦')
|
||||
@@ -2041,8 +2634,8 @@
|
||||
const menuBtn = document.querySelector('[data-target="hCMP-hSel__display-options"]');
|
||||
try {
|
||||
if (window.hLanguage && typeof window.hLanguage.t === 'function') {
|
||||
const alwaysText = window.hLanguage.t('Option_Display_Always') || '常驻显示';
|
||||
const followText = window.hLanguage.t('Option_Display_Follow') || '跟随选框';
|
||||
const alwaysText = window.hLanguage.t('hSelKit_Always') || '常驻显示';
|
||||
const followText = window.hLanguage.t('hSelKit_Follow') || '跟随选框';
|
||||
menuBtn && (menuBtn.textContent = this.isPermanent ? alwaysText : followText);
|
||||
} else {
|
||||
menuBtn && (menuBtn.textContent = this.isPermanent ? '常驻显示' : '跟随选框');
|
||||
@@ -2068,6 +2661,16 @@
|
||||
window.containerController && targetScale && window.containerController.zoomToScale(targetScale, centerX, centerY); break;
|
||||
case 'hDispMode0_Always': window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setPermanentMode(); break;
|
||||
case 'hDispMode1_Follow': window.__hMgr_DisplayMode && window.__hMgr_DisplayMode.setFollowingMode(); break;
|
||||
// 新增:通过工作模式下拉显式切换对齐逻辑(优先于自动检测)
|
||||
case 'hApBar2_Node2': __hSetAlignLogicMode('node2'); console.log('工作模式: Node2.0 对齐逻辑已启用'); break;
|
||||
case 'hApBar2_Align': __hSetAlignLogicMode('align'); console.log('工作模式: 原始对齐逻辑已启用'); break;
|
||||
case 'hApBar1_Color': // 仅显示色卡面板
|
||||
try { __hSync_WorkModeUI('hApBar1_Color'); console.log('工作模式: 色卡 (只显示色卡面板)'); } catch (e) { }
|
||||
break;
|
||||
case 'hApBar0_AlignAuto': // 自动(保留未来扩展)
|
||||
case 'hAlign_Auto':
|
||||
try { __hSync_WorkModeUI('auto'); console.log('工作模式: 自动 (Auto)'); } catch (e) { }
|
||||
break;
|
||||
}
|
||||
};
|
||||
window.__hMenu_Selection = __hMenu_Selection; // 暴露到全局
|
||||
@@ -2101,6 +2704,20 @@
|
||||
setTimeout(() => {
|
||||
hLog.debug('NodeAlignPro核心组件初始化完毕! 请等待其它插件加载...</br>🔥v2.0.3_rc新版教程文档请点击:右键菜单>【使用教程】查看...');
|
||||
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.NodeAlignProSettingsManager = new __hMgr_Settings(); // 初始化设置管理器
|
||||
// 尝试同步工作模式UI(优先使用设置管理器加载结果,兼容旧存储键)
|
||||
try {
|
||||
const storedWorkMode = localStorage.getItem('NodeAlignPro_WorkMode');
|
||||
if (storedWorkMode && window.NodeAlignProSettingsManager && typeof window.NodeAlignProSettingsManager.setWorkMode === 'function') {
|
||||
window.NodeAlignProSettingsManager.setWorkMode(storedWorkMode);
|
||||
} else {
|
||||
const legacy = localStorage.getItem('hNodeAlignPro_Logic');
|
||||
if (legacy && typeof __hSync_WorkModeUI === 'function') {
|
||||
if (legacy === 'node2') __hSync_WorkModeUI('hApBar2_Node2');
|
||||
else if (legacy === 'color') __hSync_WorkModeUI('hApBar1_Color');
|
||||
else __hSync_WorkModeUI('hApBar2_Align');
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
window.__hMenu_Selection = __hMenu_Selection; window.__hMgr_ACbar = __hMgr_ACbar; // 确保关键函数暴露
|
||||
window.__hMgr_DisplayMode = new __hMgr_DisplayMode(); const savedDisplayMode = localStorage.getItem('NodeAlignProDisplayMode'); savedDisplayMode === 'following' ? (window.__hMgr_DisplayMode.setFollowingMode(), hLog.info('显示模式: 跟随选框')) : (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);
|
||||
@@ -2144,6 +2761,17 @@
|
||||
if (linkMode !== null) { this.setLinkMode(parseInt(linkMode)); hLog.debug('--@hSetting', `从存储加载拖拽方式: ${parseInt(linkMode)}`); } //拖拽模式
|
||||
const displayMode = localStorage.getItem('NodeAlignProDisplayMode'); if (displayMode !== null) { this.setDisplayMode(displayMode === 'permanent' ? 'hDispMode0_Always' : 'hDispMode1_Follow'); } // 显示模式
|
||||
const colorMode = localStorage.getItem('NodeAlignPro_ColorApplyMode'); if (colorMode !== null) this.setColorApplyMode(parseInt(colorMode)); // 上色模式
|
||||
// 加载工作模式(优先使用新的设置键,其次兼容旧的对齐逻辑键)
|
||||
const storedWorkMode = localStorage.getItem('NodeAlignPro_WorkMode');
|
||||
if (storedWorkMode !== null) { this.setWorkMode(storedWorkMode); }
|
||||
else {
|
||||
const legacyLogic = localStorage.getItem('hNodeAlignPro_Logic');
|
||||
if (legacyLogic !== null) {
|
||||
if (legacyLogic === 'node2') this.setWorkMode('hApBar2_Node2');
|
||||
else if (legacyLogic === 'color') this.setWorkMode('hApBar1_Color');
|
||||
else this.setWorkMode('hApBar2_Align');
|
||||
}
|
||||
}
|
||||
const newVersionTips = localStorage.getItem('NodeAlignPro_NewVersionTips'); if (newVersionTips !== null) this.setNewVersionTips(newVersionTips === 'true'); // 新版说明
|
||||
// 颜色设置
|
||||
const alignColor = localStorage.getItem('NodeAlignPro_AlignButtonColor'); if (alignColor) this.setAlignButtonColor(alignColor);
|
||||
@@ -2203,6 +2831,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 设置工作模式(来自设置面板或本地存储)
|
||||
setWorkMode(value) {
|
||||
try {
|
||||
// value 可能为设置面板的值(如'hApBar1_Color','hApBar2_Align','hApBar2_Node2','hAlign_Auto')
|
||||
if (typeof __hMenu_Selection === 'function') {
|
||||
try { __hMenu_Selection(value); localStorage.setItem('NodeAlignPro_WorkMode', value); return; } catch (e) { console.warn('调用 __hMenu_Selection 失败:', e); }
|
||||
}
|
||||
// 回退:直接调用对齐逻辑/UI同步函数
|
||||
if (typeof __hSetAlignLogicMode === 'function') {
|
||||
if (value === 'hApBar2_Node2') { __hSetAlignLogicMode('node2'); }
|
||||
else if (value === 'hApBar1_Color') { typeof __hSync_WorkModeUI === 'function' ? __hSync_WorkModeUI('hApBar1_Color') : localStorage.setItem('hNodeAlignPro_Logic', 'color'); }
|
||||
else if (value === 'hAlign_Auto' || value === 'hApBar0_AlignAuto') { typeof __hSync_WorkModeUI === 'function' ? __hSync_WorkModeUI('auto') : localStorage.setItem('hNodeAlignPro_Logic', 'auto'); }
|
||||
else { __hSetAlignLogicMode('align'); }
|
||||
}
|
||||
localStorage.setItem('NodeAlignPro_WorkMode', value);
|
||||
hLog && hLog.info('--@hSetting', `工作模式已设置为: ${value}`);
|
||||
} catch (error) { hLog && hLog.error('--@hSetting', '设置工作模式失败:', error); }
|
||||
}
|
||||
|
||||
// 设置上色模式
|
||||
setColorApplyMode(mode) {
|
||||
if (window.__hMgr_ColorModeFc) {
|
||||
|
||||
@@ -69,7 +69,7 @@ const NodeAlignProSettings = [
|
||||
|
||||
{
|
||||
id: "hNodeAlignPro.linkMode", name: h_i18n('Setting_DragMode','拖拽方式'), type: "combo",
|
||||
options: [{ value: "hDragMode1_Split", text: h_i18n('Option_Drag_Split','解 耦') }, { value: "hDragMode0_Link", text: h_i18n('Option_Drag_Link','联 动') }],
|
||||
options: [{ value: "hDragMode1_Split", text: h_i18n('hSelKit_DragSplit2','解 耦') }, { value: "hDragMode0_Link", text: h_i18n('hSelKit_DragLink2','联 动') }],
|
||||
defaultValue: "hDragMode1_Split",
|
||||
category: ["🔥 NodeAlignPro", "NodeAlignPro基本设置 (Basic Settings)", h_i18n('Setting_DragMode','拖拽方式')],
|
||||
tooltip: h_i18n('Setting_DragMode','切换是否联动[运行/Action]按钮到插件面板(与插件右键菜单设置同步)'),
|
||||
@@ -125,16 +125,21 @@ const NodeAlignProSettings = [
|
||||
}, */
|
||||
{
|
||||
id: "hNodeAlignPro.WorkMode", name: h_i18n('Setting_WorkMode','工作模式'), type: "combo",
|
||||
options: [{ value: "hApBar2_Align", text: h_i18n('hSelKit_AlignBar','对 齐(AlignKit)') }],
|
||||
options: [
|
||||
// { value: 'hAlign_Auto', text: h_i18n('hSelKit_AlignAuto2','自动(Auto)') },
|
||||
// { value: "hApBar1_Color", text: h_i18n('hSelKit_ColorBar2', '色卡(ColorBar)') },
|
||||
{ value: "hApBar2_Align", text: h_i18n('hSelKit_AlignBar2', '传统对齐(AlignStd)') },
|
||||
{ value: "hApBar2_Node2", text: h_i18n('hSelKit_Node2', 'Node2.0') }
|
||||
],
|
||||
defaultValue: "hApBar2_Align",
|
||||
category: ["🔥 NodeAlignPro", "NodeAlignPro基本设置 (Basic Settings)", h_i18n('Setting_WorkMode','工作模式')],
|
||||
tooltip: h_i18n('Setting_WorkMode','切换插件工作模式(与插件右键菜单设置同步)'),
|
||||
tooltip: h_i18n('hTooltip_WorkMode','工作模式 (Work Mode):切换插件工作模式(与插件右键菜单设置同步)。可开启新版Node2.0对齐模式'),
|
||||
onChange: (value) => { try { if (window.NodeAlignProSettingsManager && typeof window.NodeAlignProSettingsManager.setWorkMode === 'function') { window.NodeAlignProSettingsManager.setWorkMode(value); } else __hNodeAlignPro_safeCall(null, null, 'workMode', value); } catch (error) { console.error('设置工作模式失败:', error); } }
|
||||
},
|
||||
|
||||
{
|
||||
id: "hNodeAlignPro.DisplayMode", name: h_i18n('Setting_DisplayMode','显示模式'), type: "combo",
|
||||
options: [{ value: "hDispMode0_Always", text: h_i18n('Option_Display_Always','常驻显示') }, { value: "hDispMode1_Follow", text: h_i18n('Option_Display_Follow','跟随选框') }],
|
||||
options: [{ value: "hDispMode0_Always", text: h_i18n('hSelKit_Always2','常驻显示') }, { value: "hDispMode1_Follow", text: h_i18n('hSelKit_Follow2','跟随选框') }],
|
||||
defaultValue: "hDispMode0_Always",
|
||||
category: ["🔥 NodeAlignPro", "NodeAlignPro基本设置 (Basic Settings)", h_i18n('Setting_DisplayMode','显示模式')],
|
||||
tooltip: h_i18n('Setting_DisplayMode','切换插件面板的显示模式(与插件右键菜单设置同步)'),
|
||||
@@ -145,7 +150,7 @@ const NodeAlignProSettings = [
|
||||
{
|
||||
id: "hNodeAlignPro.Language", name: h_i18n('Setting_Language','语言'), type: "combo",
|
||||
options: [
|
||||
{ value: 'auto', text: h_i18n('Option_Lang_Auto','自动(Auto)') },
|
||||
{ value: 'auto', text: h_i18n('hSelKit_AlignAuto','自动(Auto)') },
|
||||
{ value: 'cn', text: h_i18n('Option_Lang_CN','中文') },
|
||||
{ value: 'en', text: h_i18n('Option_Lang_EN','English') }
|
||||
],
|
||||
@@ -204,7 +209,7 @@ const NodeAlignProSettings = [
|
||||
|
||||
{
|
||||
id: "hNodeAlignPro.ColorApplyMode", name: h_i18n('Setting_ColorApplyMode','上色模式'), type: "combo",
|
||||
options: [ { value: "1", text: h_i18n('Option_Color_Whole','整体色') }, { value: "0", text: h_i18n('Option_Color_TitleOnly','仅标题') } ],
|
||||
options: [ { value: "1", text: h_i18n('Option_Color_Whole2','整体色') }, { value: "0", text: h_i18n('Option_Color_TitleOnly2','仅标题') } ],
|
||||
defaultValue: "1",
|
||||
category: ["🔥 NodeAlignPro", "NodeAlignPro节点设置 (Node Settings)", h_i18n('Setting_ColorApplyMode','上色模式')],
|
||||
tooltip: h_i18n('Setting_ColorApplyMode','设置节点上色模式:整体色(背景+标题)或仅标题色'),
|
||||
@@ -264,7 +269,182 @@ function resetNodeAlignProManually() {
|
||||
// 清除所有本地存储
|
||||
function clearAllStorage() {
|
||||
try {
|
||||
const storageKeys = ['NodeAlignPro_ShowOperationLog', 'NodeAlignPro_WorkMode', 'NodeAlignPro_AlignButtonColor', 'NodeAlignPro_ToolbarBgColor', 'NodeAlignPro_ToolbarOpacity', 'NodeAlignPro_NewVersionTips', 'NodeAlignPro_LinkMode', 'NodeAlignProPosition', 'NodeAlignProRunButtonLink', 'NodeAlignProDisplayMode', 'NodeAlignPro_ColorApplyMode'];
|
||||
const storageKeys = ['NodeAlignPro_ShowOperationLog', 'NodeAlignPro_WorkMode', 'NodeAlignPro_AlignButtonColor', 'NodeAlignPro_ToolbarBgColor', 'NodeAlignPro_ToolbarOpacity', 'NodeAlignPro_NewVersionTips', 'NodeAlignPro_LinkMode', 'NodeAlignProPosition', 'NodeAlignProRunButtonLink', 'NodeAlignProDisplayMode', 'NodeAlignPro_ColorApplyMode', 'hNodeAlignPro_Logic'];
|
||||
storageKeys.forEach(key => { localStorage.removeItem(key); }); console.log('所有相关localStorage项已清除');
|
||||
} catch (error) { console.error('清除localStorage失败:', error); }
|
||||
}
|
||||
}
|
||||
|
||||
// 【== 节点对齐工具 ==】
|
||||
const __hNAP_AlignTools = {
|
||||
// 左对齐(X轴)
|
||||
hBtnA_alignLeft(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最右侧节点的左边缘,否则使用最左侧节点的左边缘
|
||||
const refNode = isAlt ? nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.x + __hNode2_getNodeSize(node).w) > (maxPos.x + __hNode2_getNodeSize(maxNode).w) ? node : maxNode;
|
||||
}, nodes[0]) : nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.x < minPos.x ? node : minNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refX = __hNode2_getNodePos(refNode).x;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: refX, y: __hNode2_getNodePos(node).y },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最右侧节点的左边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最右侧节点
|
||||
const rightmostNode = nodes.reduce((maxNode, node) =>
|
||||
(node.pos[0] + node.size[0]) > (maxNode.pos[0] + maxNode.size[0]) ? node : maxNode
|
||||
, nodes[0]);
|
||||
return rightmostNode.pos[0];
|
||||
}
|
||||
return Math.min(...nodes.map(n => n.pos[0]));
|
||||
}, (n, ref) => n.pos[0] = ref);
|
||||
},
|
||||
|
||||
// 右对齐(X轴)
|
||||
hBtnC_alignRight(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最左侧节点的右边缘,否则使用最右侧节点的右边缘
|
||||
const refNode = isAlt ? nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.x < minPos.x ? node : minNode;
|
||||
}, nodes[0]) : nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.x + __hNode2_getNodeSize(node).w) > (maxPos.x + __hNode2_getNodeSize(maxNode).w) ? node : maxNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refX = __hNode2_getNodePos(refNode).x + __hNode2_getNodeSize(refNode).w;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: refX - __hNode2_getNodeSize(node).w, y: __hNode2_getNodePos(node).y },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最左侧节点的右边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 0, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最左侧节点
|
||||
const leftmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[0] < minNode.pos[0] ? node : minNode
|
||||
, nodes[0]);
|
||||
return leftmostNode.pos[0] + leftmostNode.size[0];
|
||||
}
|
||||
return Math.max(...nodes.map(n => n.pos[0] + n.size[0]));
|
||||
}, (n, ref) => n.pos[0] = ref - n.size[0]);
|
||||
},
|
||||
|
||||
// 顶对齐(Y轴)
|
||||
hBtnD_alignTop(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最底部节点的上边缘,否则使用最顶部节点的上边缘
|
||||
const refNode = isAlt ? nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.y + __hNode2_getNodeSize(node).h) > (maxPos.y + __hNode2_getNodeSize(maxNode).h) ? node : maxNode;
|
||||
}, nodes[0]) : nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.y < minPos.y ? node : minNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refY = __hNode2_getNodePos(refNode).y;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: __hNode2_getNodePos(node).x, y: refY },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最底部节点的上边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最底部节点
|
||||
const bottommostNode = nodes.reduce((maxNode, node) =>
|
||||
(node.pos[1] + node.size[1]) > (maxNode.pos[1] + maxNode.size[1]) ? node : maxNode
|
||||
, nodes[0]);
|
||||
return bottommostNode.pos[1];
|
||||
}
|
||||
return Math.min(...nodes.map(n => n.pos[1]));
|
||||
}, (n, ref) => n.pos[1] = ref);
|
||||
},
|
||||
|
||||
// 底对齐(Y轴)
|
||||
hBtnF_alignButton(e) {
|
||||
if (__hIsNode2Mode()) {
|
||||
const nodes = __hNode2_getSelectedNodes();
|
||||
if (nodes && nodes.length) {
|
||||
const isAlt = !!(e && e.altKey);
|
||||
// 找到基准节点:Alt+点击时使用最顶部节点的下边缘,否则使用最底部节点的下边缘
|
||||
const refNode = isAlt ? nodes.reduce((minNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const minPos = __hNode2_getNodePos(minNode);
|
||||
return pos.y < minPos.y ? node : minNode;
|
||||
}, nodes[0]) : nodes.reduce((maxNode, node) => {
|
||||
const pos = __hNode2_getNodePos(node);
|
||||
const maxPos = __hNode2_getNodePos(maxNode);
|
||||
return (pos.y + __hNode2_getNodeSize(node).h) > (maxPos.y + __hNode2_getNodeSize(maxNode).h) ? node : maxNode;
|
||||
}, nodes[0]);
|
||||
|
||||
const refY = __hNode2_getNodePos(refNode).y + __hNode2_getNodeSize(refNode).h;
|
||||
const result = nodes.map(node => ({
|
||||
node: node,
|
||||
newPos: { x: __hNode2_getNodePos(node).x, y: refY - __hNode2_getNodeSize(node).h },
|
||||
newSize: __hNode2_getNodeSize(node)
|
||||
}));
|
||||
|
||||
if (window.app && window.app.canvas && typeof window.app.canvas.repositionNodesVueMode === 'function') {
|
||||
window.app.canvas.repositionNodesVueMode(result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 传统模式下的修复:确保Alt+点击时使用最顶部节点的下边缘
|
||||
__hNAP_AlignFc.handleAlign(e, 1, (nodes, isAlt) => {
|
||||
if (isAlt) {
|
||||
// 找到最顶部节点
|
||||
const topmostNode = nodes.reduce((minNode, node) =>
|
||||
node.pos[1] < minNode.pos[1] ? node : minNode
|
||||
, nodes[0]);
|
||||
return topmostNode.pos[1] + topmostNode.size[1];
|
||||
}
|
||||
return Math.max(...nodes.map(n => n.pos[1] + n.size[1]));
|
||||
}, (n, ref) => n.pos[1] = ref - n.size[1]);
|
||||
}
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 439 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
Reference in New Issue
Block a user