From affc001629017683be2296e85c073b7857087add Mon Sep 17 00:00:00 2001 From: kk8bit Date: Sun, 20 Sep 2026 17:57:25 +0800 Subject: [PATCH] Drop docking from the align toolbar; leave it freely floating Docking into the top bar meant either living inside the Run button's card and being dragged around with it, or snapping to a row whose geometry we have to infer from the frontend's markup. Neither is worth the coupling for a toolbar the user repositions often, so the toolbar now simply stays wherever it is dropped. Removes the band probing, the dock guide, the Run card avoidance and the resize tracking that existed only to keep a docked toolbar aligned. --- CHANGELOG.md | 4 +- README.md | 4 +- web/nodealign.js | 147 +++-------------------------------------------- 3 files changed, 12 insertions(+), 143 deletions(-) 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;