From 6587753d7da3a28b2cca18026858d46664d68d9d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 14:02:20 +0000 Subject: [PATCH] refactor: remove info panel force-hide workaround Removes the `forceHideInfoPanel` method and its related helper methods (`removeForceHide`, `ensureForceHideCssRule`) from `MagnifyGlass.ts`. This workaround manually manipulated the DOM to hide the info panel, bypassing the intended state management via the `InfoPanel` class which hooks into `MagnifyGlass` visibility changes. Testing: - Verified that `MagnifyGlass.ts` compiles without errors. - Verified that unit tests pass. - Verified that `InfoPanel` logic correctly handles hiding via `hookIntoMagnifyGlass`. --- src/magnify-glass/MagnifyGlass.ts | 59 --------------------------- web/magnify-glass/MagnifyGlass.js | 47 --------------------- web/magnify-glass/MagnifyGlass.js.map | 2 +- 3 files changed, 1 insertion(+), 107 deletions(-) diff --git a/src/magnify-glass/MagnifyGlass.ts b/src/magnify-glass/MagnifyGlass.ts index b567a6f..6467198 100644 --- a/src/magnify-glass/MagnifyGlass.ts +++ b/src/magnify-glass/MagnifyGlass.ts @@ -141,13 +141,9 @@ export class MagnifyGlass { // TURNING OFF - Force hide everything state.active = false; this.ui.hide(); - - // SIMPLE FIX: Force hide info panel directly by DOM - this.forceHideInfoPanel(); } else { // TURNING ON state.active = true; - this.removeForceHide(); // Remove force-hidden class before showing this.ui.show(); if (this.eventHandler) { this.eventHandler.updateInitialPosition(); @@ -178,61 +174,6 @@ export class MagnifyGlass { } } - /** - * Force hide the info panel using CSS class with !important. - * This bypasses all state management and inline style overrides. - */ - private forceHideInfoPanel(): void { - // Ensure CSS rule exists - this.ensureForceHideCssRule(); - - // Add the force-hidden class to info panel - const infoPanel = document.querySelector('.magnify-info-panel'); - if (infoPanel) { - infoPanel.classList.add('magnify-glass-force-hidden'); - } - - // Add the force-hidden class to floating controls - const floatingControls = document.querySelector('.floating-controls'); - if (floatingControls) { - floatingControls.classList.add('magnify-glass-force-hidden'); - } - } - - /** - * Remove the force-hidden class when glass is showing. - */ - private removeForceHide(): void { - const infoPanel = document.querySelector('.magnify-info-panel'); - if (infoPanel) { - infoPanel.classList.remove('magnify-glass-force-hidden'); - } - - const floatingControls = document.querySelector('.floating-controls'); - if (floatingControls) { - floatingControls.classList.remove('magnify-glass-force-hidden'); - } - } - - /** - * Ensure the CSS rule for force-hidden exists. - */ - private ensureForceHideCssRule(): void { - const styleId = 'magnify-glass-force-hide-style'; - if (document.getElementById(styleId)) return; - - const style = document.createElement('style'); - style.id = styleId; - style.textContent = ` - .magnify-glass-force-hidden { - display: none !important; - visibility: hidden !important; - opacity: 0 !important; - } - `; - document.head.appendChild(style); - } - /** * Check if the glass preview is visible. * Returns false if hidden via hover controls. diff --git a/web/magnify-glass/MagnifyGlass.js b/web/magnify-glass/MagnifyGlass.js index 7dfa47f..ce03e59 100644 --- a/web/magnify-glass/MagnifyGlass.js +++ b/web/magnify-glass/MagnifyGlass.js @@ -88,10 +88,8 @@ class MagnifyGlass { if (state.active) { state.active = false; this.ui.hide(); - this.forceHideInfoPanel(); } else { state.active = true; - this.removeForceHide(); this.ui.show(); if (this.eventHandler) { this.eventHandler.updateInitialPosition(); @@ -114,51 +112,6 @@ class MagnifyGlass { this.ui.setPreviewVisibility(false); } } - /** - * Force hide the info panel using CSS class with !important. - * This bypasses all state management and inline style overrides. - */ - forceHideInfoPanel() { - this.ensureForceHideCssRule(); - const infoPanel = document.querySelector(".magnify-info-panel"); - if (infoPanel) { - infoPanel.classList.add("magnify-glass-force-hidden"); - } - const floatingControls = document.querySelector(".floating-controls"); - if (floatingControls) { - floatingControls.classList.add("magnify-glass-force-hidden"); - } - } - /** - * Remove the force-hidden class when glass is showing. - */ - removeForceHide() { - const infoPanel = document.querySelector(".magnify-info-panel"); - if (infoPanel) { - infoPanel.classList.remove("magnify-glass-force-hidden"); - } - const floatingControls = document.querySelector(".floating-controls"); - if (floatingControls) { - floatingControls.classList.remove("magnify-glass-force-hidden"); - } - } - /** - * Ensure the CSS rule for force-hidden exists. - */ - ensureForceHideCssRule() { - const styleId = "magnify-glass-force-hide-style"; - if (document.getElementById(styleId)) return; - const style = document.createElement("style"); - style.id = styleId; - style.textContent = ` - .magnify-glass-force-hidden { - display: none !important; - visibility: hidden !important; - opacity: 0 !important; - } - `; - document.head.appendChild(style); - } /** * Check if the glass preview is visible. * Returns false if hidden via hover controls. diff --git a/web/magnify-glass/MagnifyGlass.js.map b/web/magnify-glass/MagnifyGlass.js.map index 3ad98f0..0b9151d 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\n // SIMPLE FIX: Force hide info panel directly by DOM\n this.forceHideInfoPanel();\n } else {\n // TURNING ON\n state.active = true;\n this.removeForceHide(); // Remove force-hidden class before showing\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 * Force hide the info panel using CSS class with !important.\n * This bypasses all state management and inline style overrides.\n */\n private forceHideInfoPanel(): void {\n // Ensure CSS rule exists\n this.ensureForceHideCssRule();\n\n // Add the force-hidden class to info panel\n const infoPanel = document.querySelector('.magnify-info-panel');\n if (infoPanel) {\n infoPanel.classList.add('magnify-glass-force-hidden');\n }\n\n // Add the force-hidden class to floating controls\n const floatingControls = document.querySelector('.floating-controls');\n if (floatingControls) {\n floatingControls.classList.add('magnify-glass-force-hidden');\n }\n }\n\n /**\n * Remove the force-hidden class when glass is showing.\n */\n private removeForceHide(): void {\n const infoPanel = document.querySelector('.magnify-info-panel');\n if (infoPanel) {\n infoPanel.classList.remove('magnify-glass-force-hidden');\n }\n\n const floatingControls = document.querySelector('.floating-controls');\n if (floatingControls) {\n floatingControls.classList.remove('magnify-glass-force-hidden');\n }\n }\n\n /**\n * Ensure the CSS rule for force-hidden exists.\n */\n private ensureForceHideCssRule(): void {\n const styleId = 'magnify-glass-force-hide-style';\n if (document.getElementById(styleId)) return;\n\n const style = document.createElement('style');\n style.id = styleId;\n style.textContent = `\n .magnify-glass-force-hidden {\n display: none !important;\n visibility: hidden !important;\n opacity: 0 !important;\n }\n `;\n document.head.appendChild(style);\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 +{"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":"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"} \ No newline at end of file