From 9b1a9ee071c5da363d6e82fb1949657a93fa01db Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Tue, 21 Oct 2025 11:54:50 +0800 Subject: [PATCH] feat: refactor LoRA manager widget into top menu extension - Rename ui_utils.js to top_menu_extension.js to better reflect functionality - Replace custom button creation with ComfyUI Button and ButtonGroup components - Implement proper top menu integration using ComfyUI's menu system - Simplify window opening logic with shift-click support for new windows - Add retry mechanism for attaching button to menu - Improve code organization and maintainability by leveraging existing UI components --- .../{ui_utils.js => top_menu_extension.js} | 197 +++++++----------- 1 file changed, 76 insertions(+), 121 deletions(-) rename web/comfyui/{ui_utils.js => top_menu_extension.js} (83%) diff --git a/web/comfyui/ui_utils.js b/web/comfyui/top_menu_extension.js similarity index 83% rename from web/comfyui/ui_utils.js rename to web/comfyui/top_menu_extension.js index 4996c67a..b1a88bc0 100644 --- a/web/comfyui/ui_utils.js +++ b/web/comfyui/top_menu_extension.js @@ -1,147 +1,104 @@ import { app } from "../../scripts/app.js"; -const extension = { - name: "lora-manager.widget", -}; +import { ComfyButtonGroup } from "../../scripts/ui/components/buttonGroup.js"; +import { ComfyButton } from "../../scripts/ui/components/button.js"; -app.registerExtension(extension); -const config = { - newTab: true, - newWindow: { - width: 1200, - height: 800, - } -}; +const BUTTON_GROUP_CLASS = "lora-manager-top-menu-group"; +const BUTTON_TOOLTIP = "Launch LoRA Manager (Shift+Click opens in new window)"; +const LORA_MANAGER_PATH = "/loras"; +const NEW_WINDOW_FEATURES = "width=1200,height=800,resizable=yes,scrollbars=yes,status=yes"; +const MAX_ATTACH_ATTEMPTS = 120; -const createWidget = ({ className, text, tooltip, includeIcon, svgMarkup }) => { - const button = document.createElement('button'); - button.className = className; - button.setAttribute('aria-label', tooltip); - button.title = tooltip; +const openLoraManager = (event) => { + const url = `${window.location.origin}${LORA_MANAGER_PATH}`; - if (includeIcon && svgMarkup) { - const iconContainer = document.createElement('span'); - iconContainer.innerHTML = svgMarkup; - iconContainer.style.display = 'flex'; - iconContainer.style.alignItems = 'center'; - iconContainer.style.justifyContent = 'center'; - iconContainer.style.width = '20px'; - iconContainer.style.height = '16px'; - button.appendChild(iconContainer); - } - - const textNode = document.createTextNode(text); - button.appendChild(textNode); - - button.addEventListener('click', onClick); - return button; -}; - -const onClick = (e) => { - const loraManagerUrl = `${window.location.origin}/loras`; - - // Check if Shift key is pressed to determine how to open - if (e.shiftKey) { - // Open in new window - const { width, height } = config.newWindow; - const windowFeatures = `width=${width},height=${height},resizable=yes,scrollbars=yes,status=yes`; - window.open(loraManagerUrl, '_blank', windowFeatures); - } else { - // Default behavior: open in new tab - window.open(loraManagerUrl, '_blank'); - } -}; - -const addWidgetMenuRight = (menuRight) => { - let buttonGroup = menuRight.querySelector('.comfyui-button-group'); - - if (!buttonGroup) { - buttonGroup = document.createElement('div'); - buttonGroup.className = 'comfyui-button-group'; - menuRight.appendChild(buttonGroup); - } - - const loraManagerButton = createWidget({ - className: 'comfyui-button comfyui-menu-mobile-collapse primary', - text: '', - tooltip: 'Launch Lora Manager (Shift+Click to open in new window)', - includeIcon: true, - svgMarkup: getLoraManagerIcon(), - }); - - buttonGroup.appendChild(loraManagerButton); -}; - -const addWidgetMenu = (menu) => { - const resetViewButton = menu.querySelector('#comfy-reset-view-button'); - if (!resetViewButton) { + if (event.shiftKey) { + window.open(url, "_blank", NEW_WINDOW_FEATURES); return; } - const loraManagerButton = createWidget({ - className: 'comfy-lora-manager-button', - text: 'Lora Manager', - tooltip: 'Launch Lora Manager (Shift+Click to open in new window)', - includeIcon: false, - }); - - resetViewButton.insertAdjacentElement('afterend', loraManagerButton); + window.open(url, "_blank"); }; -const addWidget = (selector, callback) => { - const observer = new MutationObserver((mutations, obs) => { - const element = document.querySelector(selector); - if (element) { - callback(element); - obs.disconnect(); +const createTopMenuButton = () => { + const button = new ComfyButton({ + icon: "loramanager", + tooltip: BUTTON_TOOLTIP, + app, + enabled: true, + classList: "comfyui-button comfyui-menu-mobile-collapse primary", + }); + + button.element.setAttribute("aria-label", BUTTON_TOOLTIP); + button.element.title = BUTTON_TOOLTIP; + + if (button.iconElement) { + button.iconElement.innerHTML = getLoraManagerIcon(); + button.iconElement.style.width = "1.2rem"; + button.iconElement.style.height = "1.2rem"; + } + + button.element.addEventListener("click", openLoraManager); + return button; +}; + +const attachTopMenuButton = (attempt = 0) => { + if (document.querySelector(`.${BUTTON_GROUP_CLASS}`)) { + return; + } + + const settingsGroup = app.menu?.settingsGroup; + if (!settingsGroup?.element?.parentElement) { + if (attempt >= MAX_ATTACH_ATTEMPTS) { + console.warn("LoRA Manager: unable to locate the ComfyUI settings button group."); + return; } - }); - observer.observe(document.body, { childList: true, subtree: true }); + requestAnimationFrame(() => attachTopMenuButton(attempt + 1)); + return; + } + + const loraManagerButton = createTopMenuButton(); + const buttonGroup = new ComfyButtonGroup(loraManagerButton); + buttonGroup.element.classList.add(BUTTON_GROUP_CLASS); + + settingsGroup.element.before(buttonGroup.element); }; -const initializeWidgets = () => { - addWidget('.comfyui-menu-right', addWidgetMenuRight); - addWidget('.comfy-menu', addWidgetMenu); -}; - -// Fetch version info from the API const fetchVersionInfo = async () => { try { - const response = await fetch('/api/lm/version-info'); + const response = await fetch("/api/lm/version-info"); const data = await response.json(); - + if (data.success) { return data.version; } - return ''; } catch (error) { - console.error('Error fetching version info:', error); - return ''; + console.error("LoRA Manager: error fetching version info:", error); } + + return ""; }; -// Register about badge with version info -const registerAboutBadge = async () => { - let version = await fetchVersionInfo(); - const label = version ? `LoRA-Manager v${version}` : 'LoRA-Manager'; - - app.registerExtension({ - name: 'LoraManager.AboutBadge', - aboutPageBadges: [ - { - label: label, - url: 'https://github.com/willmiao/ComfyUI-Lora-Manager', - icon: 'pi pi-tags' - } - ] - }); +const createAboutBadge = (version) => { + const label = version ? `LoRA Manager v${version}` : "LoRA Manager"; + + return { + label, + url: "https://github.com/willmiao/ComfyUI-Lora-Manager", + icon: "pi pi-tags", + }; }; -// Initialize everything -const initialize = () => { - initializeWidgets(); - registerAboutBadge(); -}; +app.registerExtension({ + name: "LoraManager.TopMenu", + aboutPageBadges: [createAboutBadge()], + async setup() { + attachTopMenuButton(); + + const version = await fetchVersionInfo(); + this.aboutPageBadges = [createAboutBadge(version)]; + }, +}); const getLoraManagerIcon = () => { return ` @@ -182,5 +139,3 @@ const getLoraManagerIcon = () => { `; }; - -initialize(); \ No newline at end of file