212 lines
12 KiB
JavaScript
212 lines
12 KiB
JavaScript
/**
|
||
* @Artstich_Example
|
||
* @name easykit-node-align (ComfyUI Plugin)
|
||
* @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.
|
||
* @author ArtsticH
|
||
* @see https://registry.comfy.org/zh/nodes/easykit-node-align
|
||
* @see https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro
|
||
* @see https://gitee.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro
|
||
* @installCommand comfy node install easykit-node-align
|
||
* @installCommand git clone https://github.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro.git
|
||
* @installCommand git clone https://gitee.com/ArtsticH/ComfyUI_EasyKitHT_NodeAlignPro.git
|
||
* @created 2025-04-29 @date 2025-06-15 @version v2.0.3 @lastUpdated 2026-01-24 @license GPL-3.0
|
||
* @copyright ©2012-2026, All rights reserved. Freely open to use, modify, and distribute in accordance with the GPL-3.0 license.
|
||
*/
|
||
|
||
// 本插件全局默认官方语言为中文(cn)。国际友人可自行切换英文,支持'auto'以跟随浏览器/ComfyUI的语言设置
|
||
// This plugin uses Chinese (cn) as the global default language. International users can switch to English. The 'auto' setting is also supported to follow the browser or ComfyUI's language preference.
|
||
|
||
// NodeAlignPro 全局国际化配置
|
||
// 在 `window.hLanguage` 对象上提供 `t(key)` 辅助函数和 `lang` 属性(支持getter/setter)
|
||
(function(){
|
||
'use strict';
|
||
const data = {
|
||
// 格式:{ cn: '中文文本', en: 'English Text' }
|
||
// 保持单行格式以便于快速编辑
|
||
NodeAlignPro_Title: {cn: 'Node Align Pro', en: 'Node Align Pro'},
|
||
// 头部/菜单/标签文字
|
||
Menu_LogoTitle: {cn: '菜单栏 LOGO', en: 'Menu Logo'},
|
||
Title_Search: {cn: '搜索节点 Github@ArtsticH...', en: 'Search nodes Github@ArtsticH...'},
|
||
Aria_ModeSwitch: {cn: '模式切换', en: 'Mode Switch'},
|
||
Aria_Menu: {cn: '菜单', en: 'Menu'},
|
||
Label_Align: {cn: '对齐:', en: 'Align:'},
|
||
Label_Distribute: {cn: '分布:', en: 'Distribute:'},
|
||
Label_Mode: {cn: '模式', en: 'Mode'},
|
||
Label_Size: {cn: '尺寸:', en: 'Size:'},
|
||
Label_Select: {cn: '选择:', en: 'Select:'},
|
||
Aria_SelectMode: {cn: '框选模式', en: 'Selection Mode'},
|
||
Aria_GroupMode: {cn: '群组模式', en: 'Group Mode'},
|
||
Aria_Separator: {cn: '分隔线', en: 'Separator'},
|
||
Aria_DragMove: {cn: '按住拖移位置', en: 'Hold to drag/move'},
|
||
ColorPicker_Title: {cn: 'hColorPicker™', en: 'hColorPicker™'},
|
||
ColorPicker_HexLabel: {cn: '十六进制:', en: 'Hex:'},
|
||
ColorPicker_RGBLabel: {cn: 'RGB:', en: 'RGB:'},
|
||
ColorPicker_NodeMode: {cn: '整体色', en: 'Whole Color'},
|
||
ColorPicker_NodeModeTip: {cn: '👆双击切换上色模式:', en: '👆Double-click to toggle color mode:'},
|
||
Panel_TestTitle: {cn: 'Node 2.0 Alignment Test', en: 'Node 2.0 Alignment Test'},
|
||
ColorPicker_HueLabel: {cn: '色相(H):', en: 'Hue (H):'},
|
||
ColorPicker_SatLabel: {cn: '饱和(S):', en: 'Sat (S):'},
|
||
ColorPicker_BriLabel: {cn: '亮度(B):', en: 'Bri (B):'},
|
||
Btn_LeftAlign: {cn: '左对齐', en: 'Left Align'},
|
||
Btn_RightAlign: {cn: '右对齐', en: 'Right Align'},
|
||
Btn_TopAlign: {cn: '顶对齐', en: 'Top Align'},
|
||
Btn_BottomAlign: {cn: '底对齐', en: 'Bottom Align'},
|
||
Btn_HCenter: {cn: '水平居中', en: 'H Center'},
|
||
Btn_VCenter: {cn: '垂直居中', en: 'V Center'},
|
||
Btn_DistH: {cn: '水平等距分布', en: 'Dist H'},
|
||
Btn_DistV: {cn: '垂直等距分布', en: 'Dist V'},
|
||
Btn_EqualWidth: {cn: '等宽', en: 'Equal Width'},
|
||
Btn_EqualHeight: {cn: '等高', en: 'Equal Height'},
|
||
Picker_ScreenPickUnsupported: {cn: '浏览器不支持屏幕取色功能,请使用最新版Chrome/Edge浏览器', en: 'Browser does not support EyeDropper API; use latest Chrome/Edge.'},
|
||
Pick_NoSelection: {cn: '未选中任何节点', en: 'No nodes selected'},
|
||
Setting_ShowOperationLog: {cn: '显示操作日志 (Show Operation Log)', en: 'Show Operation Log'},
|
||
Setting_ForceReset: {cn: '⚠强制重置NodeAlignPro插件 (Force reset NodeAlignPro plugin)', en: '⚠Force reset NodeAlignPro plugin'},
|
||
hSelKit_DragMode: {cn: '拖拽方式:', en: 'Drag Mode'},
|
||
hSelKit_UIscale: {cn: 'UI缩放:', en: 'UI Scale'},
|
||
hSelKit_WorkMode: {cn: '工作模式:', en: 'Work Mode'},
|
||
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):可开启新版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'},
|
||
Setting_ToolbarOpacity: {cn: '工具栏透明度 (Toolbar Opacity)', en: 'Toolbar Opacity'},
|
||
Setting_NewVersionTips: {cn: '新版说明 (New Version Tips)', en: 'New Version Tips'},
|
||
Setting_ColorApplyMode: {cn: '上色模式 (Color Apply Mode)', en: 'Color Apply Mode'},
|
||
Setting_Language: {cn: 'NodeAlignPro UI语言 (Language)', en: 'NodeAlignPro UI Language'},
|
||
// 选项标签文字
|
||
Option_Lang_CN: {cn: '中文', en: '中文'},
|
||
Option_Lang_EN: {cn: 'English', en: 'English'},
|
||
hSelKit_DragSplit: {cn: '解 耦', en: 'Split'},
|
||
hSelKit_DragSplit2: {cn: '解 耦(Split)', en: 'Split'},
|
||
hSelKit_DragLink: {cn: '联 动', 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_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'},
|
||
|
||
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'},
|
||
Aria_RandomColor: {cn: '随机颜色', en: 'Random Color'},
|
||
Aria_ScreenPick: {cn: '屏幕取色', en: 'Screen Pick'},
|
||
Aria_Prev: {cn: '上个', en: 'Previous'},
|
||
Aria_Next: {cn: '下个', en: 'Next'},
|
||
Select_SameColor: {cn: '相同颜色', en: 'Same Color'},
|
||
Select_SameName: {cn: '相同名称', en: 'Same Name'},
|
||
Select_SameSize: {cn: '相同尺寸', en: 'Same Size'},
|
||
Select_DefaultColor: {cn: '默认色', en: 'Default Color'},
|
||
Select_Colored: {cn: '已上色', en: 'Colored'},
|
||
Select_State: {cn: '选择状态', en: 'Select State'},
|
||
Tool_MagicWand: {cn: '魔棒', en: 'Magic Wand'},
|
||
Aria_Rename: {cn: '重命名', en: 'Rename'},
|
||
hMenu_ResetAll: {cn: '一键重置', en: 'Reset All'},
|
||
hMenu_BugReport: {cn: 'bug反馈', en: 'Bug2Issue'},
|
||
hMenu_Guide: {cn: '使用教程', en: 'Guide'},
|
||
hMenu_NewTips: {cn: '新版说明', en: 'New Tips'},
|
||
// 按需添加更多键值
|
||
};
|
||
|
||
const state = { lang: 'cn' };
|
||
|
||
// 从ComfyUI或浏览器检测实际使用的语言
|
||
function detectLang() {
|
||
try {
|
||
// 优先尝试ComfyUI应用级别的设置(如果可用)
|
||
if (window.app && window.app.ui && typeof window.app.ui.settings?.getSettingValue === 'function') {
|
||
const v = window.app.ui.settings.getSettingValue('language') || window.app.ui.settings.getSettingValue('lang');
|
||
if (v && typeof v === 'string') {
|
||
const lv = v.toLowerCase();
|
||
if (lv.startsWith('en')) return 'en';
|
||
if (lv.startsWith('zh')) return 'cn';
|
||
}
|
||
}
|
||
// 回退到浏览器语言设置
|
||
const nav = (navigator && (navigator.language || navigator.userLanguage || '')).toLowerCase();
|
||
if (nav.startsWith('en')) return 'en';
|
||
if (nav.startsWith('zh')) return 'cn';
|
||
} catch (e) { /* 忽略 */ }
|
||
return 'cn';
|
||
}
|
||
|
||
// 简单的翻译函数:t(key)
|
||
function t(key) {
|
||
if (!key) return '';
|
||
const entry = data[key];
|
||
if (!entry) return key;
|
||
const langToUse = (state.lang === 'auto') ? detectLang() : (state.lang || 'cn');
|
||
return entry[langToUse] || entry.cn || Object.values(entry)[0];
|
||
}
|
||
|
||
// 将API暴露在window对象上
|
||
window.hLanguage = {
|
||
t,
|
||
data,
|
||
getLang() { return state.lang; },
|
||
setLang(l) { state.lang = (l === 'auto') ? 'auto' : ((l === 'en') ? 'en' : 'cn'); return state.lang; },
|
||
detectLang,
|
||
// 通过键值(data-i18n属性)翻译DOM元素文本内容的辅助函数
|
||
applyToDOM(root = document) {
|
||
try {
|
||
// 翻译textContent / placeholder / value属性
|
||
// 跳过仅需翻译属性(data-i18n-attr)的元素
|
||
root.querySelectorAll('[data-i18n]').forEach(el => {
|
||
if (el.hasAttribute('data-i18n-attr')) return; // 保留SVG/图标内容不翻译
|
||
const key = el.getAttribute('data-i18n');
|
||
if (!key) return;
|
||
const text = t(key);
|
||
if (!text) return;
|
||
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
|
||
el.placeholder = text;
|
||
if (el.type === 'button' || el.type === 'submit') el.value = text;
|
||
} else {
|
||
el.textContent = text;
|
||
}
|
||
});
|
||
|
||
// 通过data-i18n-attr属性翻译aria-label、title等属性
|
||
root.querySelectorAll('[data-i18n][data-i18n-attr]').forEach(el => {
|
||
try {
|
||
const key = el.getAttribute('data-i18n');
|
||
const attrName = el.getAttribute('data-i18n-attr');
|
||
if (!key || !attrName) return;
|
||
const text = t(key);
|
||
if (!text) return;
|
||
el.setAttribute(attrName, text);
|
||
} catch (ee) { /* 忽略单个元素的翻译错误 */ }
|
||
});
|
||
} catch (e) { console.warn('hLanguage.applyToDOM failed:', e); }
|
||
}
|
||
};
|
||
|
||
// DOM内容加载完成后自动应用翻译,更新所有带data-i18n属性的元素
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.hLanguage.applyToDOM(document);
|
||
});
|
||
} else {
|
||
setTimeout(() => window.hLanguage.applyToDOM(document), 50);
|
||
}
|
||
})();
|