diff --git a/CHANGELOG.md b/CHANGELOG.md index 56fce3d..db7ad17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ - Fixed the GuLuLu image failing to load when installed via ComfyUI Manager - 修复节点排版工具栏在新版前端下完全消失、快捷键同时失效的问题 - Fixed the node align toolbar disappearing entirely on the current frontend, which also disabled its shortcuts -- 工具栏改为独立浮窗,自带拖拽手柄,可拖到顶栏任意位置吸附,不再依附于官方「运行」按钮 -- The toolbar is now an independent panel with its own drag handle; drop it anywhere along the top bar to dock, no longer tied to the official Run button +- 工具栏改为完全自由的浮窗,自带拖拽手柄,拖到哪就停在哪,不再依附于官方「运行」按钮 +- The toolbar is now a freely floating panel with its own drag handle; it stays wherever you drop it, no longer tied to the official Run button ## [0.70.12] - 2025-05-15 - 将Shift+R快捷键功能改为Alt+R,解决了输入大写的“R”冲突 diff --git a/README.md b/README.md index 1f69afc..16f472a 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ This is a custom node utility package developed for ComfyUI. Initially created f - Fixed the GuLuLu image failing to load when installed via ComfyUI Manager - 修复节点排版工具栏在新版前端下完全消失、快捷键同时失效的问题 - Fixed the node align toolbar disappearing entirely on the current frontend, which also disabled its shortcuts -- 工具栏改为独立浮窗,自带拖拽手柄,可拖到顶栏任意位置吸附,不再依附于官方「运行」按钮 -- The toolbar is now an independent panel with its own drag handle; drop it anywhere along the top bar to dock, no longer tied to the official Run button +- 工具栏改为完全自由的浮窗,自带拖拽手柄,拖到哪就停在哪,不再依附于官方「运行」按钮 +- The toolbar is now a freely floating panel with its own drag handle; it stays wherever you drop it, no longer tied to the official Run button ### [0.70.12] - 2025-05-15 - 将Shift+R快捷键功能改为Alt+R,解决了输入大写的“R”冲突 diff --git a/web/nodealign.js b/web/nodealign.js index bd11c18..84869c6 100644 --- a/web/nodealign.js +++ b/web/nodealign.js @@ -16,19 +16,17 @@ const kayVerticalDistributionSvg = ` this.updatePosition()); const displayMode = app.ui.settings.getSettingValue("KayTool.NodeAlignDisplayMode"); this.updateDisplayMode(displayMode); @@ -63,43 +61,6 @@ const KayNodeAlignmentManager = { }); }, - // 顶栏所在的那条横带,只用来算几何,取不到就退回一个合理的默认值。 - getDockBand() { - const probe = document.querySelector('[data-testid="action-bar-card"]') - || document.querySelector('[data-testid="top-menu-actionbars"]') - || app.menu?.element - || null; - const rect = probe?.getBoundingClientRect(); - if (rect && rect.height > 0) { - return { top: rect.top, bottom: rect.bottom, height: rect.height, probe }; - } - const tabs = document.querySelector('[data-testid="topbar-workflow-tabs"]'); - const top = tabs ? tabs.getBoundingClientRect().bottom + 4 : 40; - return { top, bottom: top + 48, height: 48, probe: null }; - }, - - // 吸附时横向避让「运行」那张卡片,免得盖住官方按钮。 - // 让位的结果必须还在视口里,否则就换另一边;两边都塞不下才作罢。 - avoidActionBar(left, width, viewportWidth) { - const card = document.querySelector('[data-testid="action-bar-card"]'); - const rect = card?.getBoundingClientRect(); - if (!rect || rect.width === 0) return left; - if (left >= rect.right || left + width <= rect.left) return left; - - const gap = 8; - const leftSlot = rect.left - width - gap; - const rightSlot = rect.right + gap; - const leftFits = leftSlot >= 0; - const rightFits = rightSlot + width <= viewportWidth; - const preferLeft = (left + width / 2) < (rect.left + rect.width / 2); - - if (preferLeft && leftFits) return leftSlot; - if (!preferLeft && rightFits) return rightSlot; - if (leftFits) return leftSlot; - if (rightFits) return rightSlot; - return left; - }, - injectStyles() { if (stylesInjected) return; const bgColor = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundColor"); @@ -119,7 +80,6 @@ const KayNodeAlignmentManager = { pointer-events: auto; } #kay-node-alignment-toolbar.floating { position: fixed; } - #kay-node-alignment-toolbar.docked { position: fixed; } .kay-align-button { width: 25px; height: 25px; @@ -167,17 +127,6 @@ const KayNodeAlignmentManager = { white-space: nowrap; font-size: 12px; } - #kay-dock-guide { - position: fixed; - left: 0; - width: 100%; - border-top: 2px dashed #d0ff00; - border-bottom: 2px dashed #d0ff00; - background: rgba(208, 255, 0, 0.06); - z-index: 9999; - pointer-events: none; - display: none; - } `); stylesInjected = true; }, @@ -191,9 +140,7 @@ const KayNodeAlignmentManager = { } this.position = { leftPercentage: saved.leftPercentage ?? 50, - topPercentage: saved.topPercentage ?? 5, - // isAttached 是旧版插进顶栏 DOM 的标记,沿用为位置吸附。 - docked: saved.docked ?? saved.isAttached ?? false + topPercentage: saved.topPercentage ?? 5 }; }, @@ -210,17 +157,12 @@ const KayNodeAlignmentManager = { this.toolbarContainer = document.createElement('div'); this.toolbarContainer.id = 'kay-node-alignment-toolbar'; - this.toolbarContainer.classList.add(this.position.docked ? 'docked' : 'floating'); + this.toolbarContainer.classList.add('floating'); if (opacity > 0 && /^[0-9A-Fa-f]{6}$/.test(bgColor)) { this.toolbarContainer.style.background = `rgba(${parseInt(bgColor.substr(0, 2), 16)}, ${parseInt(bgColor.substr(2, 2), 16)}, ${parseInt(bgColor.substr(4, 2), 16)}, ${opacity})`; } document.body.appendChild(this.toolbarContainer); - // 吸附提示条:拖到顶栏那一行时亮起,全程只动我们自己的元素。 - this.dockGuide = document.createElement('div'); - this.dockGuide.id = 'kay-dock-guide'; - document.body.appendChild(this.dockGuide); - this.getAlignmentButtons().forEach(btn => { const el = document.createElement(btn.type === 'divider' ? 'div' : 'button'); el.className = btn.type === 'divider' ? 'kay-toolbar-divider' : 'kay-align-button'; @@ -317,10 +259,6 @@ const KayNodeAlignmentManager = { updatePosition() { if (!this.toolbarContainer || !this.isVisible) return; - if (this.position.docked) { - this.applyDockedPosition(); - return; - } const { windowRect, toolbarRect } = this.getRect(); let left = (this.position.leftPercentage / 100) * windowRect.width - toolbarRect.width / 2; let top = (this.position.topPercentage / 100) * windowRect.height; @@ -330,20 +268,6 @@ const KayNodeAlignmentManager = { this.toolbarContainer.style.top = `${top}px`; }, - // 吸附态:纵向对齐到顶栏中线,横向保留用户拖到的位置(避让运行卡片)。 - applyDockedPosition() { - if (!this.toolbarContainer) return; - const { windowRect, toolbarRect } = this.getRect(); - const band = this.getDockBand(); - let left = (this.position.leftPercentage / 100) * windowRect.width - toolbarRect.width / 2; - left = Math.max(0, Math.min(left, windowRect.width - toolbarRect.width)); - // 避让本身已经考虑了视口边界,这里不能再夹一次,否则会把它推回重叠区。 - left = this.avoidActionBar(left, toolbarRect.width, windowRect.width); - const top = band.top + (band.height - toolbarRect.height) / 2; - this.toolbarContainer.style.left = `${left}px`; - this.toolbarContainer.style.top = `${Math.max(0, top)}px`; - }, - getRect() { return { windowRect: { width: window.innerWidth, height: window.innerHeight }, @@ -351,22 +275,6 @@ const KayNodeAlignmentManager = { }; }, - // 工具栏竖直方向是否压在顶栏那条横带上 - isOverBand(toolbarRect, band) { - return toolbarRect.top < band.bottom && toolbarRect.top + toolbarRect.height > band.top; - }, - - showDockGuide(band) { - if (!this.dockGuide) return; - this.dockGuide.style.top = `${band.top}px`; - this.dockGuide.style.height = `${band.height}px`; - this.dockGuide.style.display = 'block'; - }, - - hideDockGuide() { - if (this.dockGuide) this.dockGuide.style.display = 'none'; - }, - onDragStart(e) { e.preventDefault(); const { toolbarRect } = this.getRect(); @@ -375,12 +283,10 @@ const KayNodeAlignmentManager = { offsetX: e.clientX - toolbarRect.left, offsetY: e.clientY - toolbarRect.top }; - this.setDocked(false); }, onDragging(e) { if (!this.dragState.isDragging) return; - const { windowRect, toolbarRect } = this.getRect(); let left = e.clientX - this.dragState.offsetX; let top = e.clientY - this.dragState.offsetY; @@ -388,59 +294,22 @@ const KayNodeAlignmentManager = { top = Math.max(0, Math.min(top, windowRect.height - toolbarRect.height)); this.toolbarContainer.style.left = `${left}px`; this.toolbarContainer.style.top = `${top}px`; - - const band = this.getDockBand(); - if (this.isOverBand({ ...toolbarRect.toJSON(), top }, band)) this.showDockGuide(band); - else this.hideDockGuide(); }, onDragEnd() { if (!this.dragState.isDragging) return; this.dragState = { isDragging: false, offsetX: 0, offsetY: 0 }; - this.hideDockGuide(); const { windowRect, toolbarRect } = this.getRect(); this.position.leftPercentage = ((toolbarRect.left + toolbarRect.width / 2) / windowRect.width) * 100; this.position.topPercentage = (toolbarRect.top / windowRect.height) * 100; - - const band = this.getDockBand(); - if (this.isOverBand(toolbarRect, band)) { - this.setDocked(true); - this.applyDockedPosition(); - } else { - this.setDocked(false); - } this.savePosition(); }, - setDocked(docked) { - this.position.docked = docked; - if (!this.toolbarContainer) return; - this.toolbarContainer.classList.toggle('docked', docked); - this.toolbarContainer.classList.toggle('floating', !docked); - }, - restorePosition() { - this.setDocked(this.position.docked); this.updatePosition(); }, - // 顶栏高度会随工作流标签栏出现/消失而变,吸附时跟着重新对齐。 - // 用事件驱动,不做轮询。 - bindBandTracking() { - const realign = () => { - if (this.position.docked && this.isVisible && !this.dragState.isDragging) { - this.applyDockedPosition(); - } - }; - window.addEventListener('resize', realign); - if (typeof ResizeObserver !== 'undefined') { - this.bandObserver = new ResizeObserver(realign); - const probe = this.getDockBand().probe; - if (probe) this.bandObserver.observe(probe); - } - }, - getSelectedNodes() { const nodes = Object.values(app.canvas.selected_nodes || {}); return nodes;