diff --git a/css/info-panel.css b/css/info-panel.css
index 4cec9ea..bf3b31a 100644
--- a/css/info-panel.css
+++ b/css/info-panel.css
@@ -661,4 +661,66 @@
.pulse {
animation: pulse 2s infinite;
+}
+
+/* Focus Node Button Styling - Premium Glass */
+.focus-node-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+
+ /* Ghost Style Default */
+ background: transparent;
+ color: var(--info-panel-accent-color, #74b9ff);
+ border: 1px solid var(--info-panel-accent-color, #74b9ff);
+
+ padding: 2px 10px;
+ border-radius: 6px;
+ font-weight: 600;
+ font-size: 0.9em;
+ letter-spacing: 0.02em;
+
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow: 0 0 0 rgba(0, 0, 0, 0);
+ opacity: 0.9;
+}
+
+.focus-node-btn:hover {
+ /* Glowing active state */
+ background: var(--info-panel-accent-color, #74b9ff);
+ color: #fff;
+ /* Switch to white text on fill */
+
+ /* Create a glow effect using the accent color */
+ box-shadow: 0 4px 12px -2px var(--info-panel-accent-color, #74b9ff),
+ 0 0 0 1px var(--info-panel-accent-color, #74b9ff);
+
+ transform: translateY(-1px) scale(1.02);
+ opacity: 1;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+.focus-node-btn:active {
+ transform: translateY(0) scale(0.98);
+ box-shadow: 0 2px 4px -2px var(--info-panel-accent-color, #74b9ff);
+ filter: brightness(0.95);
+}
+
+.focus-node-btn .icon {
+ display: flex;
+ align-items: center;
+ transition: transform 0.2s ease;
+}
+
+.focus-node-btn:hover .icon {
+ transform: scale(1.1);
+}
+
+/* Ensure the SVG inside inherits color properly */
+.focus-node-btn svg {
+ stroke: currentColor;
+ width: 14px;
+ height: 14px;
+ stroke-width: 2.5;
+ /* Make icon slightly bolder */
}
\ No newline at end of file
diff --git a/src/info-panel/UIManager.ts b/src/info-panel/UIManager.ts
index 46c67a4..60f5def 100644
--- a/src/info-panel/UIManager.ts
+++ b/src/info-panel/UIManager.ts
@@ -8,6 +8,7 @@
import { StateManager } from './StateManager';
import { Icons } from '../shared/icons';
import { Logger } from '../shared/logger';
+import { INFO_PANEL_ID } from '../shared/constants';
import { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';
import {
getCheckpointInfo,
@@ -56,7 +57,7 @@ export class UIManager {
createPanel(): void {
// Main panel container
this.elements.panel = document.createElement("div");
- this.elements.panel.id = "comfyui-magnify-info-panel-pro-v2";
+ this.elements.panel.id = INFO_PANEL_ID;
this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;
// Header
@@ -773,7 +774,7 @@ export class UIManager {
// Add Zoom to Node button
nodeContent.push({
label: 'Location',
- value: `${Icons.focus} Focus Node`,
+ value: `${Icons.focus} Focus Node`,
clickable: 'zoom',
nodeId: info.hoveredNode.id
});
diff --git a/src/magnify-glass/MagnifyGlass.ts b/src/magnify-glass/MagnifyGlass.ts
index 6467198..856d143 100644
--- a/src/magnify-glass/MagnifyGlass.ts
+++ b/src/magnify-glass/MagnifyGlass.ts
@@ -6,7 +6,7 @@
import type { ComfyApp, ComfyNode } from '../types/comfyui';
import { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils';
-import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';
+import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET, INFO_PANEL_ID } from '../shared/constants';
import { registerGlassSettings, registerAccessibilitySettings } from '../shared/settings';
import { ConfigManager } from './ConfigManager';
import { MagnifierState } from './MagnifierState';
@@ -139,8 +139,7 @@ export class MagnifyGlass {
if (state.active) {
// TURNING OFF - Force hide everything
- state.active = false;
- this.ui.hide();
+ this.forceHideAllComponents();
} else {
// TURNING ON
state.active = true;
@@ -151,6 +150,39 @@ export class MagnifyGlass {
}
}
+ /**
+ * Force hide all components including extensions (Info Panel, etc).
+ * This overcomes the "pinned" state of the info panel when the main tool is deactivated.
+ */
+ private forceHideAllComponents(): void {
+ this.state.active = false;
+ this.ui.hide();
+
+ // 1. Nuclear option: Direct DOM ID targeting
+ // This is necessary because in some cases object references might be stale or logic might prevent hiding
+ const panelEl = document.getElementById(INFO_PANEL_ID);
+ if (panelEl) {
+ panelEl.style.display = 'none';
+ // Also force opacity 0 just in case transition handles display
+ panelEl.style.opacity = '0';
+ }
+
+ // Iterate through all registered extensions and hide them if they have a hide method
+ const extensions = window.comfyUIMagnifyGlassExtensions;
+ if (extensions && extensions.length > 0) {
+ extensions.forEach((extension: any) => {
+ // Check basically any likely property for a UI manager or direct hide method
+ if (extension && extension.uiManager && typeof extension.uiManager.hide === 'function') {
+ extension.uiManager.hide();
+ // Explicitly force display none if available to be extra sure
+ if (extension.uiManager.elements && extension.uiManager.elements.panel) {
+ extension.uiManager.elements.panel.style.display = 'none';
+ }
+ }
+ });
+ }
+ }
+
/**
* Set glass preview visibility WITHOUT affecting info panel.
* This is used by the toggle-glass hover control button.
diff --git a/src/magnify_info_panel.ts b/src/magnify_info_panel.ts
index 93da91e..bebb83f 100644
--- a/src/magnify_info_panel.ts
+++ b/src/magnify_info_panel.ts
@@ -43,6 +43,12 @@ function initializeInfoPanel() {
// Expose orchestrator globally if needed
window.infoPanelManager = infoPanel as any;
+ // Register as extension for central management (e.g. force hide on toggle)
+ if (!window.comfyUIMagnifyGlassExtensions) {
+ window.comfyUIMagnifyGlassExtensions = [];
+ }
+ window.comfyUIMagnifyGlassExtensions.push(infoPanel);
+
// CSS is loaded by UIManager.injectStyles() - no need to load separately
Logger.info('Info Panel extension initialized');
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 7054448..0a046b5 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -64,6 +64,7 @@ export const DEFAULT_OFFSET_STEP = 5;
export const DEFAULT_PANEL_WIDTH = 300;
export const DEFAULT_PANEL_MAX_HEIGHT = 300;
export const DEFAULT_PANEL_OPACITY = 100;
+export const INFO_PANEL_ID = "comfyui-magnify-info-panel-pro-v2";
// Z-Index hierarchy
export const Z_INDEX = {
diff --git a/web/info-panel.css b/web/info-panel.css
index 82e4f8b..f4e42c9 100644
--- a/web/info-panel.css
+++ b/web/info-panel.css
@@ -795,4 +795,66 @@
.pulse {
animation: pulse 2s infinite;
+}
+
+/* Focus Node Button Styling - Premium Glass */
+.focus-node-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+
+ /* Ghost Style Default */
+ background: transparent;
+ color: var(--info-panel-accent-color, #74b9ff);
+ border: 1px solid var(--info-panel-accent-color, #74b9ff);
+
+ padding: 2px 10px;
+ border-radius: 6px;
+ font-weight: 600;
+ font-size: 0.9em;
+ letter-spacing: 0.02em;
+
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ box-shadow: 0 0 0 rgba(0, 0, 0, 0);
+ opacity: 0.9;
+}
+
+.focus-node-btn:hover {
+ /* Glowing active state */
+ background: var(--info-panel-accent-color, #74b9ff);
+ color: #fff;
+ /* Switch to white text on fill */
+
+ /* Create a glow effect using the accent color */
+ box-shadow: 0 4px 12px -2px var(--info-panel-accent-color, #74b9ff),
+ 0 0 0 1px var(--info-panel-accent-color, #74b9ff);
+
+ transform: translateY(-1px) scale(1.02);
+ opacity: 1;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+.focus-node-btn:active {
+ transform: translateY(0) scale(0.98);
+ box-shadow: 0 2px 4px -2px var(--info-panel-accent-color, #74b9ff);
+ filter: brightness(0.95);
+}
+
+.focus-node-btn .icon {
+ display: flex;
+ align-items: center;
+ transition: transform 0.2s ease;
+}
+
+.focus-node-btn:hover .icon {
+ transform: scale(1.1);
+}
+
+/* Ensure the SVG inside inherits color properly */
+.focus-node-btn svg {
+ stroke: currentColor;
+ width: 14px;
+ height: 14px;
+ stroke-width: 2.5;
+ /* Make icon slightly bolder */
}
\ No newline at end of file
diff --git a/web/info-panel/UIManager.js b/web/info-panel/UIManager.js
index 51729f3..07476c5 100644
--- a/web/info-panel/UIManager.js
+++ b/web/info-panel/UIManager.js
@@ -3,6 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { Icons } from "../shared/icons.js";
import { Logger } from "../shared/logger.js";
+import { INFO_PANEL_ID } from "../shared/constants.js";
import { formatValue, getValueClass, getValueAttributes } from "./ValueFormatter.js";
import { getCheckpointInfo, getImageInfo, getTextBoxContent, getImportantNodeParameters } from "./NodeDataExtractor.js";
import { NodeSelector } from "./NodeSelector.js";
@@ -29,7 +30,7 @@ class UIManager {
*/
createPanel() {
this.elements.panel = document.createElement("div");
- this.elements.panel.id = "comfyui-magnify-info-panel-pro-v2";
+ this.elements.panel.id = INFO_PANEL_ID;
this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;
this.elements.header = document.createElement("div");
this.elements.header.className = "panel-header";
@@ -557,7 +558,7 @@ class UIManager {
}
nodeContent.push({
label: "Location",
- value: `${Icons.focus} Focus Node`,
+ value: `${Icons.focus} Focus Node`,
clickable: "zoom",
nodeId: info.hoveredNode.id
});
diff --git a/web/info-panel/UIManager.js.map b/web/info-panel/UIManager.js.map
index e5f7906..c663fa6 100644
--- a/web/info-panel/UIManager.js.map
+++ b/web/info-panel/UIManager.js.map
@@ -1 +1 @@
-{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\nimport { Logger } from '../shared/logger';\nimport { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';\nimport {\n getCheckpointInfo,\n getImageInfo,\n getTextBoxContent,\n getImportantNodeParameters,\n type ImageInfoResult\n} from './NodeDataExtractor';\nimport { NodeSelector, type NodeListEntry, type NodeExecOrderEntry } from './NodeSelector';\n\ninterface InfoPanelElements {\n panel: HTMLDivElement | null;\n header: HTMLDivElement | null;\n content: HTMLDivElement | null;\n controls: HTMLDivElement | null;\n}\n\n/**\n * UI Manager class.\n * Handles all DOM manipulation and UI creation.\n */\nexport class UIManager {\n stateManager: StateManager;\n elements: InfoPanelElements;\n nodeSelector: NodeSelector;\n currentDropdown: HTMLDivElement | null = null;\n onNodeSelected: ((nodeId: number) => void) | null = null;\n\n constructor(stateManager: StateManager) {\n this.stateManager = stateManager;\n this.elements = {\n panel: null,\n header: null,\n content: null,\n controls: null\n };\n this.nodeSelector = new NodeSelector();\n\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = \"comfyui-magnify-info-panel-pro-v2\";\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n
\n \n `;\n\n // Content container\n this.elements.content = document.createElement(\"div\");\n this.elements.content.className = \"panel-content\";\n\n this.elements.panel.appendChild(this.elements.header);\n this.elements.panel.appendChild(this.elements.content);\n\n this.applyStyles();\n // Apply initial minimized state if set\n if (this.stateManager.state.isPanelMinimized) {\n this.elements.panel.classList.add('panel-minimized');\n }\n document.body.appendChild(this.elements.panel);\n\n // Hover-expand: Add/remove 'is-expanded' class, reposition, and resize font if needed\n let originalTop: number | null = null;\n let originalFontSize: number | null = null;\n const MIN_FONT_SIZE = 10; // Minimum font size in pixels\n const MARGIN = 20; // Minimum margin from viewport edges\n\n this.elements.panel.addEventListener('mouseenter', () => {\n // Sticky Mode (Persist): disable this behavior to prevent bouncing\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n return;\n }\n\n if (!this.stateManager.state.isPanelMinimized && this.elements.panel) {\n // Save original position\n originalTop = this.elements.panel.offsetTop;\n // Save original font size just in case, though we won't change it\n originalFontSize = parseFloat(getComputedStyle(this.elements.panel).fontSize);\n\n // Add expanded class\n this.elements.panel.classList.add('is-expanded');\n\n // Simple repositioning logic only (no font scaling)\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (!this.elements.panel) return;\n\n const viewportHeight = window.innerHeight;\n const rect = this.elements.panel.getBoundingClientRect();\n\n // Position the panel to fit (move up if needed)\n if (rect.bottom > viewportHeight - MARGIN) {\n const newTop = Math.max(MARGIN, originalTop! - (rect.bottom - viewportHeight + MARGIN));\n this.elements.panel.style.top = `${newTop}px`;\n }\n });\n });\n }\n });\n\n this.elements.panel.addEventListener('mouseleave', () => {\n // Sticky Mode (Persist): disable this behavior to prevent bouncing\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n return;\n }\n\n if (this.elements.panel) {\n this.elements.panel.classList.remove('is-expanded');\n\n // Restore original position\n if (originalTop !== null) {\n this.elements.panel.style.top = `${originalTop}px`;\n originalTop = null;\n }\n\n // Restore original font size (if it was somehow changed)\n if (originalFontSize !== null) {\n this.elements.panel.style.fontSize = `${originalFontSize}px`;\n originalFontSize = null;\n }\n\n // Clear any inline styles that might have been set\n this.elements.panel.style.transform = '';\n this.elements.panel.style.transformOrigin = '';\n }\n });\n\n // Add click event delegation for panel elements\n this.elements.panel.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n\n // Handle minimize button\n const minimizeBtn = target.closest('[data-action=\"minimize\"]');\n if (minimizeBtn) {\n this.stateManager.state.isPanelMinimized = !this.stateManager.state.isPanelMinimized;\n this.updateMinimizedState();\n return;\n }\n\n // Handle section header clicks for expand/collapse\n const sectionHeader = target.closest('.section-header') as HTMLElement;\n if (sectionHeader) {\n const sectionId = sectionHeader.getAttribute('data-section');\n // Skip node section (always expanded)\n if (sectionId && sectionId !== 'node') {\n const isExpanded = this.stateManager.state.expandedSections.has(sectionId);\n if (isExpanded) {\n this.stateManager.state.expandedSections.delete(sectionId);\n } else {\n this.stateManager.state.expandedSections.add(sectionId);\n }\n\n // Update visual state\n sectionHeader.classList.toggle('expanded', !isExpanded);\n const sectionContent = sectionHeader.nextElementSibling as HTMLElement;\n if (sectionContent && sectionContent.classList.contains('section-content')) {\n sectionContent.classList.toggle('expanded', !isExpanded);\n }\n }\n }\n });\n\n // Create floating controls after panel is in DOM\n if (!this.elements.controls) {\n this.createFloatingControls();\n // Update control states immediately after creation\n this.updateControlStates();\n }\n }\n\n /**\n * Create floating control buttons.\n */\n createFloatingControls(): void {\n this.elements.controls = document.createElement(\"div\");\n this.elements.controls.className = `floating-controls vertical-layout theme-${this.stateManager.state.currentTheme}`; // Default to vertical + theme\n\n /**\n * BUTTON MAPPING DOCUMENTATION:\n * =============================\n * Button 1 (Unlock Icon): data-action=\"pin\" → controls isPanelPinned\n * - Unpinned (default): Panel follows magnify glass\n * - Pinned: Panel stays at fixed position (unlocked from following glass)\n * \n * Button 2 (Pin Icon): data-action=\"lock\" → controls isPanelLocked\n * - Unlocked (default): Panel can be dragged\n * - Locked: Panel cannot be dragged (pinned in place)\n * \n * NOTE: The data-action names are counterintuitive but kept for backwards compatibility.\n * The ICONS correctly represent the functionality:\n * - Unlock icon = unlock from following glass\n * - Pin icon = pin position (prevent drag)\n */\n this.elements.controls.innerHTML = `\n \n \n \n \n \n \n \n \n \n \n `;\n\n // Insert before the panel in the document body, not as a child\n document.body.appendChild(this.elements.controls);\n\n // Hide and position off-screen initially - will be shown when magnify glass activates and positions them\n this.elements.controls.style.display = 'none';\n this.elements.controls.style.visibility = 'hidden';\n this.elements.controls.style.left = '-9999px';\n this.elements.controls.style.top = '-9999px';\n\n // Add click event delegation for control buttons\n this.elements.controls.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n const button = target.closest('button[data-action]') as HTMLButtonElement;\n if (!button) return;\n\n const action = button.getAttribute('data-action');\n Logger.debug(`Control button clicked: ${action}`);\n\n switch (action) {\n case 'pin':\n this.stateManager.togglePinning();\n this.updatePinnedState();\n this.updateControlStates();\n break;\n case 'lock':\n this.stateManager.state.isPanelLocked = !this.stateManager.state.isPanelLocked;\n this.updateControlStates();\n if (this.elements.panel) {\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n }\n break;\n case 'persist':\n this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"] = !this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n this.updateControlStates();\n this.applyStyles(); // Update persist-active class\n // Sync sidebar setting if needed (optional, sidebar updates on reopen usually)\n break;\n case 'toggle-panel':\n if (this.stateManager.state.isPanelVisible) {\n // User requested to hide the panel\n this.hide();\n\n // User Request: Disable sticky info when hiding via button\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"] = false;\n this.applyStyles(); // Update visual styles if necessary\n }\n } else {\n this.show();\n }\n this.updateControlStates();\n break;\n case 'toggle-glass':\n this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;\n\n // Link Glass Visibility to Panel Pinning\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass Hidden\n console.log(`[MagnifyGlass Debug] Glass Hidden. pinned=${this.stateManager.state.isPanelPinned}, autoPinned=${this.stateManager.state.isAutoPinned}`);\n // Only auto-pin if not already pinned by the user\n if (!this.stateManager.state.isPanelPinned) {\n if (this.elements.panel) {\n const rect = this.elements.panel.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n this.stateManager.state.isPanelPinned = true;\n this.stateManager.state.isAutoPinned = true; // Track that this was auto-pinned\n console.log(`[MagnifyGlass Debug] Auto-Pinned panel.`);\n }\n // Do NOT reset isPanelLocked here - preserve user's lock state\n } else {\n // Glass Shown\n console.log(`[MagnifyGlass Debug] Glass Shown. pinned=${this.stateManager.state.isPanelPinned}, autoPinned=${this.stateManager.state.isAutoPinned}`);\n // Only unpin if it was auto-pinned (not user-pinned)\n if (this.stateManager.state.isAutoPinned) {\n this.stateManager.state.isPanelPinned = false;\n this.stateManager.state.isAutoPinned = false;\n console.log(`[MagnifyGlass Debug] Auto-Unpinned panel.`);\n }\n // If user manually pinned, keep the panel pinned\n }\n\n this.updateControlStates();\n this.updatePinnedState(); // Update visual class\n\n // Toggle BOTH the visual visibility AND the rendering\n // This fully disables the offscreen renderer when preview is hidden\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.setGlassPreviewActive) {\n magnifyGlass.setGlassPreviewActive(this.stateManager.state.isGlassPreviewVisible);\n } else if (magnifyGlass && magnifyGlass.ui?.setPreviewVisibility) {\n // Fallback for backwards compatibility\n magnifyGlass.ui.setPreviewVisibility(this.stateManager.state.isGlassPreviewVisible);\n }\n break;\n case 'popout':\n // Toggle the pop-out tab\n const glass = (window as any).comfyUIMagnifyGlass;\n if (glass && glass.popOutManager) {\n glass.popOutManager.toggle();\n }\n break;\n case 'drag-glass':\n // Toggle glass drag mode\n const mglass = (window as any).comfyUIMagnifyGlass;\n if (mglass && mglass.state) {\n mglass.state.isDragModeEnabled = !mglass.state.isDragModeEnabled;\n this.updateControlStates();\n\n // Update UI to indicate drag mode\n if (mglass.ui && mglass.ui.setDragMode) {\n mglass.ui.setDragMode(mglass.state.isDragModeEnabled);\n }\n }\n break;\n case 'reset-glass':\n // Reset glass offsets\n const rglass = (window as any).comfyUIMagnifyGlass;\n if (rglass) {\n rglass.resetOffsets();\n Logger.debug(\"Reset glass position via hover controls\");\n\n // Disable drag mode if active\n if (rglass.state && rglass.state.isDragModeEnabled) {\n rglass.state.isDragModeEnabled = false;\n if (rglass.ui && rglass.ui.setDragMode) {\n rglass.ui.setDragMode(false);\n }\n this.updateControlStates();\n }\n }\n break;\n case 'toggle-cursor':\n const cglass = (window as any).comfyUIMagnifyGlass;\n if (cglass && cglass.config) {\n cglass.config.showCursorPreview = !cglass.config.showCursorPreview;\n cglass.updateMagnifiedView();\n this.updateControlStates();\n }\n break;\n case 'toggle-hold':\n this.stateManager.toggleHold();\n this.updateControlStates();\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = String(this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\");\n this.updateControlsLayout(controlsPosition);\n\n // Initial state update\n this.updateControlStates();\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const isGlassVisible = this.stateManager.state.isGlassPreviewVisible;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n // Hide unlock button when panel is hidden\n pinBtn.style.display = isPanelVisible ? 'flex' : 'none';\n\n // Disable lock button when glass is hidden (no point unlocking to follow invisible glass)\n if (!isGlassVisible) {\n pinBtn.disabled = true;\n pinBtn.style.opacity = '0.5';\n pinBtn.title = \"Cannot toggle lock when glass preview is hidden\";\n } else {\n pinBtn.disabled = false;\n pinBtn.style.opacity = '';\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel Position (Follow Glass)\" : \"Unlock Panel Position (Drag Inspector)\";\n }\n }\n\n if (lockBtn) {\n // Only show pin button when panel is visible AND unlocked from glass\n const showLockBtn = isPanelVisible && this.stateManager.state.isPanelPinned;\n lockBtn.style.display = showLockBtn ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Unpin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n const persistBtn = this.elements.controls.querySelector('[data-action=\"persist\"]') as HTMLButtonElement;\n if (persistBtn) {\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n persistBtn.classList.toggle('active', isPersistConfigured);\n persistBtn.title = isPersistConfigured ? \"Disable Sticky Info (S)\" : \"Enable Sticky Info (S)\";\n persistBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n\n const holdBtn = this.elements.controls.querySelector('[data-action=\"toggle-hold\"]') as HTMLButtonElement;\n if (holdBtn) {\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n const isHeld = this.stateManager.state.isInfoHeld;\n\n // Only show Hold button when Sticky Info (Persist) is enabled AND panel is visible\n holdBtn.style.display = (isPanelVisible && isPersistConfigured) ? 'flex' : 'none';\n\n holdBtn.classList.toggle('active', isHeld);\n // Toggle icon: Pause (to hold) vs Play (to resume)\n holdBtn.innerHTML = isHeld ? Icons.play : Icons.pause;\n holdBtn.title = isHeld ? \"Resume Info Update (P)\" : \"Pause Info Update (P)\";\n }\n\n if (visibilityBtn) {\n // Active means \"Panel is Visible\"\n visibilityBtn.classList.toggle('active', isPanelVisible);\n visibilityBtn.title = isPanelVisible ? \"Hide Panel\" : \"Show Panel\";\n\n // Disable hide button if glass is hidden (prevent hiding everything)\n if (!isGlassVisible) {\n visibilityBtn.disabled = true;\n visibilityBtn.style.opacity = '0.5';\n visibilityBtn.title = \"Cannot hide panel when glass preview is hidden\";\n } else {\n visibilityBtn.disabled = false;\n visibilityBtn.style.opacity = '';\n }\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', isGlassVisible);\n glassBtn.title = isGlassVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n\n // Only show glass toggle button if the inspector panel is visible\n glassBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n\n const cursorBtn = this.elements.controls.querySelector('[data-action=\"toggle-cursor\"]') as HTMLButtonElement;\n if (cursorBtn) {\n const cglass = (window as any).comfyUIMagnifyGlass;\n const showCursor = cglass?.config?.showCursorPreview || false;\n cursorBtn.classList.toggle('active', showCursor);\n cursorBtn.title = showCursor ? \"Hide Cursor Preview\" : \"Show Cursor Preview\";\n\n // Logic: Visible when glass is visible AND:\n // 1. Panel is NOT pinned (attached to glass) - original logic\n // 2. Sticky Info (Persist) is NOT enabled - user request\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n\n cursorBtn.style.display = (isGlassVisible && !this.stateManager.state.isPanelPinned && !isPersistConfigured) ? 'flex' : 'none';\n }\n\n const dragGlassBtn = this.elements.controls.querySelector('[data-action=\"drag-glass\"]') as HTMLButtonElement;\n if (dragGlassBtn) {\n const mglass = (window as any).comfyUIMagnifyGlass;\n const isDragMode = mglass?.state?.isDragModeEnabled || false;\n dragGlassBtn.classList.toggle('active', isDragMode);\n dragGlassBtn.title = isDragMode ? \"Cancel Move Mode (H)\" : \"Move Glass Position (H)\";\n // Only show drag button when glass is visible (even if panel is hidden)\n dragGlassBtn.style.display = isGlassVisible ? 'flex' : 'none';\n }\n\n const resetGlassBtn = this.elements.controls.querySelector('[data-action=\"reset-glass\"]') as HTMLButtonElement;\n if (resetGlassBtn) {\n // Only show reset button when glass is visible (moves with drag/popout buttons)\n resetGlassBtn.style.display = isGlassVisible ? 'flex' : 'none';\n }\n\n const popoutBtn = this.elements.controls.querySelector('[data-action=\"popout\"]') as HTMLButtonElement;\n if (popoutBtn) {\n const glass = (window as any).comfyUIMagnifyGlass;\n const isOpen = glass?.popOutManager?.isPopOutOpen() || false;\n // Logger.debug(`[UIManager] Popout button update - Glass: ${!!glass}, IsOpen: ${isOpen}`);\n popoutBtn.classList.toggle('active', isOpen);\n popoutBtn.title = isOpen ? \"Close Pop-out Viewer\" : \"Open Pop-out Viewer\";\n }\n }\n\n /**\n * Update controls layout based on position setting.\n * @param position \n */\n updateControlsLayout(position: string): void {\n if (!this.elements.controls) return;\n\n // Remove existing layout classes\n this.elements.controls.classList.remove('horizontal-layout', 'vertical-layout');\n\n // Add appropriate layout class\n if (['top', 'bottom'].includes(position)) {\n this.elements.controls.classList.add('horizontal-layout');\n } else {\n this.elements.controls.classList.add('vertical-layout');\n }\n }\n\n /**\n * Load a Google Font dynamically.\n * @param fontName - Name of the font to load\n */\n loadGoogleFont(fontName: string): void {\n // Skip if already loaded\n const linkId = `google-font-${fontName.replace(/\\s+/g, '-').toLowerCase()}`;\n if (document.getElementById(linkId)) return;\n\n // Create link element for Google Fonts\n const link = document.createElement('link');\n link.id = linkId;\n link.rel = 'stylesheet';\n link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontName)}:wght@400;500;600;700&display=swap`;\n document.head.appendChild(link);\n\n Logger.debug(`[UIManager] Loaded Google Font: ${fontName}`);\n }\n\n /**\n * Apply current styles to elements.\n */\n applyStyles(): void {\n if (!this.elements.panel) return;\n\n const settings = this.stateManager.state.settings;\n\n // Debug Log\n console.log(`[UIManager] applyStyles - MaxHeight: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}`);\n\n // 1. Apply dimensions\n this.elements.panel.style.width = `${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px`;\n // Set max-height via CSS variable so CSS :hover rules can override\n const maxHeight = settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"];\n this.elements.panel.style.setProperty('--panel-max-height', `${maxHeight}px`);\n this.elements.panel.style.height = 'auto';\n\n // 2. Apply static styles (only needed once or if overwritten, but safe to set)\n this.elements.panel.style.position = 'absolute';\n this.elements.panel.style.zIndex = '99999';\n this.elements.panel.style.transform = 'translateY(-10px)';\n this.elements.panel.style.pointerEvents = 'auto';\n this.elements.panel.style.userSelect = 'none';\n\n // Apply Font Family\n const fontFamily = settings[\"🔍MagnifyGlass.InfoPanelFontFamily\"] as string || \"System Default\";\n if (fontFamily === \"System Default\" || fontFamily === \"system-ui\") {\n this.elements.panel.style.fontFamily = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\";\n } else if (fontFamily === \"monospace\") {\n this.elements.panel.style.fontFamily = \"'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, monospace\";\n } else {\n // Load Google Font dynamically\n this.loadGoogleFont(fontFamily);\n this.elements.panel.style.fontFamily = `'${fontFamily}', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`;\n }\n\n // Apply Font Size\n const fontSize = settings[\"🔍MagnifyGlass.InfoPanelFontSize\"] || 14;\n this.elements.panel.style.fontSize = `${fontSize}px`;\n\n // 3. Apply transition\n this.elements.panel.style.transition = 'none';\n\n // 4. Apply Custom Colors (CSS Variables)\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"] as string | undefined;\n if (textColor && typeof textColor === 'string') {\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] as string | undefined;\n if (accentColor && typeof accentColor === 'string') {\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // 5. Apply Opacity\n // Logic: If visible, use setting. If hidden, use 0.\n // We do NOT touch 'display' here to avoid hiding it if it's currently shown.\n // 'display' is managed by show() / hide() methods exclusively.\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = Number(settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n } else {\n this.elements.panel.style.opacity = '0';\n }\n\n // 6. Apply Persist Mode Class\n const isPersist = !!settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n if (isPersist) {\n this.elements.panel.classList.add('persist-active');\n } else {\n this.elements.panel.classList.remove('persist-active');\n }\n }\n\n /**\n * Show the panel.\n */\n show(): void {\n if (!this.elements.panel) return;\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n this.elements.panel.style.display = \"block\";\n // Controls are shown by positionFloatingControls() after proper positioning\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = Number(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n this.updateControlStates();\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n this.elements.panel.classList.remove('visible');\n if (this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n }\n this.stateManager.state.isPanelVisible = false;\n this.updateControlStates();\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n if (this.elements.controls) {\n this.elements.controls.className = this.elements.controls.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"] && info.cursor && info.cursor.canvas) {\n const inspectorContent = [\n { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` },\n { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` },\n { label: 'Magnifier Zoom', value: `${info.magnifier.zoomFactor}×` }\n ];\n\n sections.push({\n id: 'inspector',\n icon: Icons.info,\n title: 'Inspector',\n content: inspectorContent\n });\n }\n\n // Media section\n if (info.media) {\n const mediaContent = [\n { label: 'Type', value: info.media.tagName },\n { label: 'Source', value: info.media.src }\n ];\n\n if (info.media.naturalSize) {\n mediaContent.push({ label: 'Natural', value: info.media.naturalSize });\n }\n\n sections.push({\n id: 'media',\n icon: Icons.camera,\n title: 'Media',\n badge: info.media.tagName,\n content: mediaContent\n });\n }\n\n // Node Details section\n if (info.hoveredNode) {\n const nodeContent: any[] = []; // Initial Node details\n nodeContent.push({\n label: 'Title',\n value: `${info.hoveredNode.title || 'Untitled'} (#${info.hoveredNode.id})`,\n clickable: 'title'\n });\n if (info.hoveredNode.executionOrder !== undefined) {\n nodeContent.push({ label: 'Exec Order', value: info.hoveredNode.executionOrder, clickable: 'execOrder' });\n }\n\n // Add Zoom to Node button\n nodeContent.push({\n label: 'Location',\n value: `${Icons.focus} Focus Node`,\n clickable: 'zoom',\n nodeId: info.hoveredNode.id\n });\n\n // Add category if available\n if (info.hoveredNode.category) {\n nodeContent.push({ label: 'Category', value: info.hoveredNode.category });\n }\n\n // Add python module path if available\n if (info.hoveredNode.pythonModule) {\n const path = info.hoveredNode.pythonModule.replace(/\\./g, '/') + '.py';\n nodeContent.push({ label: 'Path', value: path });\n }\n\n // Check if this is a complex node that shows all widgets\n const nodeType = info.hoveredNode.type ? info.hoveredNode.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = info.hoveredNode.type && (\n nodeType.includes('ksampler') ||\n nodeType.includes('sampler') ||\n nodeType.includes('k_samplers') ||\n nodeType.includes('checkpoint') ||\n nodeType.includes('model') ||\n nodeType.includes('lora') ||\n nodeType.includes('controlnet') ||\n nodeType.includes('advanced') ||\n nodeType.includes('detailer') ||\n nodeType.includes('inpaint') ||\n nodeType.includes('upscale') ||\n nodeType.includes('clip') ||\n nodeType.includes('text') ||\n nodeType.includes('encode')\n ) && !isSaveNode;\n\n if (!showAllWidgets) {\n // Add specific extractions for simple nodes\n const checkpoint = getCheckpointInfo(info.hoveredNode);\n if (checkpoint) {\n nodeContent.push({ label: 'Model', value: checkpoint });\n }\n\n const imgInfo = getImageInfo(info.hoveredNode);\n if (imgInfo) {\n if (typeof imgInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imgInfo });\n } else if (typeof imgInfo === 'object' && imgInfo !== null) {\n const imgResult = imgInfo as ImageInfoResult;\n nodeContent.push({ label: 'Image Size', value: `${imgResult.width}×${imgResult.height}` });\n if (imgResult.src) {\n nodeContent.push({ label: 'Image Source', value: imgResult.src });\n }\n }\n }\n\n const textContent = getTextBoxContent(info.hoveredNode);\n if (textContent) {\n nodeContent.push({ label: 'Text', value: textContent });\n }\n }\n\n const importantParameters = getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: Icons.box,\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n \n
${Icons.mapPin}
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n \n \n
\n
\n ${section.content.map((item: any) => {\n const value = formatValue(item.value, item.label);\n const valueClass = getValueClass(item.value);\n const valueAttributes = getValueAttributes(item.value);\n const clickableAttr = item.clickable ? `data-clickable=\"${item.clickable}\"` : '';\n const nodeIdAttr = item.nodeId !== undefined ? `data-node-id=\"${item.nodeId}\"` : '';\n const clickableClass = item.clickable ? 'clickable-row' : '';\n // Only show dropdown arrow for actual dropdowns, not actions like zoom\n const dropdownIcon = (item.clickable && item.clickable !== 'zoom') ? '
▼' : '';\n // Show copy button for string values that are long enough to be worth copying\n const rawValue = String(item.value || '');\n const showCopyBtn = !item.clickable && rawValue.length > 3 && typeof item.value === 'string';\n const copyBtnHtml = showCopyBtn ? `\n
` : '';\n return `\n
\n ${item.label}\n ${value}${dropdownIcon}\n ${copyBtnHtml}\n
`;\n }).join('')}\n
\n
\n
`).join('');\n\n // Add click handlers for clickable rows\n this.attachDropdownClickHandlers();\n }\n\n /**\n * Attach click handlers to clickable dropdown rows.\n */\n attachDropdownClickHandlers(): void {\n if (!this.elements.content) return;\n\n const clickableRows = this.elements.content.querySelectorAll('[data-clickable]');\n clickableRows.forEach(row => {\n const clickableType = (row as HTMLElement).dataset.clickable;\n\n row.addEventListener('click', (e) => {\n e.stopPropagation();\n\n if (clickableType === 'title') {\n this.showTitleDropdown(row as HTMLElement);\n } else if (clickableType === 'execOrder') {\n this.showExecOrderDropdown(row as HTMLElement);\n } else if (clickableType === 'id') {\n this.showIdDropdown(row as HTMLElement);\n } else if (clickableType === 'zoom') {\n const nodeId = (row as HTMLElement).dataset.nodeId;\n if (nodeId) {\n const app = (window as any).app;\n const node = app.graph.getNodeById(parseInt(nodeId));\n if (node && app.canvas) {\n app.canvas.centerOnNode(node);\n }\n }\n }\n });\n\n // Add hover effect\n row.addEventListener('mouseenter', () => {\n (row as HTMLElement).style.background = 'var(--comfy-input-bg, rgba(255,255,255,0.05))';\n });\n row.addEventListener('mouseleave', () => {\n (row as HTMLElement).style.background = '';\n });\n });\n\n // Add click handlers for copy buttons\n const copyButtons = this.elements.content.querySelectorAll('.copy-btn');\n copyButtons.forEach(btn => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n const value = (btn as HTMLElement).dataset.copyValue || '';\n navigator.clipboard.writeText(value).then(() => {\n // Show success feedback\n btn.classList.add('copied');\n const originalHtml = btn.innerHTML;\n btn.innerHTML = ``;\n setTimeout(() => {\n btn.innerHTML = originalHtml;\n btn.classList.remove('copied');\n }, 1500);\n }).catch(err => {\n console.error('Failed to copy:', err);\n });\n });\n });\n }\n\n /**\n * Update section expansion states.\n */\n updateSectionStates(): void {\n if (!this.elements.content) return;\n\n const sections = this.elements.content.querySelectorAll('.info-section');\n sections.forEach(section => {\n const sectionId = (section as HTMLElement).dataset.section;\n if (!sectionId) return;\n\n const header = section.querySelector('.section-header');\n const content = section.querySelector('.section-content');\n\n if (header && content) {\n if (this.stateManager.state.expandedSections.has(sectionId)) {\n header.classList.add('expanded');\n content.classList.add('expanded');\n } else {\n header.classList.remove('expanded');\n content.classList.remove('expanded');\n }\n }\n });\n }\n\n /**\n * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = String(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] || '');\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title} `;\n subtitleElement.style.color = accentColor;\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName} `;\n subtitleElement.style.color = accentColor;\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type} `;\n subtitleElement.style.color = accentColor;\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n /**\n * Show dropdown with nodes sorted by title.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showTitleDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedByTitle();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'title');\n }\n\n /**\n * Show dropdown with nodes sorted by execution order.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showExecOrderDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedByExecOrder();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'execOrder');\n }\n\n /**\n * Show dropdown with nodes sorted by ID.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showIdDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedById();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'id');\n }\n\n /**\n * Create and show the dropdown.\n */\n private createDropdown(\n nodes: NodeListEntry[] | NodeExecOrderEntry[],\n anchorElement: HTMLElement,\n type: 'title' | 'execOrder' | 'id'\n ): void {\n const dropdown = document.createElement('div');\n dropdown.className = `node-selector-dropdown theme-${this.stateManager.state.currentTheme}`;\n dropdown.style.cssText = `\n position: absolute;\n z-index: 100000;\n max-height: 300px;\n overflow-y: auto;\n background: var(--comfy-menu-bg, #2a2a2a);\n border: 1px solid var(--border-color, #444);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n min-width: 200px;\n max-width: 350px;\n `;\n\n // Build dropdown items\n nodes.forEach((node) => {\n const item = document.createElement('div');\n item.className = 'dropdown-item';\n item.style.cssText = `\n padding: 8px 12px;\n cursor: pointer;\n border-bottom: 1px solid var(--border-color, #333);\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n `;\n\n // Check if this is an exec order entry\n const isExecOrder = 'order' in node;\n\n if (isExecOrder) {\n const execNode = node as NodeExecOrderEntry;\n item.innerHTML = `\n #${execNode.order}\n ${execNode.title} (#${execNode.id})\n ${execNode.type}\n `;\n } else {\n item.innerHTML = `\n ${node.title} (#${node.id})\n ${node.type}\n `;\n }\n\n // Hover effect\n item.addEventListener('mouseenter', () => {\n item.style.background = 'var(--comfy-input-bg, #3a3a3a)';\n });\n item.addEventListener('mouseleave', () => {\n item.style.background = '';\n });\n\n // Selection handler\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.hideDropdown();\n\n // Set selected node in state\n this.stateManager.setSelectedNode(node.id);\n\n // Notify callback if set\n if (this.onNodeSelected) {\n this.onNodeSelected(node.id);\n }\n });\n\n dropdown.appendChild(item);\n });\n\n // Position dropdown below anchor\n document.body.appendChild(dropdown);\n const anchorRect = anchorElement.getBoundingClientRect();\n\n // Position below the anchor, aligned to left\n dropdown.style.top = `${anchorRect.bottom + 4}px`;\n dropdown.style.left = `${anchorRect.left}px`;\n\n // Adjust if dropdown would go off-screen\n const dropdownRect = dropdown.getBoundingClientRect();\n if (dropdownRect.right > window.innerWidth - 10) {\n dropdown.style.left = `${window.innerWidth - dropdownRect.width - 10}px`;\n }\n if (dropdownRect.bottom > window.innerHeight - 10) {\n // Show above instead\n dropdown.style.top = `${anchorRect.top - dropdownRect.height - 4}px`;\n }\n\n this.currentDropdown = dropdown;\n\n // Close on click outside\n const closeHandler = (e: MouseEvent) => {\n if (!dropdown.contains(e.target as Node) && !anchorElement.contains(e.target as Node)) {\n this.hideDropdown();\n document.removeEventListener('click', closeHandler);\n }\n };\n\n // Delay adding the listener to avoid immediate closure\n setTimeout(() => {\n document.addEventListener('click', closeHandler);\n }, 10);\n }\n\n /**\n * Hide the current dropdown.\n */\n hideDropdown(): void {\n if (this.currentDropdown && this.currentDropdown.parentNode) {\n this.currentDropdown.parentNode.removeChild(this.currentDropdown);\n this.currentDropdown = null;\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\nimport { Logger } from '../shared/logger';\nimport { INFO_PANEL_ID } from '../shared/constants';\nimport { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';\nimport {\n getCheckpointInfo,\n getImageInfo,\n getTextBoxContent,\n getImportantNodeParameters,\n type ImageInfoResult\n} from './NodeDataExtractor';\nimport { NodeSelector, type NodeListEntry, type NodeExecOrderEntry } from './NodeSelector';\n\ninterface InfoPanelElements {\n panel: HTMLDivElement | null;\n header: HTMLDivElement | null;\n content: HTMLDivElement | null;\n controls: HTMLDivElement | null;\n}\n\n/**\n * UI Manager class.\n * Handles all DOM manipulation and UI creation.\n */\nexport class UIManager {\n stateManager: StateManager;\n elements: InfoPanelElements;\n nodeSelector: NodeSelector;\n currentDropdown: HTMLDivElement | null = null;\n onNodeSelected: ((nodeId: number) => void) | null = null;\n\n constructor(stateManager: StateManager) {\n this.stateManager = stateManager;\n this.elements = {\n panel: null,\n header: null,\n content: null,\n controls: null\n };\n this.nodeSelector = new NodeSelector();\n\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = INFO_PANEL_ID;\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n \n \n `;\n\n // Content container\n this.elements.content = document.createElement(\"div\");\n this.elements.content.className = \"panel-content\";\n\n this.elements.panel.appendChild(this.elements.header);\n this.elements.panel.appendChild(this.elements.content);\n\n this.applyStyles();\n // Apply initial minimized state if set\n if (this.stateManager.state.isPanelMinimized) {\n this.elements.panel.classList.add('panel-minimized');\n }\n document.body.appendChild(this.elements.panel);\n\n // Hover-expand: Add/remove 'is-expanded' class, reposition, and resize font if needed\n let originalTop: number | null = null;\n let originalFontSize: number | null = null;\n const MIN_FONT_SIZE = 10; // Minimum font size in pixels\n const MARGIN = 20; // Minimum margin from viewport edges\n\n this.elements.panel.addEventListener('mouseenter', () => {\n // Sticky Mode (Persist): disable this behavior to prevent bouncing\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n return;\n }\n\n if (!this.stateManager.state.isPanelMinimized && this.elements.panel) {\n // Save original position\n originalTop = this.elements.panel.offsetTop;\n // Save original font size just in case, though we won't change it\n originalFontSize = parseFloat(getComputedStyle(this.elements.panel).fontSize);\n\n // Add expanded class\n this.elements.panel.classList.add('is-expanded');\n\n // Simple repositioning logic only (no font scaling)\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (!this.elements.panel) return;\n\n const viewportHeight = window.innerHeight;\n const rect = this.elements.panel.getBoundingClientRect();\n\n // Position the panel to fit (move up if needed)\n if (rect.bottom > viewportHeight - MARGIN) {\n const newTop = Math.max(MARGIN, originalTop! - (rect.bottom - viewportHeight + MARGIN));\n this.elements.panel.style.top = `${newTop}px`;\n }\n });\n });\n }\n });\n\n this.elements.panel.addEventListener('mouseleave', () => {\n // Sticky Mode (Persist): disable this behavior to prevent bouncing\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n return;\n }\n\n if (this.elements.panel) {\n this.elements.panel.classList.remove('is-expanded');\n\n // Restore original position\n if (originalTop !== null) {\n this.elements.panel.style.top = `${originalTop}px`;\n originalTop = null;\n }\n\n // Restore original font size (if it was somehow changed)\n if (originalFontSize !== null) {\n this.elements.panel.style.fontSize = `${originalFontSize}px`;\n originalFontSize = null;\n }\n\n // Clear any inline styles that might have been set\n this.elements.panel.style.transform = '';\n this.elements.panel.style.transformOrigin = '';\n }\n });\n\n // Add click event delegation for panel elements\n this.elements.panel.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n\n // Handle minimize button\n const minimizeBtn = target.closest('[data-action=\"minimize\"]');\n if (minimizeBtn) {\n this.stateManager.state.isPanelMinimized = !this.stateManager.state.isPanelMinimized;\n this.updateMinimizedState();\n return;\n }\n\n // Handle section header clicks for expand/collapse\n const sectionHeader = target.closest('.section-header') as HTMLElement;\n if (sectionHeader) {\n const sectionId = sectionHeader.getAttribute('data-section');\n // Skip node section (always expanded)\n if (sectionId && sectionId !== 'node') {\n const isExpanded = this.stateManager.state.expandedSections.has(sectionId);\n if (isExpanded) {\n this.stateManager.state.expandedSections.delete(sectionId);\n } else {\n this.stateManager.state.expandedSections.add(sectionId);\n }\n\n // Update visual state\n sectionHeader.classList.toggle('expanded', !isExpanded);\n const sectionContent = sectionHeader.nextElementSibling as HTMLElement;\n if (sectionContent && sectionContent.classList.contains('section-content')) {\n sectionContent.classList.toggle('expanded', !isExpanded);\n }\n }\n }\n });\n\n // Create floating controls after panel is in DOM\n if (!this.elements.controls) {\n this.createFloatingControls();\n // Update control states immediately after creation\n this.updateControlStates();\n }\n }\n\n /**\n * Create floating control buttons.\n */\n createFloatingControls(): void {\n this.elements.controls = document.createElement(\"div\");\n this.elements.controls.className = `floating-controls vertical-layout theme-${this.stateManager.state.currentTheme}`; // Default to vertical + theme\n\n /**\n * BUTTON MAPPING DOCUMENTATION:\n * =============================\n * Button 1 (Unlock Icon): data-action=\"pin\" → controls isPanelPinned\n * - Unpinned (default): Panel follows magnify glass\n * - Pinned: Panel stays at fixed position (unlocked from following glass)\n * \n * Button 2 (Pin Icon): data-action=\"lock\" → controls isPanelLocked\n * - Unlocked (default): Panel can be dragged\n * - Locked: Panel cannot be dragged (pinned in place)\n * \n * NOTE: The data-action names are counterintuitive but kept for backwards compatibility.\n * The ICONS correctly represent the functionality:\n * - Unlock icon = unlock from following glass\n * - Pin icon = pin position (prevent drag)\n */\n this.elements.controls.innerHTML = `\n \n \n \n \n \n \n \n \n \n \n `;\n\n // Insert before the panel in the document body, not as a child\n document.body.appendChild(this.elements.controls);\n\n // Hide and position off-screen initially - will be shown when magnify glass activates and positions them\n this.elements.controls.style.display = 'none';\n this.elements.controls.style.visibility = 'hidden';\n this.elements.controls.style.left = '-9999px';\n this.elements.controls.style.top = '-9999px';\n\n // Add click event delegation for control buttons\n this.elements.controls.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n const button = target.closest('button[data-action]') as HTMLButtonElement;\n if (!button) return;\n\n const action = button.getAttribute('data-action');\n Logger.debug(`Control button clicked: ${action}`);\n\n switch (action) {\n case 'pin':\n this.stateManager.togglePinning();\n this.updatePinnedState();\n this.updateControlStates();\n break;\n case 'lock':\n this.stateManager.state.isPanelLocked = !this.stateManager.state.isPanelLocked;\n this.updateControlStates();\n if (this.elements.panel) {\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n }\n break;\n case 'persist':\n this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"] = !this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n this.updateControlStates();\n this.applyStyles(); // Update persist-active class\n // Sync sidebar setting if needed (optional, sidebar updates on reopen usually)\n break;\n case 'toggle-panel':\n if (this.stateManager.state.isPanelVisible) {\n // User requested to hide the panel\n this.hide();\n\n // User Request: Disable sticky info when hiding via button\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"] = false;\n this.applyStyles(); // Update visual styles if necessary\n }\n } else {\n this.show();\n }\n this.updateControlStates();\n break;\n case 'toggle-glass':\n this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;\n\n // Link Glass Visibility to Panel Pinning\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass Hidden\n console.log(`[MagnifyGlass Debug] Glass Hidden. pinned=${this.stateManager.state.isPanelPinned}, autoPinned=${this.stateManager.state.isAutoPinned}`);\n // Only auto-pin if not already pinned by the user\n if (!this.stateManager.state.isPanelPinned) {\n if (this.elements.panel) {\n const rect = this.elements.panel.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n this.stateManager.state.isPanelPinned = true;\n this.stateManager.state.isAutoPinned = true; // Track that this was auto-pinned\n console.log(`[MagnifyGlass Debug] Auto-Pinned panel.`);\n }\n // Do NOT reset isPanelLocked here - preserve user's lock state\n } else {\n // Glass Shown\n console.log(`[MagnifyGlass Debug] Glass Shown. pinned=${this.stateManager.state.isPanelPinned}, autoPinned=${this.stateManager.state.isAutoPinned}`);\n // Only unpin if it was auto-pinned (not user-pinned)\n if (this.stateManager.state.isAutoPinned) {\n this.stateManager.state.isPanelPinned = false;\n this.stateManager.state.isAutoPinned = false;\n console.log(`[MagnifyGlass Debug] Auto-Unpinned panel.`);\n }\n // If user manually pinned, keep the panel pinned\n }\n\n this.updateControlStates();\n this.updatePinnedState(); // Update visual class\n\n // Toggle BOTH the visual visibility AND the rendering\n // This fully disables the offscreen renderer when preview is hidden\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.setGlassPreviewActive) {\n magnifyGlass.setGlassPreviewActive(this.stateManager.state.isGlassPreviewVisible);\n } else if (magnifyGlass && magnifyGlass.ui?.setPreviewVisibility) {\n // Fallback for backwards compatibility\n magnifyGlass.ui.setPreviewVisibility(this.stateManager.state.isGlassPreviewVisible);\n }\n break;\n case 'popout':\n // Toggle the pop-out tab\n const glass = (window as any).comfyUIMagnifyGlass;\n if (glass && glass.popOutManager) {\n glass.popOutManager.toggle();\n }\n break;\n case 'drag-glass':\n // Toggle glass drag mode\n const mglass = (window as any).comfyUIMagnifyGlass;\n if (mglass && mglass.state) {\n mglass.state.isDragModeEnabled = !mglass.state.isDragModeEnabled;\n this.updateControlStates();\n\n // Update UI to indicate drag mode\n if (mglass.ui && mglass.ui.setDragMode) {\n mglass.ui.setDragMode(mglass.state.isDragModeEnabled);\n }\n }\n break;\n case 'reset-glass':\n // Reset glass offsets\n const rglass = (window as any).comfyUIMagnifyGlass;\n if (rglass) {\n rglass.resetOffsets();\n Logger.debug(\"Reset glass position via hover controls\");\n\n // Disable drag mode if active\n if (rglass.state && rglass.state.isDragModeEnabled) {\n rglass.state.isDragModeEnabled = false;\n if (rglass.ui && rglass.ui.setDragMode) {\n rglass.ui.setDragMode(false);\n }\n this.updateControlStates();\n }\n }\n break;\n case 'toggle-cursor':\n const cglass = (window as any).comfyUIMagnifyGlass;\n if (cglass && cglass.config) {\n cglass.config.showCursorPreview = !cglass.config.showCursorPreview;\n cglass.updateMagnifiedView();\n this.updateControlStates();\n }\n break;\n case 'toggle-hold':\n this.stateManager.toggleHold();\n this.updateControlStates();\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = String(this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\");\n this.updateControlsLayout(controlsPosition);\n\n // Initial state update\n this.updateControlStates();\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const isGlassVisible = this.stateManager.state.isGlassPreviewVisible;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n // Hide unlock button when panel is hidden\n pinBtn.style.display = isPanelVisible ? 'flex' : 'none';\n\n // Disable lock button when glass is hidden (no point unlocking to follow invisible glass)\n if (!isGlassVisible) {\n pinBtn.disabled = true;\n pinBtn.style.opacity = '0.5';\n pinBtn.title = \"Cannot toggle lock when glass preview is hidden\";\n } else {\n pinBtn.disabled = false;\n pinBtn.style.opacity = '';\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel Position (Follow Glass)\" : \"Unlock Panel Position (Drag Inspector)\";\n }\n }\n\n if (lockBtn) {\n // Only show pin button when panel is visible AND unlocked from glass\n const showLockBtn = isPanelVisible && this.stateManager.state.isPanelPinned;\n lockBtn.style.display = showLockBtn ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Unpin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n const persistBtn = this.elements.controls.querySelector('[data-action=\"persist\"]') as HTMLButtonElement;\n if (persistBtn) {\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n persistBtn.classList.toggle('active', isPersistConfigured);\n persistBtn.title = isPersistConfigured ? \"Disable Sticky Info (S)\" : \"Enable Sticky Info (S)\";\n persistBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n\n const holdBtn = this.elements.controls.querySelector('[data-action=\"toggle-hold\"]') as HTMLButtonElement;\n if (holdBtn) {\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n const isHeld = this.stateManager.state.isInfoHeld;\n\n // Only show Hold button when Sticky Info (Persist) is enabled AND panel is visible\n holdBtn.style.display = (isPanelVisible && isPersistConfigured) ? 'flex' : 'none';\n\n holdBtn.classList.toggle('active', isHeld);\n // Toggle icon: Pause (to hold) vs Play (to resume)\n holdBtn.innerHTML = isHeld ? Icons.play : Icons.pause;\n holdBtn.title = isHeld ? \"Resume Info Update (P)\" : \"Pause Info Update (P)\";\n }\n\n if (visibilityBtn) {\n // Active means \"Panel is Visible\"\n visibilityBtn.classList.toggle('active', isPanelVisible);\n visibilityBtn.title = isPanelVisible ? \"Hide Panel\" : \"Show Panel\";\n\n // Disable hide button if glass is hidden (prevent hiding everything)\n if (!isGlassVisible) {\n visibilityBtn.disabled = true;\n visibilityBtn.style.opacity = '0.5';\n visibilityBtn.title = \"Cannot hide panel when glass preview is hidden\";\n } else {\n visibilityBtn.disabled = false;\n visibilityBtn.style.opacity = '';\n }\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', isGlassVisible);\n glassBtn.title = isGlassVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n\n // Only show glass toggle button if the inspector panel is visible\n glassBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n\n const cursorBtn = this.elements.controls.querySelector('[data-action=\"toggle-cursor\"]') as HTMLButtonElement;\n if (cursorBtn) {\n const cglass = (window as any).comfyUIMagnifyGlass;\n const showCursor = cglass?.config?.showCursorPreview || false;\n cursorBtn.classList.toggle('active', showCursor);\n cursorBtn.title = showCursor ? \"Hide Cursor Preview\" : \"Show Cursor Preview\";\n\n // Logic: Visible when glass is visible AND:\n // 1. Panel is NOT pinned (attached to glass) - original logic\n // 2. Sticky Info (Persist) is NOT enabled - user request\n const isPersistConfigured = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n\n cursorBtn.style.display = (isGlassVisible && !this.stateManager.state.isPanelPinned && !isPersistConfigured) ? 'flex' : 'none';\n }\n\n const dragGlassBtn = this.elements.controls.querySelector('[data-action=\"drag-glass\"]') as HTMLButtonElement;\n if (dragGlassBtn) {\n const mglass = (window as any).comfyUIMagnifyGlass;\n const isDragMode = mglass?.state?.isDragModeEnabled || false;\n dragGlassBtn.classList.toggle('active', isDragMode);\n dragGlassBtn.title = isDragMode ? \"Cancel Move Mode (H)\" : \"Move Glass Position (H)\";\n // Only show drag button when glass is visible (even if panel is hidden)\n dragGlassBtn.style.display = isGlassVisible ? 'flex' : 'none';\n }\n\n const resetGlassBtn = this.elements.controls.querySelector('[data-action=\"reset-glass\"]') as HTMLButtonElement;\n if (resetGlassBtn) {\n // Only show reset button when glass is visible (moves with drag/popout buttons)\n resetGlassBtn.style.display = isGlassVisible ? 'flex' : 'none';\n }\n\n const popoutBtn = this.elements.controls.querySelector('[data-action=\"popout\"]') as HTMLButtonElement;\n if (popoutBtn) {\n const glass = (window as any).comfyUIMagnifyGlass;\n const isOpen = glass?.popOutManager?.isPopOutOpen() || false;\n // Logger.debug(`[UIManager] Popout button update - Glass: ${!!glass}, IsOpen: ${isOpen}`);\n popoutBtn.classList.toggle('active', isOpen);\n popoutBtn.title = isOpen ? \"Close Pop-out Viewer\" : \"Open Pop-out Viewer\";\n }\n }\n\n /**\n * Update controls layout based on position setting.\n * @param position \n */\n updateControlsLayout(position: string): void {\n if (!this.elements.controls) return;\n\n // Remove existing layout classes\n this.elements.controls.classList.remove('horizontal-layout', 'vertical-layout');\n\n // Add appropriate layout class\n if (['top', 'bottom'].includes(position)) {\n this.elements.controls.classList.add('horizontal-layout');\n } else {\n this.elements.controls.classList.add('vertical-layout');\n }\n }\n\n /**\n * Load a Google Font dynamically.\n * @param fontName - Name of the font to load\n */\n loadGoogleFont(fontName: string): void {\n // Skip if already loaded\n const linkId = `google-font-${fontName.replace(/\\s+/g, '-').toLowerCase()}`;\n if (document.getElementById(linkId)) return;\n\n // Create link element for Google Fonts\n const link = document.createElement('link');\n link.id = linkId;\n link.rel = 'stylesheet';\n link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontName)}:wght@400;500;600;700&display=swap`;\n document.head.appendChild(link);\n\n Logger.debug(`[UIManager] Loaded Google Font: ${fontName}`);\n }\n\n /**\n * Apply current styles to elements.\n */\n applyStyles(): void {\n if (!this.elements.panel) return;\n\n const settings = this.stateManager.state.settings;\n\n // Debug Log\n console.log(`[UIManager] applyStyles - MaxHeight: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}`);\n\n // 1. Apply dimensions\n this.elements.panel.style.width = `${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px`;\n // Set max-height via CSS variable so CSS :hover rules can override\n const maxHeight = settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"];\n this.elements.panel.style.setProperty('--panel-max-height', `${maxHeight}px`);\n this.elements.panel.style.height = 'auto';\n\n // 2. Apply static styles (only needed once or if overwritten, but safe to set)\n this.elements.panel.style.position = 'absolute';\n this.elements.panel.style.zIndex = '99999';\n this.elements.panel.style.transform = 'translateY(-10px)';\n this.elements.panel.style.pointerEvents = 'auto';\n this.elements.panel.style.userSelect = 'none';\n\n // Apply Font Family\n const fontFamily = settings[\"🔍MagnifyGlass.InfoPanelFontFamily\"] as string || \"System Default\";\n if (fontFamily === \"System Default\" || fontFamily === \"system-ui\") {\n this.elements.panel.style.fontFamily = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\";\n } else if (fontFamily === \"monospace\") {\n this.elements.panel.style.fontFamily = \"'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco, monospace\";\n } else {\n // Load Google Font dynamically\n this.loadGoogleFont(fontFamily);\n this.elements.panel.style.fontFamily = `'${fontFamily}', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif`;\n }\n\n // Apply Font Size\n const fontSize = settings[\"🔍MagnifyGlass.InfoPanelFontSize\"] || 14;\n this.elements.panel.style.fontSize = `${fontSize}px`;\n\n // 3. Apply transition\n this.elements.panel.style.transition = 'none';\n\n // 4. Apply Custom Colors (CSS Variables)\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"] as string | undefined;\n if (textColor && typeof textColor === 'string') {\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] as string | undefined;\n if (accentColor && typeof accentColor === 'string') {\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // 5. Apply Opacity\n // Logic: If visible, use setting. If hidden, use 0.\n // We do NOT touch 'display' here to avoid hiding it if it's currently shown.\n // 'display' is managed by show() / hide() methods exclusively.\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = Number(settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n } else {\n this.elements.panel.style.opacity = '0';\n }\n\n // 6. Apply Persist Mode Class\n const isPersist = !!settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n if (isPersist) {\n this.elements.panel.classList.add('persist-active');\n } else {\n this.elements.panel.classList.remove('persist-active');\n }\n }\n\n /**\n * Show the panel.\n */\n show(): void {\n if (!this.elements.panel) return;\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n this.elements.panel.style.display = \"block\";\n // Controls are shown by positionFloatingControls() after proper positioning\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = Number(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n this.updateControlStates();\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n this.elements.panel.classList.remove('visible');\n if (this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n }\n this.stateManager.state.isPanelVisible = false;\n this.updateControlStates();\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n if (this.elements.controls) {\n this.elements.controls.className = this.elements.controls.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"] && info.cursor && info.cursor.canvas) {\n const inspectorContent = [\n { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` },\n { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` },\n { label: 'Magnifier Zoom', value: `${info.magnifier.zoomFactor}×` }\n ];\n\n sections.push({\n id: 'inspector',\n icon: Icons.info,\n title: 'Inspector',\n content: inspectorContent\n });\n }\n\n // Media section\n if (info.media) {\n const mediaContent = [\n { label: 'Type', value: info.media.tagName },\n { label: 'Source', value: info.media.src }\n ];\n\n if (info.media.naturalSize) {\n mediaContent.push({ label: 'Natural', value: info.media.naturalSize });\n }\n\n sections.push({\n id: 'media',\n icon: Icons.camera,\n title: 'Media',\n badge: info.media.tagName,\n content: mediaContent\n });\n }\n\n // Node Details section\n if (info.hoveredNode) {\n const nodeContent: any[] = []; // Initial Node details\n nodeContent.push({\n label: 'Title',\n value: `${info.hoveredNode.title || 'Untitled'} (#${info.hoveredNode.id})`,\n clickable: 'title'\n });\n if (info.hoveredNode.executionOrder !== undefined) {\n nodeContent.push({ label: 'Exec Order', value: info.hoveredNode.executionOrder, clickable: 'execOrder' });\n }\n\n // Add Zoom to Node button\n nodeContent.push({\n label: 'Location',\n value: `${Icons.focus} Focus Node`,\n clickable: 'zoom',\n nodeId: info.hoveredNode.id\n });\n\n // Add category if available\n if (info.hoveredNode.category) {\n nodeContent.push({ label: 'Category', value: info.hoveredNode.category });\n }\n\n // Add python module path if available\n if (info.hoveredNode.pythonModule) {\n const path = info.hoveredNode.pythonModule.replace(/\\./g, '/') + '.py';\n nodeContent.push({ label: 'Path', value: path });\n }\n\n // Check if this is a complex node that shows all widgets\n const nodeType = info.hoveredNode.type ? info.hoveredNode.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = info.hoveredNode.type && (\n nodeType.includes('ksampler') ||\n nodeType.includes('sampler') ||\n nodeType.includes('k_samplers') ||\n nodeType.includes('checkpoint') ||\n nodeType.includes('model') ||\n nodeType.includes('lora') ||\n nodeType.includes('controlnet') ||\n nodeType.includes('advanced') ||\n nodeType.includes('detailer') ||\n nodeType.includes('inpaint') ||\n nodeType.includes('upscale') ||\n nodeType.includes('clip') ||\n nodeType.includes('text') ||\n nodeType.includes('encode')\n ) && !isSaveNode;\n\n if (!showAllWidgets) {\n // Add specific extractions for simple nodes\n const checkpoint = getCheckpointInfo(info.hoveredNode);\n if (checkpoint) {\n nodeContent.push({ label: 'Model', value: checkpoint });\n }\n\n const imgInfo = getImageInfo(info.hoveredNode);\n if (imgInfo) {\n if (typeof imgInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imgInfo });\n } else if (typeof imgInfo === 'object' && imgInfo !== null) {\n const imgResult = imgInfo as ImageInfoResult;\n nodeContent.push({ label: 'Image Size', value: `${imgResult.width}×${imgResult.height}` });\n if (imgResult.src) {\n nodeContent.push({ label: 'Image Source', value: imgResult.src });\n }\n }\n }\n\n const textContent = getTextBoxContent(info.hoveredNode);\n if (textContent) {\n nodeContent.push({ label: 'Text', value: textContent });\n }\n }\n\n const importantParameters = getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: Icons.box,\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n \n
${Icons.mapPin}
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n \n \n
\n
\n ${section.content.map((item: any) => {\n const value = formatValue(item.value, item.label);\n const valueClass = getValueClass(item.value);\n const valueAttributes = getValueAttributes(item.value);\n const clickableAttr = item.clickable ? `data-clickable=\"${item.clickable}\"` : '';\n const nodeIdAttr = item.nodeId !== undefined ? `data-node-id=\"${item.nodeId}\"` : '';\n const clickableClass = item.clickable ? 'clickable-row' : '';\n // Only show dropdown arrow for actual dropdowns, not actions like zoom\n const dropdownIcon = (item.clickable && item.clickable !== 'zoom') ? '
▼' : '';\n // Show copy button for string values that are long enough to be worth copying\n const rawValue = String(item.value || '');\n const showCopyBtn = !item.clickable && rawValue.length > 3 && typeof item.value === 'string';\n const copyBtnHtml = showCopyBtn ? `\n
` : '';\n return `\n
\n ${item.label}\n ${value}${dropdownIcon}\n ${copyBtnHtml}\n
`;\n }).join('')}\n
\n
\n
`).join('');\n\n // Add click handlers for clickable rows\n this.attachDropdownClickHandlers();\n }\n\n /**\n * Attach click handlers to clickable dropdown rows.\n */\n attachDropdownClickHandlers(): void {\n if (!this.elements.content) return;\n\n const clickableRows = this.elements.content.querySelectorAll('[data-clickable]');\n clickableRows.forEach(row => {\n const clickableType = (row as HTMLElement).dataset.clickable;\n\n row.addEventListener('click', (e) => {\n e.stopPropagation();\n\n if (clickableType === 'title') {\n this.showTitleDropdown(row as HTMLElement);\n } else if (clickableType === 'execOrder') {\n this.showExecOrderDropdown(row as HTMLElement);\n } else if (clickableType === 'id') {\n this.showIdDropdown(row as HTMLElement);\n } else if (clickableType === 'zoom') {\n const nodeId = (row as HTMLElement).dataset.nodeId;\n if (nodeId) {\n const app = (window as any).app;\n const node = app.graph.getNodeById(parseInt(nodeId));\n if (node && app.canvas) {\n app.canvas.centerOnNode(node);\n }\n }\n }\n });\n\n // Add hover effect\n row.addEventListener('mouseenter', () => {\n (row as HTMLElement).style.background = 'var(--comfy-input-bg, rgba(255,255,255,0.05))';\n });\n row.addEventListener('mouseleave', () => {\n (row as HTMLElement).style.background = '';\n });\n });\n\n // Add click handlers for copy buttons\n const copyButtons = this.elements.content.querySelectorAll('.copy-btn');\n copyButtons.forEach(btn => {\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n const value = (btn as HTMLElement).dataset.copyValue || '';\n navigator.clipboard.writeText(value).then(() => {\n // Show success feedback\n btn.classList.add('copied');\n const originalHtml = btn.innerHTML;\n btn.innerHTML = ``;\n setTimeout(() => {\n btn.innerHTML = originalHtml;\n btn.classList.remove('copied');\n }, 1500);\n }).catch(err => {\n console.error('Failed to copy:', err);\n });\n });\n });\n }\n\n /**\n * Update section expansion states.\n */\n updateSectionStates(): void {\n if (!this.elements.content) return;\n\n const sections = this.elements.content.querySelectorAll('.info-section');\n sections.forEach(section => {\n const sectionId = (section as HTMLElement).dataset.section;\n if (!sectionId) return;\n\n const header = section.querySelector('.section-header');\n const content = section.querySelector('.section-content');\n\n if (header && content) {\n if (this.stateManager.state.expandedSections.has(sectionId)) {\n header.classList.add('expanded');\n content.classList.add('expanded');\n } else {\n header.classList.remove('expanded');\n content.classList.remove('expanded');\n }\n }\n });\n }\n\n /**\n * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = String(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] || '');\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title} `;\n subtitleElement.style.color = accentColor;\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName} `;\n subtitleElement.style.color = accentColor;\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type} `;\n subtitleElement.style.color = accentColor;\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n /**\n * Show dropdown with nodes sorted by title.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showTitleDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedByTitle();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'title');\n }\n\n /**\n * Show dropdown with nodes sorted by execution order.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showExecOrderDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedByExecOrder();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'execOrder');\n }\n\n /**\n * Show dropdown with nodes sorted by ID.\n * @param anchorElement - Element to anchor the dropdown to\n */\n showIdDropdown(anchorElement: HTMLElement): void {\n this.hideDropdown();\n\n const nodes = this.nodeSelector.getNodesSortedById();\n if (nodes.length === 0) return;\n\n this.createDropdown(nodes, anchorElement, 'id');\n }\n\n /**\n * Create and show the dropdown.\n */\n private createDropdown(\n nodes: NodeListEntry[] | NodeExecOrderEntry[],\n anchorElement: HTMLElement,\n type: 'title' | 'execOrder' | 'id'\n ): void {\n const dropdown = document.createElement('div');\n dropdown.className = `node-selector-dropdown theme-${this.stateManager.state.currentTheme}`;\n dropdown.style.cssText = `\n position: absolute;\n z-index: 100000;\n max-height: 300px;\n overflow-y: auto;\n background: var(--comfy-menu-bg, #2a2a2a);\n border: 1px solid var(--border-color, #444);\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n min-width: 200px;\n max-width: 350px;\n `;\n\n // Build dropdown items\n nodes.forEach((node) => {\n const item = document.createElement('div');\n item.className = 'dropdown-item';\n item.style.cssText = `\n padding: 8px 12px;\n cursor: pointer;\n border-bottom: 1px solid var(--border-color, #333);\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 13px;\n `;\n\n // Check if this is an exec order entry\n const isExecOrder = 'order' in node;\n\n if (isExecOrder) {\n const execNode = node as NodeExecOrderEntry;\n item.innerHTML = `\n #${execNode.order}\n ${execNode.title} (#${execNode.id})\n ${execNode.type}\n `;\n } else {\n item.innerHTML = `\n ${node.title} (#${node.id})\n ${node.type}\n `;\n }\n\n // Hover effect\n item.addEventListener('mouseenter', () => {\n item.style.background = 'var(--comfy-input-bg, #3a3a3a)';\n });\n item.addEventListener('mouseleave', () => {\n item.style.background = '';\n });\n\n // Selection handler\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.hideDropdown();\n\n // Set selected node in state\n this.stateManager.setSelectedNode(node.id);\n\n // Notify callback if set\n if (this.onNodeSelected) {\n this.onNodeSelected(node.id);\n }\n });\n\n dropdown.appendChild(item);\n });\n\n // Position dropdown below anchor\n document.body.appendChild(dropdown);\n const anchorRect = anchorElement.getBoundingClientRect();\n\n // Position below the anchor, aligned to left\n dropdown.style.top = `${anchorRect.bottom + 4}px`;\n dropdown.style.left = `${anchorRect.left}px`;\n\n // Adjust if dropdown would go off-screen\n const dropdownRect = dropdown.getBoundingClientRect();\n if (dropdownRect.right > window.innerWidth - 10) {\n dropdown.style.left = `${window.innerWidth - dropdownRect.width - 10}px`;\n }\n if (dropdownRect.bottom > window.innerHeight - 10) {\n // Show above instead\n dropdown.style.top = `${anchorRect.top - dropdownRect.height - 4}px`;\n }\n\n this.currentDropdown = dropdown;\n\n // Close on click outside\n const closeHandler = (e: MouseEvent) => {\n if (!dropdown.contains(e.target as Node) && !anchorElement.contains(e.target as Node)) {\n this.hideDropdown();\n document.removeEventListener('click', closeHandler);\n }\n };\n\n // Delay adding the listener to avoid immediate closure\n setTimeout(() => {\n document.addEventListener('click', closeHandler);\n }, 10);\n }\n\n /**\n * Hide the current dropdown.\n */\n hideDropdown(): void {\n if (this.currentDropdown && this.currentDropdown.parentNode) {\n this.currentDropdown.parentNode.removeChild(this.currentDropdown);\n this.currentDropdown = null;\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/magnify-glass/MagnifyGlass.js b/web/magnify-glass/MagnifyGlass.js
index ce03e59..26da80b 100644
--- a/web/magnify-glass/MagnifyGlass.js
+++ b/web/magnify-glass/MagnifyGlass.js
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { findLiteGraphCanvas, rectsOverlap } from "../shared/utils.js";
-import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from "../shared/constants.js";
+import { INFO_PANEL_ID, DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from "../shared/constants.js";
import { registerGlassSettings } from "../shared/settings/glassSettings.js";
import "/scripts/app.js";
import { registerAccessibilitySettings } from "../shared/settings/accessibilitySettings.js";
@@ -86,8 +86,7 @@ class MagnifyGlass {
toggle() {
const state = this.state;
if (state.active) {
- state.active = false;
- this.ui.hide();
+ this.forceHideAllComponents();
} else {
state.active = true;
this.ui.show();
@@ -96,6 +95,30 @@ class MagnifyGlass {
}
}
}
+ /**
+ * Force hide all components including extensions (Info Panel, etc).
+ * This overcomes the "pinned" state of the info panel when the main tool is deactivated.
+ */
+ forceHideAllComponents() {
+ this.state.active = false;
+ this.ui.hide();
+ const panelEl = document.getElementById(INFO_PANEL_ID);
+ if (panelEl) {
+ panelEl.style.display = "none";
+ panelEl.style.opacity = "0";
+ }
+ const extensions = window.comfyUIMagnifyGlassExtensions;
+ if (extensions && extensions.length > 0) {
+ extensions.forEach((extension) => {
+ if (extension && extension.uiManager && typeof extension.uiManager.hide === "function") {
+ extension.uiManager.hide();
+ if (extension.uiManager.elements && extension.uiManager.elements.panel) {
+ extension.uiManager.elements.panel.style.display = "none";
+ }
+ }
+ });
+ }
+ }
/**
* Set glass preview visibility WITHOUT affecting info panel.
* This is used by the toggle-glass hover control button.
diff --git a/web/magnify-glass/MagnifyGlass.js.map b/web/magnify-glass/MagnifyGlass.js.map
index 0b9151d..4306f0e 100644
--- a/web/magnify-glass/MagnifyGlass.js.map
+++ b/web/magnify-glass/MagnifyGlass.js.map
@@ -1 +1 @@
-{"version":3,"file":"MagnifyGlass.js","sources":["../../src/magnify-glass/MagnifyGlass.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Main Class (TypeScript)\n * \n * Orchestrates all components of the magnifying glass.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\nimport { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils';\nimport { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';\nimport { registerGlassSettings, registerAccessibilitySettings } from '../shared/settings';\nimport { ConfigManager } from './ConfigManager';\nimport { MagnifierState } from './MagnifierState';\nimport { UiManager } from './UiManager';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { DebugManager } from './DebugManager';\nimport { EventHandler } from './EventHandler';\nimport { PopOutManager } from './PopOutManager';\nimport { OffscreenRenderer } from './OffscreenRenderer';\n\n// External ComfyUI globals\ndeclare const app: ComfyApp;\ndeclare const LiteGraph: unknown;\n\n/**\n * Main MagnifyGlass class.\n * Orchestrates all components and provides the main API.\n */\nexport class MagnifyGlass {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n renderer: WebGLRenderer | null;\n debugger: DebugManager;\n eventHandler: EventHandler;\n popOutManager: PopOutManager;\n offscreenRenderer: OffscreenRenderer | null;\n\n /** The LiteGraph canvas */\n litegraphCanvas: HTMLCanvasElement | null;\n\n /** Last known mouse position for better initial positioning */\n lastKnownMousePosition: { x: number; y: number };\n\n /** Whether we're currently over media (for info panel) */\n isOverMedia: boolean;\n\n /** Current media element under cursor */\n currentMediaElement: HTMLImageElement | HTMLVideoElement | null;\n\n constructor() {\n this.config = new ConfigManager();\n this.state = new MagnifierState();\n this.popOutManager = new PopOutManager();\n this.ui = new UiManager(\n this.config,\n this.state,\n () => this.toggle()\n );\n\n // onStateChange is handled by InfoPanel\n\n\n this.renderer = null;\n this.offscreenRenderer = null;\n this.debugger = new DebugManager(this.config, this.state, this.ui);\n this.eventHandler = new EventHandler(this);\n\n // The LiteGraph canvas\n this.litegraphCanvas = null;\n\n // Last known mouse position for better initial positioning\n this.lastKnownMousePosition = { x: 0, y: 0 };\n\n // Media tracking\n this.isOverMedia = false;\n this.currentMediaElement = null;\n }\n\n /**\n * Initialize the magnifying glass.\n */\n init(): void {\n if (typeof LiteGraph === 'undefined' || typeof app === 'undefined' || !app.canvas) {\n this.debugger.log(\"LiteGraph or app not ready, retrying in 100ms.\");\n setTimeout(() => this.init(), 100);\n return;\n }\n this.debugger.log(\"LiteGraph and app ready.\");\n\n\n\n // Load saved offsets first\n this.config.loadSavedOffsets();\n\n // Print detailed canvas information\n\n\n // Create UI elements\n this.ui.createElements();\n\n // Ensure glass is validly positioned (within viewport) right away\n // This handles cases where config has saved positions from a different monitor size\n this.ui.updateResponsivePosition();\n\n // Setup WebGL renderer\n this.renderer = new WebGLRenderer(this.config, this.state, this.ui);\n if (!this.renderer.isValid()) {\n this.ui.cleanup();\n return;\n }\n\n // Find the LiteGraph canvas\n this.litegraphCanvas = findLiteGraphCanvas();\n this.debugger.log(\"LiteGraph canvas found:\", this.litegraphCanvas);\n if (!this.litegraphCanvas) {\n this.debugger.error(\"Could not find LiteGraph canvas. Magnifier will not work.\");\n this.ui.cleanup();\n return;\n }\n\n // Initialize offscreen renderer for high-res magnification\n this.offscreenRenderer = new OffscreenRenderer(this.config, this.state);\n\n // Attach event handlers\n this.eventHandler.attachListeners();\n\n // Register settings with ComfyUI\n registerGlassSettings(this);\n registerAccessibilitySettings(this);\n\n this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate.`);\n }\n\n /**\n * Toggle the magnifying glass active state.\n */\n toggle(): void {\n const state = this.state;\n\n if (state.active) {\n // TURNING OFF - Force hide everything\n state.active = false;\n this.ui.hide();\n } else {\n // TURNING ON\n state.active = true;\n this.ui.show();\n if (this.eventHandler) {\n this.eventHandler.updateInitialPosition();\n }\n }\n }\n\n /**\n * Set glass preview visibility WITHOUT affecting info panel.\n * This is used by the toggle-glass hover control button.\n * When hidden, the renderer stops completely to save performance.\n * When shown, the renderer resumes.\n * \n * @param visible - true to show and enable rendering, false to hide and disable\n */\n setGlassPreviewActive(visible: boolean): void {\n // Track this state internally\n this.state.isPreviewHidden = !visible;\n\n if (visible) {\n // Show the glass preview\n this.ui.setPreviewVisibility(true);\n // Resume rendering on next mouse move\n } else {\n // Hide the glass preview visually\n this.ui.setPreviewVisibility(false);\n // The rendering will be skipped in updateMagnifiedView due to isPreviewHidden check\n }\n }\n\n /**\n * Check if the glass preview is visible.\n * Returns false if hidden via hover controls.\n */\n private isGlassPreviewVisible(): boolean {\n // Check info panel extension state for isGlassPreviewVisible\n const extensions = (window as any).comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n const infoPanel = extensions[0];\n if (infoPanel?.stateManager?.state) {\n return infoPanel.stateManager.state.isGlassPreviewVisible !== false;\n }\n }\n // Default to visible if we can't find the state\n return true;\n }\n\n /**\n * Update the magnified view.\n */\n updateMagnifiedView(): void {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n return;\n }\n\n // Skip rendering if glass preview is hidden via hover controls\n // This prevents double FPS when only the info panel is active\n if (this.state.isPreviewHidden) {\n return;\n }\n\n // Get canvas transformation info\n this.updateCanvasTransformation();\n\n // Calculate the source region\n this.calculateSourceRegion();\n\n // Schedule the rendering operation\n if (!this.state.isRenderScheduled) {\n this.state.isRenderScheduled = true;\n requestAnimationFrame(() => {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n this.state.isRenderScheduled = false;\n return;\n }\n\n // Try virtual zoom capture for high-res magnification\n // This temporarily sets LiteGraph to 100% zoom, captures clean pixels,\n // then restores the original zoom level\n let sourceCanvas: HTMLCanvasElement = this.litegraphCanvas;\n if (this.offscreenRenderer && this.offscreenRenderer.isAvailable()) {\n const highResCanvas = this.offscreenRenderer.renderHighResRegion(this.litegraphCanvas);\n if (highResCanvas) {\n sourceCanvas = highResCanvas;\n }\n }\n\n // Render the magnified view\n this.renderer.render(sourceCanvas);\n\n // Update debug visualization\n\n\n // Render HTML overlays for text, video, and image widgets\n this.renderHtmlOverlays();\n\n // Send frame to pop-out tab if open\n if (this.popOutManager.isPopOutOpen() && this.ui.glassCanvas) {\n this.popOutManager.sendFrame(this.ui.glassCanvas);\n }\n\n this.state.isRenderScheduled = false;\n });\n }\n }\n\n /**\n * Update canvas transformation state.\n */\n updateCanvasTransformation(): void {\n this.state.canvasScale = 1.0;\n this.state.canvasOffsetX = 0;\n this.state.canvasOffsetY = 0;\n\n if (app?.canvas) {\n const ds = (app.canvas as any).ds;\n if (ds) {\n if (typeof ds.scale === 'number') {\n this.state.canvasScale = ds.scale;\n }\n\n if (ds.offset) {\n this.state.canvasOffsetX = ds.offset[0] || 0;\n this.state.canvasOffsetY = ds.offset[1] || 0;\n }\n }\n }\n }\n\n /**\n * Calculate the source region for magnification.\n */\n calculateSourceRegion(): void {\n const cursorPixelX = this.state.x;\n const cursorPixelY = this.state.y;\n const canvasScale = this.state.canvasScale;\n const canvasOffsetX = this.state.canvasOffsetX;\n const canvasOffsetY = this.state.canvasOffsetY;\n\n if (canvasScale === 0) return;\n if (!this.litegraphCanvas) return;\n\n // Get DPR (Device Pixel Ratio) - relationship between Backing Store pixels and CSS pixels\n const rect = this.litegraphCanvas.getBoundingClientRect();\n const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;\n\n // Convert cursor Backing pixels to CSS pixels (Screen pixels)\n // state.x/y are in Backing Pixels (set in EventHandler)\n const cursorCssX = cursorPixelX / dpr;\n const cursorCssY = cursorPixelY / dpr;\n\n // Convert cursor CSS pixels to LiteGraph graph coordinates\n // ds.offset and ds.scale operate in CSS pixel space\n const cursorGraphX = (cursorCssX - canvasOffsetX) / canvasScale;\n const cursorGraphY = (cursorCssY - canvasOffsetY) / canvasScale;\n\n // Apply manual offset (in Graph Units - arbitrary units)\n const targetGraphCenterX = cursorGraphX + this.config.offsetX;\n const targetGraphCenterY = cursorGraphY + this.config.offsetY;\n\n // Calculate source dimensions in graph units\n const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n\n // Calculate source top-left corner in Graph Coordinates\n const sourceGraphX = targetGraphCenterX - (sourceGraphWidth / 2);\n const sourceGraphY = targetGraphCenterY - (sourceGraphHeight / 2);\n\n // Convert back to Canvas Backing Pixels for the source rectangle\n // 1. Convert Graph -> CSS: (Graph * Scale) + Offset\n // 2. Convert CSS -> Backing: CSS * DPR\n const sourceCssX = (sourceGraphX * canvasScale) + canvasOffsetX;\n const sourceCssY = (sourceGraphY * canvasScale) + canvasOffsetY;\n const sourceCssWidth = sourceGraphWidth * canvasScale;\n const sourceCssHeight = sourceGraphHeight * canvasScale;\n\n this.state.sourceX = sourceCssX * dpr;\n this.state.sourceY = sourceCssY * dpr;\n this.state.sourceWidth = sourceCssWidth * dpr;\n this.state.sourceHeight = sourceCssHeight * dpr;\n }\n\n /**\n * Render HTML overlays for text and media in the magnified view.\n */\n renderHtmlOverlays(): void {\n const graph = (app as any).graph;\n if (!this.state.active || !this.ui.htmlOverlayContainer || !graph || !this.litegraphCanvas) {\n if (this.ui.htmlOverlayContainer) this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n this.ui.htmlOverlayContainer.innerHTML = '';\n\n const magnifyRect: Rectangle = {\n x: this.state.sourceX,\n y: this.state.sourceY,\n width: this.state.sourceWidth,\n height: this.state.sourceHeight\n };\n\n const nodes: ComfyNode[] = graph._nodes;\n if (!nodes) return;\n\n for (const node of nodes) {\n const widgets = (node as any).widgets;\n if (!widgets) continue;\n\n for (const widget of widgets) {\n let isTextElement = false;\n let isVideoElement = false;\n let isImageElement = false;\n let elementToProcess: HTMLElement | null = null;\n\n if (widget.element) {\n const element = widget.element as HTMLElement;\n // Skip text elements - now rendered natively on canvas via OffscreenRenderer\n if (widget.type === \"text\" || widget.type === \"string\" || element.tagName === 'TEXTAREA') {\n // isTextElement = true; // DISABLED - using native canvas rendering\n // elementToProcess = element;\n } else if (element.tagName === 'VIDEO') {\n isVideoElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'IMG') {\n isImageElement = true;\n elementToProcess = element;\n } else {\n const potentialVideo = element.querySelector('video');\n if (potentialVideo) {\n isVideoElement = true;\n elementToProcess = potentialVideo;\n } else {\n const potentialImage = element.querySelector('img');\n if (potentialImage) {\n isImageElement = true;\n elementToProcess = potentialImage;\n }\n }\n }\n }\n\n if (elementToProcess && (isTextElement || isVideoElement || isImageElement)) {\n const widgetRect = elementToProcess.getBoundingClientRect();\n const canvasRect = this.litegraphCanvas!.getBoundingClientRect();\n\n // DPR = backing pixels / CSS pixels\n const dpr = canvasRect.width > 0 ? (this.litegraphCanvas!.width / canvasRect.width) : 1;\n const currentScale = this.state.canvasScale;\n const isVirtualZoomMode = currentScale < 0.7;\n\n // Widget position in CSS coordinates relative to canvas\n const widgetCssX = widgetRect.left - canvasRect.left;\n const widgetCssY = widgetRect.top - canvasRect.top;\n const widgetCssWidth = widgetRect.width;\n const widgetCssHeight = widgetRect.height;\n\n // Pivot point (mouse position) in CSS coordinates\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n let finalWidgetCssX: number;\n let finalWidgetCssY: number;\n let finalWidgetCssWidth: number;\n let finalWidgetCssHeight: number;\n\n if (isVirtualZoomMode) {\n // Virtual Zoom: Transform widget position to match virtual 1.0 scale capture\n // Formula: virtualCss = (widgetCss - pivotCss) / currentScale + pivotCss\n finalWidgetCssX = (widgetCssX - pivotCssX) / currentScale + pivotCssX;\n finalWidgetCssY = (widgetCssY - pivotCssY) / currentScale + pivotCssY;\n // Size also scales inversely\n finalWidgetCssWidth = widgetCssWidth / currentScale;\n finalWidgetCssHeight = widgetCssHeight / currentScale;\n } else {\n // Direct Capture: Use actual positions\n finalWidgetCssX = widgetCssX;\n finalWidgetCssY = widgetCssY;\n finalWidgetCssWidth = widgetCssWidth;\n finalWidgetCssHeight = widgetCssHeight;\n }\n\n // Convert to backing pixels for comparison with magnifyRect\n const widgetCanvasX = finalWidgetCssX * dpr;\n const widgetCanvasY = finalWidgetCssY * dpr;\n const widgetCanvasWidth = finalWidgetCssWidth * dpr;\n const widgetCanvasHeight = finalWidgetCssHeight * dpr;\n\n const widgetSourceRect: Rectangle = {\n x: widgetCanvasX,\n y: widgetCanvasY,\n width: widgetCanvasWidth,\n height: widgetCanvasHeight\n };\n\n if (rectsOverlap(magnifyRect, widgetSourceRect)) {\n const clonedElement = elementToProcess.cloneNode(true) as HTMLElement;\n clonedElement.style.position = 'absolute';\n clonedElement.style.pointerEvents = 'none';\n\n if (isTextElement) {\n clonedElement.style.backgroundColor = elementToProcess.style.backgroundColor || '#222';\n clonedElement.style.color = elementToProcess.style.color || '#DDD';\n clonedElement.style.border = elementToProcess.style.border || '1px solid #555';\n // Hide scrollbar while keeping content visible\n clonedElement.style.overflow = 'hidden';\n (clonedElement as HTMLInputElement).disabled = true;\n\n // Dual adaptive font sizing:\n // - When zoomed OUT (below 50%): shrink text to fit all content\n // - When zoomed IN (above 50%): grow text for better readability\n const canvasScale = this.state.canvasScale;\n const originalFontSize = parseFloat(window.getComputedStyle(elementToProcess).fontSize);\n const threshold = 0.5; // 50% zoom threshold\n\n let adaptedFontSize: number;\n if (canvasScale < threshold) {\n // Zoomed out: shrink text aggressively to fit content\n // Scale from threshold down to minimum\n const shrinkFactor = canvasScale / threshold;\n adaptedFontSize = Math.max(6, originalFontSize * shrinkFactor * 0.8);\n } else {\n // Zoomed in: grow text for readability\n // Scale from 100% at threshold up to 150% at 100%+ zoom\n const growFactor = 1 + ((canvasScale - threshold) / (1 - threshold)) * 0.5;\n adaptedFontSize = originalFontSize * Math.min(1.5, growFactor);\n }\n\n clonedElement.style.fontSize = `${adaptedFontSize}px`;\n clonedElement.style.lineHeight = canvasScale < threshold ? '1.2' : '1.4';\n } else if (isVideoElement) {\n const video = clonedElement as HTMLVideoElement;\n const originalVideo = elementToProcess as HTMLVideoElement;\n video.src = originalVideo.src;\n video.autoplay = originalVideo.autoplay;\n video.loop = originalVideo.loop;\n video.preload = originalVideo.preload;\n video.crossOrigin = originalVideo.crossOrigin;\n video.muted = true;\n if (!originalVideo.paused) {\n video.play().catch(e => console.warn(\"Magnify Glass: Cloned video play failed\", e));\n }\n video.currentTime = originalVideo.currentTime;\n } else if (isImageElement) {\n const img = clonedElement as HTMLImageElement;\n const originalImg = elementToProcess as HTMLImageElement;\n img.src = originalImg.src;\n img.alt = originalImg.alt;\n }\n\n const relativeX = widgetSourceRect.x - magnifyRect.x;\n const relativeY = widgetSourceRect.y - magnifyRect.y;\n const magnifiedX = relativeX * this.config.zoomFactor;\n const magnifiedY = relativeY * this.config.zoomFactor;\n\n clonedElement.style.left = `${magnifiedX}px`;\n clonedElement.style.top = `${magnifiedY}px`;\n clonedElement.style.width = `${widgetSourceRect.width}px`;\n clonedElement.style.height = `${widgetSourceRect.height}px`;\n clonedElement.style.transformOrigin = 'top left';\n clonedElement.style.transform = `scale(${this.config.zoomFactor})`;\n\n this.ui.htmlOverlayContainer!.appendChild(clonedElement);\n }\n }\n }\n }\n }\n\n /**\n * Update config from current settings values.\n */\n updateConfigFromSettings(): void {\n this.config.loadSettings();\n }\n\n /**\n * Apply UI changes based on current config.\n */\n applyUiChanges(): void {\n this.ui.applyStyles();\n\n if (this.renderer?.gl) {\n this.renderer.updateViewport();\n }\n }\n\n\n\n /**\n * Reset offsets and panel positions to defaults.\n */\n resetOffsets(): void {\n this.config.resetOffsets();\n\n // Also disable follow cursor\n this.config.followCursor = false;\n\n if (this.state.active) {\n this.updateMagnifiedView();\n }\n\n // Reset magnify glass position to top-right corner\n if (this.ui.glassDiv) {\n // If it's the first activation, position at top right of the window (Anchored)\n // Otherwise, reset to the last known position or default if not active.\n if (!this.state.wasActivatedBefore) {\n const padding = DEFAULT_PADDING;\n // Using style.right ensures it stays on the right even if window resizes before first usage\n this.ui.glassDiv.style.right = `${padding}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.ui.glassDiv.style.left = 'auto'; // Ensure left is unset\n this.state.wasActivatedBefore = true;\n } else {\n // If it was already activated, and we are resetting, we might want to keep its current position\n // or explicitly reset it to the initial anchored position.\n // For now, let's re-apply the anchored position on reset if it was already activated.\n const padding = DEFAULT_PADDING;\n this.ui.glassDiv.style.right = `${padding}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.ui.glassDiv.style.left = 'auto';\n }\n }\n\n // Reset inspector panel position if exists\n const extensions = window.comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n extensions.forEach((extension: any) => {\n if (extension?.stateManager) {\n extension.stateManager.state.isPanelPinned = false;\n extension.stateManager.state.isPanelLocked = false;\n extension.stateManager.state.isAutoPinned = false;\n extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };\n extension.stateManager.state.lastPinnedPosition = null;\n\n if (extension.positionManager) {\n extension.positionManager.positionPanel();\n }\n if (extension.uiManager?.updateControlStates) {\n extension.uiManager.updateControlStates();\n }\n }\n });\n }\n\n this.debugger.log(\"Reset: Magnify glass and inspector panel positions restored to defaults\");\n }\n\n /**\n * Cleanup all resources.\n */\n cleanup(): void {\n this.eventHandler.detachListeners();\n this.popOutManager.cleanup();\n this.ui.cleanup();\n }\n\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA2BO,MAAM,aAAa;AAAA,EAsBtB,cAAc;AArBd;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGI,SAAK,SAAS,IAAI,cAAA;AAClB,SAAK,QAAQ,IAAI,eAAA;AACjB,SAAK,gBAAgB,IAAI,cAAA;AACzB,SAAK,KAAK,IAAI;AAAA,MACV,KAAK;AAAA,MACL,KAAK;AAAA,MACL,MAAM,KAAK,OAAA;AAAA,IAAO;AAMtB,SAAK,WAAW;AAChB,SAAK,oBAAoB;AACzB,SAAK,WAAW,IAAI,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AACjE,SAAK,eAAe,IAAI,aAAa,IAAI;AAGzC,SAAK,kBAAkB;AAGvB,SAAK,yBAAyB,EAAE,GAAG,GAAG,GAAG,EAAA;AAGzC,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACT,QAAI,OAAO,cAAc,eAAe,OAAO,QAAQ,eAAe,CAAC,IAAI,QAAQ;AAC/E,WAAK,SAAS,IAAI,gDAAgD;AAClE,iBAAW,MAAM,KAAK,KAAA,GAAQ,GAAG;AACjC;AAAA,IACJ;AACA,SAAK,SAAS,IAAI,0BAA0B;AAK5C,SAAK,OAAO,iBAAA;AAMZ,SAAK,GAAG,eAAA;AAIR,SAAK,GAAG,yBAAA;AAGR,SAAK,WAAW,IAAI,cAAc,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AAClE,QAAI,CAAC,KAAK,SAAS,WAAW;AAC1B,WAAK,GAAG,QAAA;AACR;AAAA,IACJ;AAGA,SAAK,kBAAkB,oBAAA;AACvB,SAAK,SAAS,IAAI,2BAA2B,KAAK,eAAe;AACjE,QAAI,CAAC,KAAK,iBAAiB;AACvB,WAAK,SAAS,MAAM,2DAA2D;AAC/E,WAAK,GAAG,QAAA;AACR;AAAA,IACJ;AAGA,SAAK,oBAAoB,IAAI,kBAAkB,KAAK,QAAQ,KAAK,KAAK;AAGtE,SAAK,aAAa,gBAAA;AAGlB,0BAAsB,IAAI;AAC1B,kCAA8B,IAAI;AAElC,SAAK,SAAS,IAAI,yDAAyD,KAAK,OAAO,cAAc,SAAS,EAAE,GAAG,KAAK,OAAO,cAAc,YAAA,CAAa,eAAe;AAAA,EAC7K;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACX,UAAM,QAAQ,KAAK;AAEnB,QAAI,MAAM,QAAQ;AAEd,YAAM,SAAS;AACf,WAAK,GAAG,KAAA;AAAA,IACZ,OAAO;AAEH,YAAM,SAAS;AACf,WAAK,GAAG,KAAA;AACR,UAAI,KAAK,cAAc;AACnB,aAAK,aAAa,sBAAA;AAAA,MACtB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,sBAAsB,SAAwB;AAE1C,SAAK,MAAM,kBAAkB,CAAC;AAE9B,QAAI,SAAS;AAET,WAAK,GAAG,qBAAqB,IAAI;AAAA,IAErC,OAAO;AAEH,WAAK,GAAG,qBAAqB,KAAK;AAAA,IAEtC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,wBAAiC;;AAErC,UAAM,aAAc,OAAe;AACnC,QAAI,cAAc,WAAW,SAAS,GAAG;AACrC,YAAM,YAAY,WAAW,CAAC;AAC9B,WAAI,4CAAW,iBAAX,mBAAyB,OAAO;AAChC,eAAO,UAAU,aAAa,MAAM,0BAA0B;AAAA,MAClE;AAAA,IACJ;AAEA,WAAO;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA4B;AACxB,QAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,iBAAiB;AAC/D;AAAA,IACJ;AAIA,QAAI,KAAK,MAAM,iBAAiB;AAC5B;AAAA,IACJ;AAGA,SAAK,2BAAA;AAGL,SAAK,sBAAA;AAGL,QAAI,CAAC,KAAK,MAAM,mBAAmB;AAC/B,WAAK,MAAM,oBAAoB;AAC/B,4BAAsB,MAAM;AACxB,YAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,iBAAiB;AAC/D,eAAK,MAAM,oBAAoB;AAC/B;AAAA,QACJ;AAKA,YAAI,eAAkC,KAAK;AAC3C,YAAI,KAAK,qBAAqB,KAAK,kBAAkB,eAAe;AAChE,gBAAM,gBAAgB,KAAK,kBAAkB,oBAAoB,KAAK,eAAe;AACrF,cAAI,eAAe;AACf,2BAAe;AAAA,UACnB;AAAA,QACJ;AAGA,aAAK,SAAS,OAAO,YAAY;AAMjC,aAAK,mBAAA;AAGL,YAAI,KAAK,cAAc,aAAA,KAAkB,KAAK,GAAG,aAAa;AAC1D,eAAK,cAAc,UAAU,KAAK,GAAG,WAAW;AAAA,QACpD;AAEA,aAAK,MAAM,oBAAoB;AAAA,MACnC,CAAC;AAAA,IACL;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,6BAAmC;AAC/B,SAAK,MAAM,cAAc;AACzB,SAAK,MAAM,gBAAgB;AAC3B,SAAK,MAAM,gBAAgB;AAE3B,QAAI,2BAAK,QAAQ;AACb,YAAM,KAAM,IAAI,OAAe;AAC/B,UAAI,IAAI;AACJ,YAAI,OAAO,GAAG,UAAU,UAAU;AAC9B,eAAK,MAAM,cAAc,GAAG;AAAA,QAChC;AAEA,YAAI,GAAG,QAAQ;AACX,eAAK,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK;AAC3C,eAAK,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK;AAAA,QAC/C;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA8B;AAC1B,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,cAAc,KAAK,MAAM;AAC/B,UAAM,gBAAgB,KAAK,MAAM;AACjC,UAAM,gBAAgB,KAAK,MAAM;AAEjC,QAAI,gBAAgB,EAAG;AACvB,QAAI,CAAC,KAAK,gBAAiB;AAG3B,UAAM,OAAO,KAAK,gBAAgB,sBAAA;AAClC,UAAM,MAAM,KAAK,QAAQ,IAAI,KAAK,gBAAgB,QAAQ,KAAK,QAAQ;AAIvE,UAAM,aAAa,eAAe;AAClC,UAAM,aAAa,eAAe;AAIlC,UAAM,gBAAgB,aAAa,iBAAiB;AACpD,UAAM,gBAAgB,aAAa,iBAAiB;AAGpD,UAAM,qBAAqB,eAAe,KAAK,OAAO;AACtD,UAAM,qBAAqB,eAAe,KAAK,OAAO;AAGtD,UAAM,mBAAoB,KAAK,OAAO,YAAY,KAAK,OAAO,aAAc;AAC5E,UAAM,oBAAqB,KAAK,OAAO,YAAY,KAAK,OAAO,aAAc;AAG7E,UAAM,eAAe,qBAAsB,mBAAmB;AAC9D,UAAM,eAAe,qBAAsB,oBAAoB;AAK/D,UAAM,aAAc,eAAe,cAAe;AAClD,UAAM,aAAc,eAAe,cAAe;AAClD,UAAM,iBAAiB,mBAAmB;AAC1C,UAAM,kBAAkB,oBAAoB;AAE5C,SAAK,MAAM,UAAU,aAAa;AAClC,SAAK,MAAM,UAAU,aAAa;AAClC,SAAK,MAAM,cAAc,iBAAiB;AAC1C,SAAK,MAAM,eAAe,kBAAkB;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA2B;AACvB,UAAM,QAAS,IAAY;AAC3B,QAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,GAAG,wBAAwB,CAAC,SAAS,CAAC,KAAK,iBAAiB;AACxF,UAAI,KAAK,GAAG,qBAAsB,MAAK,GAAG,qBAAqB,YAAY;AAC3E;AAAA,IACJ;AAEA,SAAK,GAAG,qBAAqB,YAAY;AAEzC,UAAM,cAAyB;AAAA,MAC3B,GAAG,KAAK,MAAM;AAAA,MACd,GAAG,KAAK,MAAM;AAAA,MACd,OAAO,KAAK,MAAM;AAAA,MAClB,QAAQ,KAAK,MAAM;AAAA,IAAA;AAGvB,UAAM,QAAqB,MAAM;AACjC,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACtB,YAAM,UAAW,KAAa;AAC9B,UAAI,CAAC,QAAS;AAEd,iBAAW,UAAU,SAAS;AAE1B,YAAI,iBAAiB;AACrB,YAAI,iBAAiB;AACrB,YAAI,mBAAuC;AAE3C,YAAI,OAAO,SAAS;AAChB,gBAAM,UAAU,OAAO;AAEvB,cAAI,OAAO,SAAS,UAAU,OAAO,SAAS,YAAY,QAAQ,YAAY,WAAY;AAAA,mBAG/E,QAAQ,YAAY,SAAS;AACpC,6BAAiB;AACjB,+BAAmB;AAAA,UACvB,WAAW,QAAQ,YAAY,OAAO;AAClC,6BAAiB;AACjB,+BAAmB;AAAA,UACvB,OAAO;AACH,kBAAM,iBAAiB,QAAQ,cAAc,OAAO;AACpD,gBAAI,gBAAgB;AAChB,+BAAiB;AACjB,iCAAmB;AAAA,YACvB,OAAO;AACH,oBAAM,iBAAiB,QAAQ,cAAc,KAAK;AAClD,kBAAI,gBAAgB;AAChB,iCAAiB;AACjB,mCAAmB;AAAA,cACvB;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ;AAEA,YAAI,qBAAsC,kBAAkB,iBAAiB;AACzE,gBAAM,aAAa,iBAAiB,sBAAA;AACpC,gBAAM,aAAa,KAAK,gBAAiB,sBAAA;AAGzC,gBAAM,MAAM,WAAW,QAAQ,IAAK,KAAK,gBAAiB,QAAQ,WAAW,QAAS;AACtF,gBAAM,eAAe,KAAK,MAAM;AAChC,gBAAM,oBAAoB,eAAe;AAGzC,gBAAM,aAAa,WAAW,OAAO,WAAW;AAChD,gBAAM,aAAa,WAAW,MAAM,WAAW;AAC/C,gBAAM,iBAAiB,WAAW;AAClC,gBAAM,kBAAkB,WAAW;AAGnC,gBAAM,YAAY,KAAK,MAAM,IAAI;AACjC,gBAAM,YAAY,KAAK,MAAM,IAAI;AAEjC,cAAI;AACJ,cAAI;AACJ,cAAI;AACJ,cAAI;AAEJ,cAAI,mBAAmB;AAGnB,+BAAmB,aAAa,aAAa,eAAe;AAC5D,+BAAmB,aAAa,aAAa,eAAe;AAE5D,kCAAsB,iBAAiB;AACvC,mCAAuB,kBAAkB;AAAA,UAC7C,OAAO;AAEH,8BAAkB;AAClB,8BAAkB;AAClB,kCAAsB;AACtB,mCAAuB;AAAA,UAC3B;AAGA,gBAAM,gBAAgB,kBAAkB;AACxC,gBAAM,gBAAgB,kBAAkB;AACxC,gBAAM,oBAAoB,sBAAsB;AAChD,gBAAM,qBAAqB,uBAAuB;AAElD,gBAAM,mBAA8B;AAAA,YAChC,GAAG;AAAA,YACH,GAAG;AAAA,YACH,OAAO;AAAA,YACP,QAAQ;AAAA,UAAA;AAGZ,cAAI,aAAa,aAAa,gBAAgB,GAAG;AAC7C,kBAAM,gBAAgB,iBAAiB,UAAU,IAAI;AACrD,0BAAc,MAAM,WAAW;AAC/B,0BAAc,MAAM,gBAAgB;gBAgCzB,gBAAgB;AACvB,oBAAM,QAAQ;AACd,oBAAM,gBAAgB;AACtB,oBAAM,MAAM,cAAc;AAC1B,oBAAM,WAAW,cAAc;AAC/B,oBAAM,OAAO,cAAc;AAC3B,oBAAM,UAAU,cAAc;AAC9B,oBAAM,cAAc,cAAc;AAClC,oBAAM,QAAQ;AACd,kBAAI,CAAC,cAAc,QAAQ;AACvB,sBAAM,OAAO,MAAM,CAAA,MAAK,QAAQ,KAAK,2CAA2C,CAAC,CAAC;AAAA,cACtF;AACA,oBAAM,cAAc,cAAc;AAAA,YACtC,WAAW,gBAAgB;AACvB,oBAAM,MAAM;AACZ,oBAAM,cAAc;AACpB,kBAAI,MAAM,YAAY;AACtB,kBAAI,MAAM,YAAY;AAAA,YAC1B;AAEA,kBAAM,YAAY,iBAAiB,IAAI,YAAY;AACnD,kBAAM,YAAY,iBAAiB,IAAI,YAAY;AACnD,kBAAM,aAAa,YAAY,KAAK,OAAO;AAC3C,kBAAM,aAAa,YAAY,KAAK,OAAO;AAE3C,0BAAc,MAAM,OAAO,GAAG,UAAU;AACxC,0BAAc,MAAM,MAAM,GAAG,UAAU;AACvC,0BAAc,MAAM,QAAQ,GAAG,iBAAiB,KAAK;AACrD,0BAAc,MAAM,SAAS,GAAG,iBAAiB,MAAM;AACvD,0BAAc,MAAM,kBAAkB;AACtC,0BAAc,MAAM,YAAY,SAAS,KAAK,OAAO,UAAU;AAE/D,iBAAK,GAAG,qBAAsB,YAAY,aAAa;AAAA,UAC3D;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,2BAAiC;AAC7B,SAAK,OAAO,aAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;;AACnB,SAAK,GAAG,YAAA;AAER,SAAI,UAAK,aAAL,mBAAe,IAAI;AACnB,WAAK,SAAS,eAAA;AAAA,IAClB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAOA,eAAqB;AACjB,SAAK,OAAO,aAAA;AAGZ,SAAK,OAAO,eAAe;AAE3B,QAAI,KAAK,MAAM,QAAQ;AACnB,WAAK,oBAAA;AAAA,IACT;AAGA,QAAI,KAAK,GAAG,UAAU;AAGlB,UAAI,CAAC,KAAK,MAAM,oBAAoB;AAChC,cAAM,UAAU;AAEhB,aAAK,GAAG,SAAS,MAAM,QAAQ,GAAG,OAAO;AACzC,aAAK,GAAG,SAAS,MAAM,MAAM,GAAG,sBAAsB;AACtD,aAAK,GAAG,SAAS,MAAM,OAAO;AAC9B,aAAK,MAAM,qBAAqB;AAAA,MACpC,OAAO;AAIH,cAAM,UAAU;AAChB,aAAK,GAAG,SAAS,MAAM,QAAQ,GAAG,OAAO;AACzC,aAAK,GAAG,SAAS,MAAM,MAAM,GAAG,sBAAsB;AACtD,aAAK,GAAG,SAAS,MAAM,OAAO;AAAA,MAClC;AAAA,IACJ;AAGA,UAAM,aAAa,OAAO;AAC1B,QAAI,cAAc,WAAW,SAAS,GAAG;AACrC,iBAAW,QAAQ,CAAC,cAAmB;;AACnC,YAAI,uCAAW,cAAc;AACzB,oBAAU,aAAa,MAAM,gBAAgB;AAC7C,oBAAU,aAAa,MAAM,gBAAgB;AAC7C,oBAAU,aAAa,MAAM,eAAe;AAC5C,oBAAU,aAAa,MAAM,iBAAiB,EAAE,GAAG,GAAG,GAAG,EAAA;AACzD,oBAAU,aAAa,MAAM,qBAAqB;AAElD,cAAI,UAAU,iBAAiB;AAC3B,sBAAU,gBAAgB,cAAA;AAAA,UAC9B;AACA,eAAI,eAAU,cAAV,mBAAqB,qBAAqB;AAC1C,sBAAU,UAAU,oBAAA;AAAA,UACxB;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAEA,SAAK,SAAS,IAAI,yEAAyE;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,SAAK,aAAa,gBAAA;AAClB,SAAK,cAAc,QAAA;AACnB,SAAK,GAAG,QAAA;AAAA,EACZ;AAEJ;"}
\ No newline at end of file
+{"version":3,"file":"MagnifyGlass.js","sources":["../../src/magnify-glass/MagnifyGlass.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Main Class (TypeScript)\n * \n * Orchestrates all components of the magnifying glass.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\nimport { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils';\nimport { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET, INFO_PANEL_ID } from '../shared/constants';\nimport { registerGlassSettings, registerAccessibilitySettings } from '../shared/settings';\nimport { ConfigManager } from './ConfigManager';\nimport { MagnifierState } from './MagnifierState';\nimport { UiManager } from './UiManager';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { DebugManager } from './DebugManager';\nimport { EventHandler } from './EventHandler';\nimport { PopOutManager } from './PopOutManager';\nimport { OffscreenRenderer } from './OffscreenRenderer';\n\n// External ComfyUI globals\ndeclare const app: ComfyApp;\ndeclare const LiteGraph: unknown;\n\n/**\n * Main MagnifyGlass class.\n * Orchestrates all components and provides the main API.\n */\nexport class MagnifyGlass {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n renderer: WebGLRenderer | null;\n debugger: DebugManager;\n eventHandler: EventHandler;\n popOutManager: PopOutManager;\n offscreenRenderer: OffscreenRenderer | null;\n\n /** The LiteGraph canvas */\n litegraphCanvas: HTMLCanvasElement | null;\n\n /** Last known mouse position for better initial positioning */\n lastKnownMousePosition: { x: number; y: number };\n\n /** Whether we're currently over media (for info panel) */\n isOverMedia: boolean;\n\n /** Current media element under cursor */\n currentMediaElement: HTMLImageElement | HTMLVideoElement | null;\n\n constructor() {\n this.config = new ConfigManager();\n this.state = new MagnifierState();\n this.popOutManager = new PopOutManager();\n this.ui = new UiManager(\n this.config,\n this.state,\n () => this.toggle()\n );\n\n // onStateChange is handled by InfoPanel\n\n\n this.renderer = null;\n this.offscreenRenderer = null;\n this.debugger = new DebugManager(this.config, this.state, this.ui);\n this.eventHandler = new EventHandler(this);\n\n // The LiteGraph canvas\n this.litegraphCanvas = null;\n\n // Last known mouse position for better initial positioning\n this.lastKnownMousePosition = { x: 0, y: 0 };\n\n // Media tracking\n this.isOverMedia = false;\n this.currentMediaElement = null;\n }\n\n /**\n * Initialize the magnifying glass.\n */\n init(): void {\n if (typeof LiteGraph === 'undefined' || typeof app === 'undefined' || !app.canvas) {\n this.debugger.log(\"LiteGraph or app not ready, retrying in 100ms.\");\n setTimeout(() => this.init(), 100);\n return;\n }\n this.debugger.log(\"LiteGraph and app ready.\");\n\n\n\n // Load saved offsets first\n this.config.loadSavedOffsets();\n\n // Print detailed canvas information\n\n\n // Create UI elements\n this.ui.createElements();\n\n // Ensure glass is validly positioned (within viewport) right away\n // This handles cases where config has saved positions from a different monitor size\n this.ui.updateResponsivePosition();\n\n // Setup WebGL renderer\n this.renderer = new WebGLRenderer(this.config, this.state, this.ui);\n if (!this.renderer.isValid()) {\n this.ui.cleanup();\n return;\n }\n\n // Find the LiteGraph canvas\n this.litegraphCanvas = findLiteGraphCanvas();\n this.debugger.log(\"LiteGraph canvas found:\", this.litegraphCanvas);\n if (!this.litegraphCanvas) {\n this.debugger.error(\"Could not find LiteGraph canvas. Magnifier will not work.\");\n this.ui.cleanup();\n return;\n }\n\n // Initialize offscreen renderer for high-res magnification\n this.offscreenRenderer = new OffscreenRenderer(this.config, this.state);\n\n // Attach event handlers\n this.eventHandler.attachListeners();\n\n // Register settings with ComfyUI\n registerGlassSettings(this);\n registerAccessibilitySettings(this);\n\n this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate.`);\n }\n\n /**\n * Toggle the magnifying glass active state.\n */\n toggle(): void {\n const state = this.state;\n\n if (state.active) {\n // TURNING OFF - Force hide everything\n this.forceHideAllComponents();\n } else {\n // TURNING ON\n state.active = true;\n this.ui.show();\n if (this.eventHandler) {\n this.eventHandler.updateInitialPosition();\n }\n }\n }\n\n /**\n * Force hide all components including extensions (Info Panel, etc).\n * This overcomes the \"pinned\" state of the info panel when the main tool is deactivated.\n */\n private forceHideAllComponents(): void {\n this.state.active = false;\n this.ui.hide();\n\n // 1. Nuclear option: Direct DOM ID targeting\n // This is necessary because in some cases object references might be stale or logic might prevent hiding\n const panelEl = document.getElementById(INFO_PANEL_ID);\n if (panelEl) {\n panelEl.style.display = 'none';\n // Also force opacity 0 just in case transition handles display\n panelEl.style.opacity = '0';\n }\n\n // Iterate through all registered extensions and hide them if they have a hide method\n const extensions = window.comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n extensions.forEach((extension: any) => {\n // Check basically any likely property for a UI manager or direct hide method\n if (extension && extension.uiManager && typeof extension.uiManager.hide === 'function') {\n extension.uiManager.hide();\n // Explicitly force display none if available to be extra sure\n if (extension.uiManager.elements && extension.uiManager.elements.panel) {\n extension.uiManager.elements.panel.style.display = 'none';\n }\n }\n });\n }\n }\n\n /**\n * Set glass preview visibility WITHOUT affecting info panel.\n * This is used by the toggle-glass hover control button.\n * When hidden, the renderer stops completely to save performance.\n * When shown, the renderer resumes.\n * \n * @param visible - true to show and enable rendering, false to hide and disable\n */\n setGlassPreviewActive(visible: boolean): void {\n // Track this state internally\n this.state.isPreviewHidden = !visible;\n\n if (visible) {\n // Show the glass preview\n this.ui.setPreviewVisibility(true);\n // Resume rendering on next mouse move\n } else {\n // Hide the glass preview visually\n this.ui.setPreviewVisibility(false);\n // The rendering will be skipped in updateMagnifiedView due to isPreviewHidden check\n }\n }\n\n /**\n * Check if the glass preview is visible.\n * Returns false if hidden via hover controls.\n */\n private isGlassPreviewVisible(): boolean {\n // Check info panel extension state for isGlassPreviewVisible\n const extensions = (window as any).comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n const infoPanel = extensions[0];\n if (infoPanel?.stateManager?.state) {\n return infoPanel.stateManager.state.isGlassPreviewVisible !== false;\n }\n }\n // Default to visible if we can't find the state\n return true;\n }\n\n /**\n * Update the magnified view.\n */\n updateMagnifiedView(): void {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n return;\n }\n\n // Skip rendering if glass preview is hidden via hover controls\n // This prevents double FPS when only the info panel is active\n if (this.state.isPreviewHidden) {\n return;\n }\n\n // Get canvas transformation info\n this.updateCanvasTransformation();\n\n // Calculate the source region\n this.calculateSourceRegion();\n\n // Schedule the rendering operation\n if (!this.state.isRenderScheduled) {\n this.state.isRenderScheduled = true;\n requestAnimationFrame(() => {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n this.state.isRenderScheduled = false;\n return;\n }\n\n // Try virtual zoom capture for high-res magnification\n // This temporarily sets LiteGraph to 100% zoom, captures clean pixels,\n // then restores the original zoom level\n let sourceCanvas: HTMLCanvasElement = this.litegraphCanvas;\n if (this.offscreenRenderer && this.offscreenRenderer.isAvailable()) {\n const highResCanvas = this.offscreenRenderer.renderHighResRegion(this.litegraphCanvas);\n if (highResCanvas) {\n sourceCanvas = highResCanvas;\n }\n }\n\n // Render the magnified view\n this.renderer.render(sourceCanvas);\n\n // Update debug visualization\n\n\n // Render HTML overlays for text, video, and image widgets\n this.renderHtmlOverlays();\n\n // Send frame to pop-out tab if open\n if (this.popOutManager.isPopOutOpen() && this.ui.glassCanvas) {\n this.popOutManager.sendFrame(this.ui.glassCanvas);\n }\n\n this.state.isRenderScheduled = false;\n });\n }\n }\n\n /**\n * Update canvas transformation state.\n */\n updateCanvasTransformation(): void {\n this.state.canvasScale = 1.0;\n this.state.canvasOffsetX = 0;\n this.state.canvasOffsetY = 0;\n\n if (app?.canvas) {\n const ds = (app.canvas as any).ds;\n if (ds) {\n if (typeof ds.scale === 'number') {\n this.state.canvasScale = ds.scale;\n }\n\n if (ds.offset) {\n this.state.canvasOffsetX = ds.offset[0] || 0;\n this.state.canvasOffsetY = ds.offset[1] || 0;\n }\n }\n }\n }\n\n /**\n * Calculate the source region for magnification.\n */\n calculateSourceRegion(): void {\n const cursorPixelX = this.state.x;\n const cursorPixelY = this.state.y;\n const canvasScale = this.state.canvasScale;\n const canvasOffsetX = this.state.canvasOffsetX;\n const canvasOffsetY = this.state.canvasOffsetY;\n\n if (canvasScale === 0) return;\n if (!this.litegraphCanvas) return;\n\n // Get DPR (Device Pixel Ratio) - relationship between Backing Store pixels and CSS pixels\n const rect = this.litegraphCanvas.getBoundingClientRect();\n const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;\n\n // Convert cursor Backing pixels to CSS pixels (Screen pixels)\n // state.x/y are in Backing Pixels (set in EventHandler)\n const cursorCssX = cursorPixelX / dpr;\n const cursorCssY = cursorPixelY / dpr;\n\n // Convert cursor CSS pixels to LiteGraph graph coordinates\n // ds.offset and ds.scale operate in CSS pixel space\n const cursorGraphX = (cursorCssX - canvasOffsetX) / canvasScale;\n const cursorGraphY = (cursorCssY - canvasOffsetY) / canvasScale;\n\n // Apply manual offset (in Graph Units - arbitrary units)\n const targetGraphCenterX = cursorGraphX + this.config.offsetX;\n const targetGraphCenterY = cursorGraphY + this.config.offsetY;\n\n // Calculate source dimensions in graph units\n const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n\n // Calculate source top-left corner in Graph Coordinates\n const sourceGraphX = targetGraphCenterX - (sourceGraphWidth / 2);\n const sourceGraphY = targetGraphCenterY - (sourceGraphHeight / 2);\n\n // Convert back to Canvas Backing Pixels for the source rectangle\n // 1. Convert Graph -> CSS: (Graph * Scale) + Offset\n // 2. Convert CSS -> Backing: CSS * DPR\n const sourceCssX = (sourceGraphX * canvasScale) + canvasOffsetX;\n const sourceCssY = (sourceGraphY * canvasScale) + canvasOffsetY;\n const sourceCssWidth = sourceGraphWidth * canvasScale;\n const sourceCssHeight = sourceGraphHeight * canvasScale;\n\n this.state.sourceX = sourceCssX * dpr;\n this.state.sourceY = sourceCssY * dpr;\n this.state.sourceWidth = sourceCssWidth * dpr;\n this.state.sourceHeight = sourceCssHeight * dpr;\n }\n\n /**\n * Render HTML overlays for text and media in the magnified view.\n */\n renderHtmlOverlays(): void {\n const graph = (app as any).graph;\n if (!this.state.active || !this.ui.htmlOverlayContainer || !graph || !this.litegraphCanvas) {\n if (this.ui.htmlOverlayContainer) this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n this.ui.htmlOverlayContainer.innerHTML = '';\n\n const magnifyRect: Rectangle = {\n x: this.state.sourceX,\n y: this.state.sourceY,\n width: this.state.sourceWidth,\n height: this.state.sourceHeight\n };\n\n const nodes: ComfyNode[] = graph._nodes;\n if (!nodes) return;\n\n for (const node of nodes) {\n const widgets = (node as any).widgets;\n if (!widgets) continue;\n\n for (const widget of widgets) {\n let isTextElement = false;\n let isVideoElement = false;\n let isImageElement = false;\n let elementToProcess: HTMLElement | null = null;\n\n if (widget.element) {\n const element = widget.element as HTMLElement;\n // Skip text elements - now rendered natively on canvas via OffscreenRenderer\n if (widget.type === \"text\" || widget.type === \"string\" || element.tagName === 'TEXTAREA') {\n // isTextElement = true; // DISABLED - using native canvas rendering\n // elementToProcess = element;\n } else if (element.tagName === 'VIDEO') {\n isVideoElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'IMG') {\n isImageElement = true;\n elementToProcess = element;\n } else {\n const potentialVideo = element.querySelector('video');\n if (potentialVideo) {\n isVideoElement = true;\n elementToProcess = potentialVideo;\n } else {\n const potentialImage = element.querySelector('img');\n if (potentialImage) {\n isImageElement = true;\n elementToProcess = potentialImage;\n }\n }\n }\n }\n\n if (elementToProcess && (isTextElement || isVideoElement || isImageElement)) {\n const widgetRect = elementToProcess.getBoundingClientRect();\n const canvasRect = this.litegraphCanvas!.getBoundingClientRect();\n\n // DPR = backing pixels / CSS pixels\n const dpr = canvasRect.width > 0 ? (this.litegraphCanvas!.width / canvasRect.width) : 1;\n const currentScale = this.state.canvasScale;\n const isVirtualZoomMode = currentScale < 0.7;\n\n // Widget position in CSS coordinates relative to canvas\n const widgetCssX = widgetRect.left - canvasRect.left;\n const widgetCssY = widgetRect.top - canvasRect.top;\n const widgetCssWidth = widgetRect.width;\n const widgetCssHeight = widgetRect.height;\n\n // Pivot point (mouse position) in CSS coordinates\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n let finalWidgetCssX: number;\n let finalWidgetCssY: number;\n let finalWidgetCssWidth: number;\n let finalWidgetCssHeight: number;\n\n if (isVirtualZoomMode) {\n // Virtual Zoom: Transform widget position to match virtual 1.0 scale capture\n // Formula: virtualCss = (widgetCss - pivotCss) / currentScale + pivotCss\n finalWidgetCssX = (widgetCssX - pivotCssX) / currentScale + pivotCssX;\n finalWidgetCssY = (widgetCssY - pivotCssY) / currentScale + pivotCssY;\n // Size also scales inversely\n finalWidgetCssWidth = widgetCssWidth / currentScale;\n finalWidgetCssHeight = widgetCssHeight / currentScale;\n } else {\n // Direct Capture: Use actual positions\n finalWidgetCssX = widgetCssX;\n finalWidgetCssY = widgetCssY;\n finalWidgetCssWidth = widgetCssWidth;\n finalWidgetCssHeight = widgetCssHeight;\n }\n\n // Convert to backing pixels for comparison with magnifyRect\n const widgetCanvasX = finalWidgetCssX * dpr;\n const widgetCanvasY = finalWidgetCssY * dpr;\n const widgetCanvasWidth = finalWidgetCssWidth * dpr;\n const widgetCanvasHeight = finalWidgetCssHeight * dpr;\n\n const widgetSourceRect: Rectangle = {\n x: widgetCanvasX,\n y: widgetCanvasY,\n width: widgetCanvasWidth,\n height: widgetCanvasHeight\n };\n\n if (rectsOverlap(magnifyRect, widgetSourceRect)) {\n const clonedElement = elementToProcess.cloneNode(true) as HTMLElement;\n clonedElement.style.position = 'absolute';\n clonedElement.style.pointerEvents = 'none';\n\n if (isTextElement) {\n clonedElement.style.backgroundColor = elementToProcess.style.backgroundColor || '#222';\n clonedElement.style.color = elementToProcess.style.color || '#DDD';\n clonedElement.style.border = elementToProcess.style.border || '1px solid #555';\n // Hide scrollbar while keeping content visible\n clonedElement.style.overflow = 'hidden';\n (clonedElement as HTMLInputElement).disabled = true;\n\n // Dual adaptive font sizing:\n // - When zoomed OUT (below 50%): shrink text to fit all content\n // - When zoomed IN (above 50%): grow text for better readability\n const canvasScale = this.state.canvasScale;\n const originalFontSize = parseFloat(window.getComputedStyle(elementToProcess).fontSize);\n const threshold = 0.5; // 50% zoom threshold\n\n let adaptedFontSize: number;\n if (canvasScale < threshold) {\n // Zoomed out: shrink text aggressively to fit content\n // Scale from threshold down to minimum\n const shrinkFactor = canvasScale / threshold;\n adaptedFontSize = Math.max(6, originalFontSize * shrinkFactor * 0.8);\n } else {\n // Zoomed in: grow text for readability\n // Scale from 100% at threshold up to 150% at 100%+ zoom\n const growFactor = 1 + ((canvasScale - threshold) / (1 - threshold)) * 0.5;\n adaptedFontSize = originalFontSize * Math.min(1.5, growFactor);\n }\n\n clonedElement.style.fontSize = `${adaptedFontSize}px`;\n clonedElement.style.lineHeight = canvasScale < threshold ? '1.2' : '1.4';\n } else if (isVideoElement) {\n const video = clonedElement as HTMLVideoElement;\n const originalVideo = elementToProcess as HTMLVideoElement;\n video.src = originalVideo.src;\n video.autoplay = originalVideo.autoplay;\n video.loop = originalVideo.loop;\n video.preload = originalVideo.preload;\n video.crossOrigin = originalVideo.crossOrigin;\n video.muted = true;\n if (!originalVideo.paused) {\n video.play().catch(e => console.warn(\"Magnify Glass: Cloned video play failed\", e));\n }\n video.currentTime = originalVideo.currentTime;\n } else if (isImageElement) {\n const img = clonedElement as HTMLImageElement;\n const originalImg = elementToProcess as HTMLImageElement;\n img.src = originalImg.src;\n img.alt = originalImg.alt;\n }\n\n const relativeX = widgetSourceRect.x - magnifyRect.x;\n const relativeY = widgetSourceRect.y - magnifyRect.y;\n const magnifiedX = relativeX * this.config.zoomFactor;\n const magnifiedY = relativeY * this.config.zoomFactor;\n\n clonedElement.style.left = `${magnifiedX}px`;\n clonedElement.style.top = `${magnifiedY}px`;\n clonedElement.style.width = `${widgetSourceRect.width}px`;\n clonedElement.style.height = `${widgetSourceRect.height}px`;\n clonedElement.style.transformOrigin = 'top left';\n clonedElement.style.transform = `scale(${this.config.zoomFactor})`;\n\n this.ui.htmlOverlayContainer!.appendChild(clonedElement);\n }\n }\n }\n }\n }\n\n /**\n * Update config from current settings values.\n */\n updateConfigFromSettings(): void {\n this.config.loadSettings();\n }\n\n /**\n * Apply UI changes based on current config.\n */\n applyUiChanges(): void {\n this.ui.applyStyles();\n\n if (this.renderer?.gl) {\n this.renderer.updateViewport();\n }\n }\n\n\n\n /**\n * Reset offsets and panel positions to defaults.\n */\n resetOffsets(): void {\n this.config.resetOffsets();\n\n // Also disable follow cursor\n this.config.followCursor = false;\n\n if (this.state.active) {\n this.updateMagnifiedView();\n }\n\n // Reset magnify glass position to top-right corner\n if (this.ui.glassDiv) {\n // If it's the first activation, position at top right of the window (Anchored)\n // Otherwise, reset to the last known position or default if not active.\n if (!this.state.wasActivatedBefore) {\n const padding = DEFAULT_PADDING;\n // Using style.right ensures it stays on the right even if window resizes before first usage\n this.ui.glassDiv.style.right = `${padding}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.ui.glassDiv.style.left = 'auto'; // Ensure left is unset\n this.state.wasActivatedBefore = true;\n } else {\n // If it was already activated, and we are resetting, we might want to keep its current position\n // or explicitly reset it to the initial anchored position.\n // For now, let's re-apply the anchored position on reset if it was already activated.\n const padding = DEFAULT_PADDING;\n this.ui.glassDiv.style.right = `${padding}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.ui.glassDiv.style.left = 'auto';\n }\n }\n\n // Reset inspector panel position if exists\n const extensions = window.comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n extensions.forEach((extension: any) => {\n if (extension?.stateManager) {\n extension.stateManager.state.isPanelPinned = false;\n extension.stateManager.state.isPanelLocked = false;\n extension.stateManager.state.isAutoPinned = false;\n extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };\n extension.stateManager.state.lastPinnedPosition = null;\n\n if (extension.positionManager) {\n extension.positionManager.positionPanel();\n }\n if (extension.uiManager?.updateControlStates) {\n extension.uiManager.updateControlStates();\n }\n }\n });\n }\n\n this.debugger.log(\"Reset: Magnify glass and inspector panel positions restored to defaults\");\n }\n\n /**\n * Cleanup all resources.\n */\n cleanup(): void {\n this.eventHandler.detachListeners();\n this.popOutManager.cleanup();\n this.ui.cleanup();\n }\n\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/magnify_info_panel.js b/web/magnify_info_panel.js
index de0e72b..54f5497 100644
--- a/web/magnify_info_panel.js
+++ b/web/magnify_info_panel.js
@@ -23,6 +23,10 @@ function initializeInfoPanel() {
try {
const infoPanel = new InfoPanel(magnifyGlass);
window.infoPanelManager = infoPanel;
+ if (!window.comfyUIMagnifyGlassExtensions) {
+ window.comfyUIMagnifyGlassExtensions = [];
+ }
+ window.comfyUIMagnifyGlassExtensions.push(infoPanel);
Logger.info("Info Panel extension initialized");
} catch (e) {
Logger.error("Error during initialization:", e);
diff --git a/web/magnify_info_panel.js.map b/web/magnify_info_panel.js.map
index dc1a722..9b195f9 100644
--- a/web/magnify_info_panel.js.map
+++ b/web/magnify_info_panel.js.map
@@ -1 +1 @@
-{"version":3,"file":"magnify_info_panel.js","sources":["../src/magnify_info_panel.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Info Panel - Main Entry Point\n * \n * Initializes the Info Panel extension.\n */\n\nimport { InfoPanel } from './info-panel';\nimport { Logger } from './shared/logger';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// We rely on the global 'app' object provided by ComfyUI\n// No need to import it as it's global, but TypeScript needs to know about it.\n// We handled this in types/comfyui.d.ts\n\napp.registerExtension({\n name: \"comfyui.magnify.info.panel\",\n init() {\n // Wait for magnify glass to be available\n const checkDependencies = () => {\n if (window.comfyUIMagnifyGlass) {\n initializeInfoPanel();\n } else {\n setTimeout(checkDependencies, 100);\n }\n };\n\n checkDependencies();\n }\n});\n\nfunction initializeInfoPanel() {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass) {\n Logger.error('Failed to find MagnifyGlass instance.');\n return;\n }\n\n try {\n // Initialize the info panel\n const infoPanel = new InfoPanel(magnifyGlass);\n\n // Expose orchestrator globally if needed\n window.infoPanelManager = infoPanel as any;\n\n // CSS is loaded by UIManager.injectStyles() - no need to load separately\n\n Logger.info('Info Panel extension initialized');\n } catch (e) {\n Logger.error('Error during initialization:', e);\n }\n}\n\n"],"names":[],"mappings":";;;AAeA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,oBAAoB,MAAM;AAC5B,UAAI,OAAO,qBAAqB;AAC5B,4BAAA;AAAA,MACJ,OAAO;AACH,mBAAW,mBAAmB,GAAG;AAAA,MACrC;AAAA,IACJ;AAEA,sBAAA;AAAA,EACJ;AACJ,CAAC;AAED,SAAS,sBAAsB;AAC3B,QAAM,eAAe,OAAO;AAC5B,MAAI,CAAC,cAAc;AACf,WAAO,MAAM,uCAAuC;AACpD;AAAA,EACJ;AAEA,MAAI;AAEA,UAAM,YAAY,IAAI,UAAU,YAAY;AAG5C,WAAO,mBAAmB;AAI1B,WAAO,KAAK,kCAAkC;AAAA,EAClD,SAAS,GAAG;AACR,WAAO,MAAM,gCAAgC,CAAC;AAAA,EAClD;AACJ;"}
\ No newline at end of file
+{"version":3,"file":"magnify_info_panel.js","sources":["../src/magnify_info_panel.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Info Panel - Main Entry Point\n * \n * Initializes the Info Panel extension.\n */\n\nimport { InfoPanel } from './info-panel';\nimport { Logger } from './shared/logger';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// We rely on the global 'app' object provided by ComfyUI\n// No need to import it as it's global, but TypeScript needs to know about it.\n// We handled this in types/comfyui.d.ts\n\napp.registerExtension({\n name: \"comfyui.magnify.info.panel\",\n init() {\n // Wait for magnify glass to be available\n const checkDependencies = () => {\n if (window.comfyUIMagnifyGlass) {\n initializeInfoPanel();\n } else {\n setTimeout(checkDependencies, 100);\n }\n };\n\n checkDependencies();\n }\n});\n\nfunction initializeInfoPanel() {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass) {\n Logger.error('Failed to find MagnifyGlass instance.');\n return;\n }\n\n try {\n // Initialize the info panel\n const infoPanel = new InfoPanel(magnifyGlass);\n\n // Expose orchestrator globally if needed\n window.infoPanelManager = infoPanel as any;\n\n // Register as extension for central management (e.g. force hide on toggle)\n if (!window.comfyUIMagnifyGlassExtensions) {\n window.comfyUIMagnifyGlassExtensions = [];\n }\n window.comfyUIMagnifyGlassExtensions.push(infoPanel);\n\n // CSS is loaded by UIManager.injectStyles() - no need to load separately\n\n Logger.info('Info Panel extension initialized');\n } catch (e) {\n Logger.error('Error during initialization:', e);\n }\n}\n\n"],"names":[],"mappings":";;;AAeA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,oBAAoB,MAAM;AAC5B,UAAI,OAAO,qBAAqB;AAC5B,4BAAA;AAAA,MACJ,OAAO;AACH,mBAAW,mBAAmB,GAAG;AAAA,MACrC;AAAA,IACJ;AAEA,sBAAA;AAAA,EACJ;AACJ,CAAC;AAED,SAAS,sBAAsB;AAC3B,QAAM,eAAe,OAAO;AAC5B,MAAI,CAAC,cAAc;AACf,WAAO,MAAM,uCAAuC;AACpD;AAAA,EACJ;AAEA,MAAI;AAEA,UAAM,YAAY,IAAI,UAAU,YAAY;AAG5C,WAAO,mBAAmB;AAG1B,QAAI,CAAC,OAAO,+BAA+B;AACvC,aAAO,gCAAgC,CAAA;AAAA,IAC3C;AACA,WAAO,8BAA8B,KAAK,SAAS;AAInD,WAAO,KAAK,kCAAkC;AAAA,EAClD,SAAS,GAAG;AACR,WAAO,MAAM,gCAAgC,CAAC;AAAA,EAClD;AACJ;"}
\ No newline at end of file
diff --git a/web/shared/constants.js b/web/shared/constants.js
index 6548c2e..fced7af 100644
--- a/web/shared/constants.js
+++ b/web/shared/constants.js
@@ -33,6 +33,7 @@ const DEFAULT_OFFSET_STEP = 5;
const DEFAULT_PANEL_WIDTH = 300;
const DEFAULT_PANEL_MAX_HEIGHT = 300;
const DEFAULT_PANEL_OPACITY = 100;
+const INFO_PANEL_ID = "comfyui-magnify-info-panel-pro-v2";
const Z_INDEX = {
GLASS: 98999
};
@@ -57,6 +58,7 @@ export {
DIRECT_CAPTURE_KEYS,
GLASS_POSITIONS,
GLASS_SHAPES,
+ INFO_PANEL_ID,
PANEL_POSITIONS,
RESET_KEYS,
STORAGE_KEYS,
diff --git a/web/shared/constants.js.map b/web/shared/constants.js.map
index b592da8..a83afd8 100644
--- a/web/shared/constants.js.map
+++ b/web/shared/constants.js.map
@@ -1 +1 @@
-{"version":3,"file":"constants.js","sources":["../../src/shared/constants.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Shared Constants\n * \n * Configuration constants shared between magnify glass and info panel modules.\n */\n\n// Glass positioning options\nexport const GLASS_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\",\n \"Top-Left\",\n \"Top-Right\",\n \"Bottom-Left\",\n \"Bottom-Right\"\n] as const;\n\nexport type GlassPosition = typeof GLASS_POSITIONS[number];\n\n// Glass shape options\nexport const GLASS_SHAPES = [\n \"Circle\",\n \"Square\",\n \"Rounded Square\"\n] as const;\n\nexport type GlassShape = typeof GLASS_SHAPES[number];\n\n// Texture filtering modes\nexport const TEXTURE_FILTERS = [\n \"Linear\",\n \"Nearest\"\n] as const;\n\nexport type TextureFilter = typeof TEXTURE_FILTERS[number];\n\n// Info panel positions relative to glass\nexport const PANEL_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\"\n] as const;\n\nexport type PanelPosition = typeof PANEL_POSITIONS[number];\n\n// Available keys for settings\nexport const ACTIVATION_KEYS = [\"x\", \"z\", \"m\", \"q\", \"v\", \"c\"] as const;\nexport const RESET_KEYS = [\"r\", \"o\", \"p\", \"k\", \"l\"] as const;\nexport const TOGGLE_FOLLOW_KEYS = [\"f\", \"g\", \"h\", \"j\", \"k\"] as const;\nexport const DIRECT_CAPTURE_KEYS = [\"d\", \"s\", \"a\", \"q\", \"e\"] as const;\n\n// UI Constants\nexport const DEFAULT_PADDING = 4;\nexport const DEFAULT_GLASS_Y_OFFSET = 200; // Starting Y position offset (below top bar with 3px gap)\nexport const DEFAULT_GLASS_SIZE = 300;\nexport const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage\nexport const DEFAULT_BORDER_WIDTH = 1;\nexport const DEFAULT_BORDER_COLOR = \"#6b7280\";\nexport const DEFAULT_OFFSET_STEP = 5;\n\n// Panel Constants\nexport const DEFAULT_PANEL_WIDTH = 300;\nexport const DEFAULT_PANEL_MAX_HEIGHT = 300;\nexport const DEFAULT_PANEL_OPACITY = 100;\n\n// Z-Index hierarchy\nexport const Z_INDEX = {\n GLASS: 98999,\n DEBUG: 99000,\n PANEL: 99999,\n CONTROLS: 99998\n} as const;\n\n// Animation durations (ms)\nexport const ANIMATION = {\n PANEL_TRANSITION: 300,\n AUTO_COLLAPSE_DELAY: 1500,\n THEME_DETECTION_INTERVAL: 2000\n} as const;\n\n// LocalStorage keys\nexport const STORAGE_KEYS = {\n OFFSET_X: 'comfyui_magnify_offset_x',\n OFFSET_Y: 'comfyui_magnify_offset_y'\n} as const;\n\n// BroadcastChannel for pop-out tab communication\nexport const BROADCAST_CHANNEL_NAME = 'comfyui-magnifyglass-popout';\n\n"],"names":[],"mappings":"AAOO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,eAAe;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACJ;AAaO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,kBAAkB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AACrD,MAAM,aAAa,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAC3C,MAAM,qBAAqB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AACnD,MAAM,sBAAsB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAGpD,MAAM,kBAAkB;AACxB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;AACjC,MAAM,wBAAwB;AAG9B,MAAM,UAAU;AAAA,EACnB,OAAO;AAIX;AAUO,MAAM,eAAe;AAAA,EACxB,UAAU;AAAA,EACV,UAAU;AACd;AAGO,MAAM,yBAAyB;"}
\ No newline at end of file
+{"version":3,"file":"constants.js","sources":["../../src/shared/constants.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Shared Constants\n * \n * Configuration constants shared between magnify glass and info panel modules.\n */\n\n// Glass positioning options\nexport const GLASS_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\",\n \"Top-Left\",\n \"Top-Right\",\n \"Bottom-Left\",\n \"Bottom-Right\"\n] as const;\n\nexport type GlassPosition = typeof GLASS_POSITIONS[number];\n\n// Glass shape options\nexport const GLASS_SHAPES = [\n \"Circle\",\n \"Square\",\n \"Rounded Square\"\n] as const;\n\nexport type GlassShape = typeof GLASS_SHAPES[number];\n\n// Texture filtering modes\nexport const TEXTURE_FILTERS = [\n \"Linear\",\n \"Nearest\"\n] as const;\n\nexport type TextureFilter = typeof TEXTURE_FILTERS[number];\n\n// Info panel positions relative to glass\nexport const PANEL_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\"\n] as const;\n\nexport type PanelPosition = typeof PANEL_POSITIONS[number];\n\n// Available keys for settings\nexport const ACTIVATION_KEYS = [\"x\", \"z\", \"m\", \"q\", \"v\", \"c\"] as const;\nexport const RESET_KEYS = [\"r\", \"o\", \"p\", \"k\", \"l\"] as const;\nexport const TOGGLE_FOLLOW_KEYS = [\"f\", \"g\", \"h\", \"j\", \"k\"] as const;\nexport const DIRECT_CAPTURE_KEYS = [\"d\", \"s\", \"a\", \"q\", \"e\"] as const;\n\n// UI Constants\nexport const DEFAULT_PADDING = 4;\nexport const DEFAULT_GLASS_Y_OFFSET = 200; // Starting Y position offset (below top bar with 3px gap)\nexport const DEFAULT_GLASS_SIZE = 300;\nexport const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage\nexport const DEFAULT_BORDER_WIDTH = 1;\nexport const DEFAULT_BORDER_COLOR = \"#6b7280\";\nexport const DEFAULT_OFFSET_STEP = 5;\n\n// Panel Constants\nexport const DEFAULT_PANEL_WIDTH = 300;\nexport const DEFAULT_PANEL_MAX_HEIGHT = 300;\nexport const DEFAULT_PANEL_OPACITY = 100;\nexport const INFO_PANEL_ID = \"comfyui-magnify-info-panel-pro-v2\";\n\n// Z-Index hierarchy\nexport const Z_INDEX = {\n GLASS: 98999,\n DEBUG: 99000,\n PANEL: 99999,\n CONTROLS: 99998\n} as const;\n\n// Animation durations (ms)\nexport const ANIMATION = {\n PANEL_TRANSITION: 300,\n AUTO_COLLAPSE_DELAY: 1500,\n THEME_DETECTION_INTERVAL: 2000\n} as const;\n\n// LocalStorage keys\nexport const STORAGE_KEYS = {\n OFFSET_X: 'comfyui_magnify_offset_x',\n OFFSET_Y: 'comfyui_magnify_offset_y'\n} as const;\n\n// BroadcastChannel for pop-out tab communication\nexport const BROADCAST_CHANNEL_NAME = 'comfyui-magnifyglass-popout';\n\n"],"names":[],"mappings":"AAOO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,eAAe;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACJ;AAaO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,kBAAkB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AACrD,MAAM,aAAa,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAC3C,MAAM,qBAAqB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AACnD,MAAM,sBAAsB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAGpD,MAAM,kBAAkB;AACxB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;AACjC,MAAM,wBAAwB;AAC9B,MAAM,gBAAgB;AAGtB,MAAM,UAAU;AAAA,EACnB,OAAO;AAIX;AAUO,MAAM,eAAe;AAAA,EACxB,UAAU;AAAA,EACV,UAAU;AACd;AAGO,MAAM,yBAAyB;"}
\ No newline at end of file