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
This commit is contained in:
Will Miao
2025-10-21 11:54:50 +08:00
parent 0b8f137a1b
commit 9b1a9ee071

View File

@@ -1,147 +1,104 @@
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
const extension = { import { ComfyButtonGroup } from "../../scripts/ui/components/buttonGroup.js";
name: "lora-manager.widget", import { ComfyButton } from "../../scripts/ui/components/button.js";
};
app.registerExtension(extension); const BUTTON_GROUP_CLASS = "lora-manager-top-menu-group";
const config = { const BUTTON_TOOLTIP = "Launch LoRA Manager (Shift+Click opens in new window)";
newTab: true, const LORA_MANAGER_PATH = "/loras";
newWindow: { const NEW_WINDOW_FEATURES = "width=1200,height=800,resizable=yes,scrollbars=yes,status=yes";
width: 1200, const MAX_ATTACH_ATTEMPTS = 120;
height: 800,
}
};
const createWidget = ({ className, text, tooltip, includeIcon, svgMarkup }) => { const openLoraManager = (event) => {
const button = document.createElement('button'); const url = `${window.location.origin}${LORA_MANAGER_PATH}`;
button.className = className;
button.setAttribute('aria-label', tooltip);
button.title = tooltip;
if (includeIcon && svgMarkup) { if (event.shiftKey) {
const iconContainer = document.createElement('span'); window.open(url, "_blank", NEW_WINDOW_FEATURES);
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) {
return; return;
} }
const loraManagerButton = createWidget({ window.open(url, "_blank");
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);
}; };
const addWidget = (selector, callback) => { const createTopMenuButton = () => {
const observer = new MutationObserver((mutations, obs) => { const button = new ComfyButton({
const element = document.querySelector(selector); icon: "loramanager",
if (element) { tooltip: BUTTON_TOOLTIP,
callback(element); app,
obs.disconnect(); 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 () => { const fetchVersionInfo = async () => {
try { try {
const response = await fetch('/api/lm/version-info'); const response = await fetch("/api/lm/version-info");
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
return data.version; return data.version;
} }
return '';
} catch (error) { } catch (error) {
console.error('Error fetching version info:', error); console.error("LoRA Manager: error fetching version info:", error);
return '';
} }
return "";
}; };
// Register about badge with version info const createAboutBadge = (version) => {
const registerAboutBadge = async () => { const label = version ? `LoRA Manager v${version}` : "LoRA Manager";
let version = await fetchVersionInfo();
const label = version ? `LoRA-Manager v${version}` : 'LoRA-Manager';
app.registerExtension({ return {
name: 'LoraManager.AboutBadge', label,
aboutPageBadges: [ url: "https://github.com/willmiao/ComfyUI-Lora-Manager",
{ icon: "pi pi-tags",
label: label, };
url: 'https://github.com/willmiao/ComfyUI-Lora-Manager',
icon: 'pi pi-tags'
}
]
});
}; };
// Initialize everything app.registerExtension({
const initialize = () => { name: "LoraManager.TopMenu",
initializeWidgets(); aboutPageBadges: [createAboutBadge()],
registerAboutBadge(); async setup() {
}; attachTopMenuButton();
const version = await fetchVersionInfo();
this.aboutPageBadges = [createAboutBadge(version)];
},
});
const getLoraManagerIcon = () => { const getLoraManagerIcon = () => {
return ` return `
@@ -182,5 +139,3 @@ const getLoraManagerIcon = () => {
</svg> </svg>
`; `;
}; };
initialize();