From 8954d2d44d2723b6037b0f0bcc6d20239145bf21 Mon Sep 17 00:00:00 2001 From: AEmotionStudio <163354043+AEmotionStudio@users.noreply.github.com> Date: Sun, 18 Jan 2026 18:24:00 -0800 Subject: [PATCH] perf: disable sourcemaps, remove build artifacts, and adjust default glass Y offset --- src/shared/constants.ts | 2 +- vite.config.ts | 2 +- web/info-panel/CanvasHighlighter.js | 1 - web/info-panel/CanvasHighlighter.js.map | 1 - web/info-panel/EventManager.js | 1 - web/info-panel/EventManager.js.map | 1 - web/info-panel/InfoPanel.js | 1 - web/info-panel/InfoPanel.js.map | 1 - web/info-panel/InformationGatherer.js | 1 - web/info-panel/InformationGatherer.js.map | 1 - web/info-panel/NodeDataExtractor.js | 1 - web/info-panel/NodeDataExtractor.js.map | 1 - web/info-panel/NodeSelector.js | 1 - web/info-panel/NodeSelector.js.map | 1 - web/info-panel/PositionManager.js | 1 - web/info-panel/PositionManager.js.map | 1 - web/info-panel/StateManager.js | 1 - web/info-panel/StateManager.js.map | 1 - web/info-panel/UIManager.js | 1 - web/info-panel/UIManager.js.map | 1 - web/info-panel/ValueFormatter.js | 1 - web/info-panel/ValueFormatter.js.map | 1 - web/info-panel/widget-editors/DragValueController.js | 1 - web/info-panel/widget-editors/DragValueController.js.map | 1 - web/info-panel/widget-editors/InlineControlFactory.js | 1 - web/info-panel/widget-editors/InlineControlFactory.js.map | 1 - web/info-panel/widget-editors/WidgetEditorFactory.js | 1 - web/info-panel/widget-editors/WidgetEditorFactory.js.map | 1 - web/info-panel/widget-editors/WidgetSyncManager.js | 1 - web/info-panel/widget-editors/WidgetSyncManager.js.map | 1 - web/magnify-glass/ConfigManager.js | 1 - web/magnify-glass/ConfigManager.js.map | 1 - web/magnify-glass/DebugManager.js | 1 - web/magnify-glass/DebugManager.js.map | 1 - web/magnify-glass/EventHandler.js | 1 - web/magnify-glass/EventHandler.js.map | 1 - web/magnify-glass/MagnifierState.js | 1 - web/magnify-glass/MagnifierState.js.map | 1 - web/magnify-glass/MagnifyGlass.js | 1 - web/magnify-glass/MagnifyGlass.js.map | 1 - web/magnify-glass/OffscreenRenderer.js | 1 - web/magnify-glass/OffscreenRenderer.js.map | 1 - web/magnify-glass/PopOutManager.js | 1 - web/magnify-glass/PopOutManager.js.map | 1 - web/magnify-glass/UiManager.js | 1 - web/magnify-glass/UiManager.js.map | 1 - web/magnify-glass/WebGLRenderer.js | 1 - web/magnify-glass/WebGLRenderer.js.map | 1 - web/magnify_glass.js | 1 - web/magnify_glass.js.map | 1 - web/magnify_info_panel.js | 1 - web/magnify_info_panel.js.map | 1 - web/shared/CustomDropdown.js | 1 - web/shared/CustomDropdown.js.map | 1 - web/shared/constants.js | 3 +-- web/shared/constants.js.map | 1 - web/shared/icons.js | 1 - web/shared/icons.js.map | 1 - web/shared/logger.js | 1 - web/shared/logger.js.map | 1 - web/shared/settings.js.map | 1 - web/shared/settings/accessibilitySettings.js | 1 - web/shared/settings/accessibilitySettings.js.map | 1 - web/shared/settings/defaults.js | 1 - web/shared/settings/defaults.js.map | 1 - web/shared/settings/glassSettings.js | 1 - web/shared/settings/glassSettings.js.map | 1 - web/shared/settings/panelSettings.js | 1 - web/shared/settings/panelSettings.js.map | 1 - web/shared/utils.js | 1 - web/shared/utils.js.map | 1 - web/sidebar/SidebarInspector.js.map | 1 - web/sidebar/SidebarSettings.js | 1 - web/sidebar/SidebarSettings.js.map | 1 - web/sidebar/sidebar.js | 1 - web/sidebar/sidebar.js.map | 1 - 76 files changed, 3 insertions(+), 77 deletions(-) delete mode 100644 web/info-panel/CanvasHighlighter.js.map delete mode 100644 web/info-panel/EventManager.js.map delete mode 100644 web/info-panel/InfoPanel.js.map delete mode 100644 web/info-panel/InformationGatherer.js.map delete mode 100644 web/info-panel/NodeDataExtractor.js.map delete mode 100644 web/info-panel/NodeSelector.js.map delete mode 100644 web/info-panel/PositionManager.js.map delete mode 100644 web/info-panel/StateManager.js.map delete mode 100644 web/info-panel/UIManager.js.map delete mode 100644 web/info-panel/ValueFormatter.js.map delete mode 100644 web/info-panel/widget-editors/DragValueController.js.map delete mode 100644 web/info-panel/widget-editors/InlineControlFactory.js.map delete mode 100644 web/info-panel/widget-editors/WidgetEditorFactory.js.map delete mode 100644 web/info-panel/widget-editors/WidgetSyncManager.js.map delete mode 100644 web/magnify-glass/ConfigManager.js.map delete mode 100644 web/magnify-glass/DebugManager.js.map delete mode 100644 web/magnify-glass/EventHandler.js.map delete mode 100644 web/magnify-glass/MagnifierState.js.map delete mode 100644 web/magnify-glass/MagnifyGlass.js.map delete mode 100644 web/magnify-glass/OffscreenRenderer.js.map delete mode 100644 web/magnify-glass/PopOutManager.js.map delete mode 100644 web/magnify-glass/UiManager.js.map delete mode 100644 web/magnify-glass/WebGLRenderer.js.map delete mode 100644 web/magnify_glass.js.map delete mode 100644 web/magnify_info_panel.js.map delete mode 100644 web/shared/CustomDropdown.js.map delete mode 100644 web/shared/constants.js.map delete mode 100644 web/shared/icons.js.map delete mode 100644 web/shared/logger.js.map delete mode 100644 web/shared/settings.js.map delete mode 100644 web/shared/settings/accessibilitySettings.js.map delete mode 100644 web/shared/settings/defaults.js.map delete mode 100644 web/shared/settings/glassSettings.js.map delete mode 100644 web/shared/settings/panelSettings.js.map delete mode 100644 web/shared/utils.js.map delete mode 100644 web/sidebar/SidebarInspector.js.map delete mode 100644 web/sidebar/SidebarSettings.js.map delete mode 100644 web/sidebar/sidebar.js.map diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 0a046b5..e062879 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -53,7 +53,7 @@ export const DIRECT_CAPTURE_KEYS = ["d", "s", "a", "q", "e"] as const; // UI Constants export const DEFAULT_PADDING = 4; -export const DEFAULT_GLASS_Y_OFFSET = 200; // Starting Y position offset (below top bar with 3px gap) +export const DEFAULT_GLASS_Y_OFFSET = 198; // Starting Y position offset (below top bar with 3px gap) export const DEFAULT_GLASS_SIZE = 300; export const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage export const DEFAULT_BORDER_WIDTH = 1; diff --git a/vite.config.ts b/vite.config.ts index 4a8b539..f7548e9 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ entryFileNames: '[name].js', } }, - sourcemap: true, + sourcemap: false, minify: false // Keep readable for debugging }, resolve: { diff --git a/web/info-panel/CanvasHighlighter.js b/web/info-panel/CanvasHighlighter.js index ee97218..14e7c04 100644 --- a/web/info-panel/CanvasHighlighter.js +++ b/web/info-panel/CanvasHighlighter.js @@ -148,4 +148,3 @@ class CanvasHighlighter { export { CanvasHighlighter }; -//# sourceMappingURL=CanvasHighlighter.js.map diff --git a/web/info-panel/CanvasHighlighter.js.map b/web/info-panel/CanvasHighlighter.js.map deleted file mode 100644 index 6194dd6..0000000 --- a/web/info-panel/CanvasHighlighter.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"CanvasHighlighter.js","sources":["../../src/info-panel/CanvasHighlighter.ts"],"sourcesContent":["/**\n * CanvasHighlighter.ts\n * \n * Handles drawing a visual highlight on the main canvas around the currently inspected node.\n * Hooks into LiteGraph's canvas drawing cycle.\n */\n\nimport { ComfyApp, ComfyNode } from '../types/comfyui';\nimport { getSettingValue } from '../shared/utils';\n\ndeclare const app: ComfyApp;\n\nexport class CanvasHighlighter {\n private originalOnDrawForeground: ((ctx: CanvasRenderingContext2D, visible_nodes: any) => void) | null = null;\n private highlightedNodeId: number | null = null;\n\n private highlightEl: HTMLDivElement | null = null;\n // Cache for DOM updates to minimize thrashing\n private lastStyleState: string = '';\n\n constructor() {\n this.createHighlightElement();\n // Don't hook immediately, will hook lazily when needed\n }\n\n private createHighlightElement(): void {\n this.highlightEl = document.createElement(\"div\");\n this.highlightEl.id = \"magnify-glass-node-highlight\";\n this.highlightEl.style.cssText = `\n position: fixed;\n pointer-events: none;\n border: 2px solid #007bff;\n border-radius: 10px;\n z-index: 1000;\n display: none;\n box-sizing: border-box;\n box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);\n `;\n document.body.appendChild(this.highlightEl);\n }\n\n /**\n * Hook into the main canvas onDrawForeground method.\n */\n private hookCanvas(): boolean {\n const app = (window as any).app;\n if (!app || !app.canvas) {\n return false;\n }\n const canvas = app.canvas;\n\n // Already hooked?\n if (canvas.onDrawForeground === this.boundOnDrawForeground) {\n return true;\n }\n\n // Save original method\n this.originalOnDrawForeground = canvas.onDrawForeground;\n\n // Override\n canvas.onDrawForeground = this.boundOnDrawForeground;\n return true;\n }\n\n /**\n * Set the node ID to highlight.\n */\n setHighlightedNode(nodeId: number | null): void {\n this.ensureHook(); // Ensure we are still hooked\n\n if (this.highlightedNodeId === nodeId) return;\n this.highlightedNodeId = nodeId;\n\n // Force redraw effectively updates our position logic via the hook\n const app = (window as any).app;\n if (app && app.canvas) {\n app.canvas.setDirty(true, true);\n }\n\n // Immediate update attempt (in case canvas isn't dirty)\n if (nodeId === null && this.highlightEl) {\n this.highlightEl.style.display = 'none';\n }\n }\n\n /**\n * Ensure the canvas hook is active.\n */\n private ensureHook(): void {\n const app = (window as any).app;\n if (!app || !app.canvas) return;\n\n // If our hook was overwritten (e.g. by another extension), re-hook\n if (app.canvas.onDrawForeground !== this.boundOnDrawForeground) {\n this.originalOnDrawForeground = app.canvas.onDrawForeground;\n app.canvas.onDrawForeground = this.boundOnDrawForeground;\n }\n }\n\n // Bound method to preserve 'this' and allow equality check\n private boundOnDrawForeground = (ctx: CanvasRenderingContext2D, visible_nodes: any) => {\n // Call original first\n if (this.originalOnDrawForeground) {\n this.originalOnDrawForeground.call((window as any).app.canvas, ctx, visible_nodes);\n }\n\n // Update DOM Highlight Position\n this.updateHighlightPosition();\n };\n\n /**\n * Update the position of the DOM highlight element.\n */\n private updateHighlightPosition(): void {\n if (!this.highlightEl) return;\n const app = (window as any).app;\n if (!app || !app.canvas || !app.graph) return;\n\n // 1. Check Visibility Settings\n const highlightEnabled = getSettingValue('🔍MagnifyGlass.NodeHighlightEnabled', true);\n if (!highlightEnabled || this.highlightedNodeId === null) {\n if (this.highlightEl.style.display !== 'none') {\n this.highlightEl.style.display = 'none';\n }\n return;\n }\n\n // 2. Get Node\n const node = app.graph.getNodeById(this.highlightedNodeId);\n if (!node) {\n if (this.highlightEl.style.display !== 'none') {\n this.highlightEl.style.display = 'none';\n }\n return;\n }\n\n // 3. Get Canvas Element's position on the page\n const canvasEl = app.canvas.canvas as HTMLCanvasElement;\n if (!canvasEl) return;\n const canvasRect = canvasEl.getBoundingClientRect();\n\n // 4. Coordinate Math\n const ds = app.canvas.ds;\n const scale = ds.scale;\n const offset = ds.offset;\n\n const LiteGraph = (window as any).LiteGraph;\n const titleHeight = LiteGraph?.NODE_TITLE_HEIGHT ?? 30;\n const padding = 6;\n\n // Graph coordinates\n // Assuming node.pos is top of body, we shift up by titleHeight to cover title\n // And use total height = title + body\n const graphX = node.pos[0];\n const graphY = node.pos[1] - titleHeight;\n const graphW = node.size[0];\n const graphH = titleHeight + node.size[1];\n\n // Canvas-relative screen coordinates (CSS pixels within the canvas element)\n // LiteGraph transform is: (Graph + Offset) * Scale\n const canvasX = (graphX + offset[0]) * scale;\n const canvasY = (graphY + offset[1]) * scale;\n const canvasW = graphW * scale;\n const canvasH = graphH * scale;\n\n // Page-absolute coordinates (add canvas position on the page)\n const pageX = canvasRect.left + canvasX - padding;\n const pageY = canvasRect.top + canvasY - padding; // Removed hardcoded offset\n const finalW = canvasW + (padding * 2);\n const finalH = canvasH + (padding * 2);\n\n // 5. Update DOM\n const styleState = `${pageX.toFixed(1)},${pageY.toFixed(1)},${finalW.toFixed(1)},${finalH.toFixed(1)}`;\n if (this.lastStyleState !== styleState || this.highlightEl.style.display === 'none') {\n this.highlightEl.style.display = 'block';\n this.highlightEl.style.left = `${pageX}px`;\n this.highlightEl.style.top = `${pageY}px`;\n this.highlightEl.style.width = `${finalW}px`;\n this.highlightEl.style.height = `${finalH}px`;\n\n // Border width scales with zoom (clamped 2-6px)\n const borderWidth = Math.max(2, Math.min(6, 3 * scale));\n this.highlightEl.style.borderWidth = `${borderWidth}px`;\n\n this.lastStyleState = styleState;\n }\n }\n\n /**\n * Clean up hooks.\n */\n cleanup(): void {\n const app = (window as any).app;\n if (app && app.canvas && this.originalOnDrawForeground) {\n if (app.canvas.onDrawForeground === this.boundOnDrawForeground) {\n app.canvas.onDrawForeground = this.originalOnDrawForeground;\n }\n }\n\n if (this.highlightEl) {\n this.highlightEl.remove();\n this.highlightEl = null;\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/EventManager.js b/web/info-panel/EventManager.js index f2ad1f4..bce1c41 100644 --- a/web/info-panel/EventManager.js +++ b/web/info-panel/EventManager.js @@ -182,4 +182,3 @@ class EventManager { export { EventManager }; -//# sourceMappingURL=EventManager.js.map diff --git a/web/info-panel/EventManager.js.map b/web/info-panel/EventManager.js.map deleted file mode 100644 index 36ce611..0000000 --- a/web/info-panel/EventManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"EventManager.js","sources":["../../src/info-panel/EventManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Event Manager (TypeScript)\n * \n * Complete Event Manager extracted from magnify_info_panel.js\n * Handles all event binding and delegation for the info panel.\n */\n\nimport { StateManager } from './StateManager';\nimport { UIManager } from './UIManager';\nimport { PositionManager } from './PositionManager';\nimport { isUserTyping } from '../shared/utils';\nimport { Logger } from '../shared/logger';\n\nexport interface EventCallbacks {\n toggleVisibility: () => void;\n togglePin: () => void;\n updatePosition: () => void;\n}\n\n/**\n * Event Manager class.\n * Handles all event listeners for the info panel.\n */\nexport class EventManager {\n stateManager: StateManager;\n panelElement: HTMLElement | null;\n positionManager: PositionManager;\n callbacks: EventCallbacks | null;\n\n dragCleanup: (() => void) | null;\n\n boundHandleMouseMove: (e: MouseEvent) => void;\n boundHandleKeyDown: (e: KeyboardEvent) => void;\n boundHandleKeyUp: (e: KeyboardEvent) => void;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement | null, positionManager: PositionManager, callbacks?: EventCallbacks) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n this.positionManager = positionManager;\n this.callbacks = callbacks || null;\n this.dragCleanup = null;\n\n this.boundHandleMouseMove = this.handleMouseMove.bind(this);\n this.boundHandleKeyDown = this.handleKeyDown.bind(this);\n this.boundHandleKeyUp = this.handleKeyUp.bind(this);\n\n this.setupEventListeners();\n }\n\n setupEventListeners(): void {\n this.setupPanelEvents();\n this.setupHotkeyEvents();\n this.setupDragEvents();\n this.setupHoverEvents();\n }\n\n setupPanelEvents(): void {\n if (!this.panelElement) return;\n\n // Vue handles most internal events now.\n // We can keep specific global-ish listeners if needed here.\n }\n\n setupHotkeyEvents(): void {\n document.addEventListener('keydown', this.boundHandleKeyDown);\n }\n\n setupDragEvents(): void {\n if (!this.panelElement) return;\n\n let isDragging = false;\n let rafId: number | null = null;\n let targetX = 0;\n let targetY = 0;\n\n const startDrag = (e: MouseEvent) => {\n if (!this.panelElement) return;\n const target = e.target as HTMLElement;\n\n // Only allow dragging from header (expand clickable area)\n const header = target.closest('.panel-header');\n if (!header) return;\n if (target.closest('button')) return;\n\n // Button 2 (pin icon, data-action=\"lock\") prevents dragging\n if (this.stateManager.state.isPanelLocked) return;\n\n // Also prevent dragging if we are \"Following Glass\" (not pinned to screen)\n // This prevents fighting with the auto-positioner and accidental drags\n if (!this.stateManager.state.isPanelPinned) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n isDragging = true;\n\n const startX = e.clientX;\n const startY = e.clientY;\n const rect = this.panelElement.getBoundingClientRect();\n const startLeft = rect.left;\n const startTop = rect.top;\n\n // Visual feedback - dragging state\n this.panelElement.style.cursor = 'grabbing';\n this.panelElement.style.opacity = '0.9';\n this.panelElement.style.transition = 'none'; // Disable transitions during drag\n document.body.style.cursor = 'grabbing';\n document.body.style.userSelect = 'none';\n\n const onMouseMove = (moveEvent: MouseEvent) => {\n if (!isDragging) return;\n\n const dx = moveEvent.clientX - startX;\n const dy = moveEvent.clientY - startY;\n\n targetX = startLeft + dx;\n targetY = startTop + dy;\n\n // Keep within viewport bounds\n const panelWidth = this.panelElement?.offsetWidth || 0;\n const panelHeight = this.panelElement?.offsetHeight || 0;\n targetX = Math.max(0, Math.min(targetX, window.innerWidth - panelWidth));\n targetY = Math.max(0, Math.min(targetY, window.innerHeight - panelHeight));\n\n // Use RAF for smooth updates\n if (rafId === null) {\n rafId = requestAnimationFrame(() => {\n if (this.panelElement && isDragging) {\n this.panelElement.style.left = `${targetX}px`;\n this.panelElement.style.top = `${targetY}px`;\n }\n rafId = null;\n });\n }\n };\n\n const onMouseUp = () => {\n isDragging = false;\n\n // Cancel any pending RAF\n if (rafId !== null) {\n cancelAnimationFrame(rafId);\n rafId = null;\n }\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n\n // Restore visual state\n if (this.panelElement) {\n this.panelElement.style.cursor = '';\n this.panelElement.style.opacity = '';\n this.panelElement.style.transition = '';\n\n // Final position update\n this.panelElement.style.left = `${targetX}px`;\n this.panelElement.style.top = `${targetY}px`;\n\n // Save pinned position\n this.stateManager.state.pinnedPosition = { x: targetX, y: targetY };\n }\n\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n };\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n this.panelElement.addEventListener('mousedown', startDrag);\n\n this.dragCleanup = () => {\n if (this.panelElement) this.panelElement.removeEventListener('mousedown', startDrag);\n };\n }\n\n setupHoverEvents(): void {\n if (!this.panelElement) return;\n\n this.panelElement.addEventListener('mouseenter', () => {\n this.stateManager.state.isPanelHovered = true;\n this.stateManager.clearAutoExpandTimer();\n });\n\n this.panelElement.addEventListener('mouseleave', () => {\n this.stateManager.state.isPanelHovered = false;\n if (!this.stateManager.state.isHoveringNode) {\n this.stateManager.scheduleAutoCollapse();\n }\n });\n }\n\n handleKeyDown(e: KeyboardEvent): void {\n if (isUserTyping()) return;\n\n const settings = this.stateManager.state.settings;\n const key = e.key.toLowerCase();\n // console.log(`[InfoPanel] KeyDown: ${key}`);\n\n // Toggle visibility\n if (key === (settings[\"🔍MagnifyGlass.ToggleHotkey\"] as string).toLowerCase()) {\n Logger.debug('Toggle visibility hotkey matched!');\n e.preventDefault();\n this.callbacks?.toggleVisibility();\n }\n\n // Toggle Pin\n if (key === (settings[\"🔍MagnifyGlass.PinPanelHotkey\"] as string).toLowerCase()) {\n e.preventDefault();\n this.callbacks?.togglePin();\n }\n\n // Toggle Hold Info (Pause/Play)\n const holdInfoHotkey = (settings[\"🔍MagnifyGlass.HoldInfoHotkey\"] as string || \"p\").toLowerCase();\n if (key === holdInfoHotkey) {\n // Only works when Sticky Info (Persist) is enabled\n if (settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n e.preventDefault();\n const isHeld = this.stateManager.toggleHold();\n Logger.debug(`Hold Info toggled: ${isHeld ? 'PAUSED' : 'PLAYING'}`);\n // Update the hover controls UI\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.uiManager) {\n infoPanel.uiManager.updateControlStates();\n }\n }\n }\n\n // Toggle Sticky Info (Persist mode)\n const stickyInfoHotkey = (settings[\"🔍MagnifyGlass.StickyInfoHotkey\"] as string || \"s\").toLowerCase();\n if (key === stickyInfoHotkey) {\n e.preventDefault();\n const newValue = !(settings[\"🔍MagnifyGlass.InfoPanelPersist\"] as boolean);\n // Update local state\n settings[\"🔍MagnifyGlass.InfoPanelPersist\"] = newValue;\n // Persist to ComfyUI settings\n try {\n const app = (window as any).app;\n app.ui.settings.setSettingValue(\"🔍MagnifyGlass.InfoPanelPersist\", newValue);\n } catch (err) {\n Logger.debug('Failed to persist sticky info setting');\n }\n // If disabling sticky, also disable hold\n if (!newValue) {\n this.stateManager.state.isInfoHeld = false;\n }\n Logger.debug(`Sticky Info toggled: ${newValue ? 'ON' : 'OFF'}`);\n // Update the hover controls UI\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.uiManager) {\n infoPanel.uiManager.updateControlStates();\n }\n }\n }\n\n handleMouseMove(e: MouseEvent): void {\n // Placeholder\n }\n\n handleKeyUp(e: KeyboardEvent): void {\n // Placeholder\n }\n\n cleanup(): void {\n document.removeEventListener('keydown', this.boundHandleKeyDown);\n if (this.dragCleanup) this.dragCleanup();\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/InfoPanel.js b/web/info-panel/InfoPanel.js index f5bcdae..896bb0f 100644 --- a/web/info-panel/InfoPanel.js +++ b/web/info-panel/InfoPanel.js @@ -221,4 +221,3 @@ class InfoPanel { export { InfoPanel }; -//# sourceMappingURL=InfoPanel.js.map diff --git a/web/info-panel/InfoPanel.js.map b/web/info-panel/InfoPanel.js.map deleted file mode 100644 index 27d628f..0000000 --- a/web/info-panel/InfoPanel.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"InfoPanel.js","sources":["../../src/info-panel/InfoPanel.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Orchestrator (TypeScript)\n * \n * Complete Info Panel Manager extracted from magnify_info_panel.js\n * Main class that orchestrates all info panel components.\n */\n\nimport type { MagnifyGlassInstance } from '../types/comfyui';\nimport { registerPanelSettings } from '../shared/settings';\nimport { Logger } from '../shared/logger';\nimport { StateManager } from './StateManager';\nimport { UIManager } from './UIManager';\nimport { PositionManager } from './PositionManager';\nimport { EventManager } from './EventManager';\nimport { InformationGatherer } from './InformationGatherer';\nimport { CanvasHighlighter } from './CanvasHighlighter';\n\n/**\n * Professional Info Panel Manager.\n * Orchestrates all the other components for the info panel.\n */\nexport class InfoPanel {\n magnifyGlass: MagnifyGlassInstance;\n stateManager: StateManager;\n uiManager: UIManager;\n positionManager: PositionManager;\n eventManager: EventManager;\n informationGatherer: InformationGatherer;\n canvasHighlighter: CanvasHighlighter;\n\n constructor(magnifyGlass: MagnifyGlassInstance) {\n this.magnifyGlass = magnifyGlass;\n\n // Initialize Managers\n this.stateManager = new StateManager();\n this.uiManager = new UIManager(this.stateManager);\n this.informationGatherer = new InformationGatherer();\n this.canvasHighlighter = new CanvasHighlighter();\n\n // Position Manager needs the UI elements\n this.positionManager = new PositionManager(this.stateManager, this.uiManager.elements.panel!);\n\n // Event Manager needs state and UI manager\n this.eventManager = new EventManager(this.stateManager, this.uiManager.elements.panel!, this.positionManager, {\n toggleVisibility: () => {\n if (this.stateManager.state.isPanelVisible) {\n this.uiManager.hide();\n } else {\n this.uiManager.show();\n }\n },\n togglePin: () => {\n this.stateManager.togglePinning();\n this.uiManager.updatePinnedState();\n },\n updatePosition: () => this.positionManager.positionPanel()\n });\n\n this.hookIntoMagnifyGlass();\n\n // Register settings with ComfyUI\n registerPanelSettings(this.stateManager, this.uiManager, this.positionManager);\n\n // Listen for popout state changes to update UI buttons\n if (this.magnifyGlass.popOutManager) {\n this.magnifyGlass.popOutManager.onStateChange = (isOpen: boolean) => {\n this.uiManager.updateControlStates();\n };\n // Listen for node selection from popout\n this.magnifyGlass.popOutManager.onNodeSelect = (nodeId: number) => {\n this.onNodeSelected(nodeId);\n };\n // Listen for widget edits from popout to force refresh\n this.magnifyGlass.popOutManager.onWidgetEdit = () => {\n // Force update to reflect changes (e.g. unhidden widgets)\n this.updateInfo(true);\n };\n }\n\n // Set up node selection callback\n this.uiManager.onNodeSelected = (nodeId: number) => {\n this.onNodeSelected(nodeId);\n };\n\n Logger.debug('Info Panel initialized successfully');\n }\n\n hookIntoMagnifyGlass(): void {\n // Hook into magnify glass update cycle\n const originalUpdateMagnifiedView = this.magnifyGlass.updateMagnifiedView.bind(this.magnifyGlass);\n\n this.magnifyGlass.updateMagnifiedView = (() => {\n originalUpdateMagnifiedView();\n\n // Schedule info update if panel is enabled and glass is active\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] &&\n this.magnifyGlass.state.active) {\n this.scheduleInfoUpdate();\n }\n }).bind(this);\n\n // Hook into show/hide\n const originalShow = this.magnifyGlass.ui.show.bind(this.magnifyGlass.ui);\n const originalHide = this.magnifyGlass.ui.hide.bind(this.magnifyGlass.ui);\n\n this.magnifyGlass.ui.show = (() => {\n originalShow();\n\n // CRITICAL: Only restore panel if the magnify glass is actually active\n // This prevents the panel from showing when toggle() is switching it OFF\n if (!this.magnifyGlass.state.active) {\n return;\n }\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n // Restore state: If it was visible before hide, make it visible now.\n // Otherwise only show controls.\n if (this.stateManager.state.wasPanelVisibleBeforeHide) {\n this.uiManager.show();\n\n // Position properly\n setTimeout(() => {\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n } else {\n // Do NOT force panel visible. Only show controls.\n const showControls = this.stateManager.state.settings[\"🔍MagnifyGlass.ShowHoveringControls\"] !== false;\n if (showControls && this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'flex';\n }\n\n // Ensure states are updated\n this.uiManager.updateControlStates();\n\n // Position controls after showing (relative to glass since panel is hidden)\n setTimeout(() => {\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n }\n }\n }).bind(this);\n\n this.magnifyGlass.ui.hide = (() => {\n // Save state before hiding\n this.stateManager.state.wasPanelVisibleBeforeHide = this.stateManager.state.isPanelVisible;\n\n originalHide();\n this.uiManager.hide();\n\n // FORCE HIDE: Directly set panel display to none as a safety measure\n if (this.uiManager.elements.panel) {\n this.uiManager.elements.panel.style.display = 'none';\n }\n\n // Explicitly hide floating controls when glass is disabled\n if (this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'none';\n }\n\n // Fix: Ensure highlight is cleared when glass is disabled\n this.canvasHighlighter.setHighlightedNode(null);\n }).bind(this);\n }\n\n scheduleInfoUpdate(): void {\n if (this.stateManager.state.updateScheduled) return;\n\n this.stateManager.state.updateScheduled = true;\n requestAnimationFrame(() => {\n this.updateInfo();\n this.stateManager.state.updateScheduled = false;\n });\n }\n\n // State for persistence\n private lastValidNodeInfo: any = null;\n\n /**\n * Update the info panel with current information.\n * @param forceUpdate - If true, bypass the isInfoHeld check to force an update\n */\n updateInfo(forceUpdate: boolean = false): void {\n const settings = this.stateManager.state.settings;\n const isActive = this.magnifyGlass.state.active;\n\n // Safety: If disabled or inactive, ensure hidden and return\n if (!settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] || !isActive) {\n // Hide the panel completely when the glass is inactive\n this.uiManager.hide();\n if (this.uiManager.elements.controls && this.uiManager.elements.controls.style.display !== 'none') {\n this.uiManager.elements.controls.style.display = 'none';\n }\n this.canvasHighlighter.setHighlightedNode(null);\n return;\n }\n\n // Logic for \"Hold Info\" (Freeze)\n // Skip if held, UNLESS we're forcing an update (e.g., from dropdown selection)\n if (this.stateManager.state.isInfoHeld && !forceUpdate) {\n // If held, we skip gathering new info and skip updating the display content.\n // But we MUST still update positioning to keep the UI responsive to movement.\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n return;\n }\n\n let info = this.informationGatherer.gatherInformation();\n\n // AUTO-CLEAR SELECTION: If user hovers a different node, clear the manual selection\n // This allows the dropdown selection to be a \"jump to\" action, then resume normal hover\n const selectedNodeId = this.stateManager.state.selectedNodeId;\n if (selectedNodeId !== null && info.hoveredNode && info.hoveredNode.id !== selectedNodeId) {\n // User is hovering a different node than the selected one - clear selection\n this.stateManager.clearSelectedNode();\n }\n\n // NODE SELECTION OVERRIDE - check if user has selected a specific node (after auto-clear check)\n const currentSelectedId = this.stateManager.state.selectedNodeId;\n if (currentSelectedId !== null) {\n const selectedNode = this.uiManager.nodeSelector.getNodeById(currentSelectedId);\n if (selectedNode) {\n // Override with selected node info\n const detailedInfo = this.informationGatherer.getDetailedNodeInfo(\n selectedNode,\n { x: 0, y: 0 } // No local position when manually selected\n );\n info = {\n ...info,\n hoveredNode: detailedInfo,\n hoveredWidget: null\n };\n }\n }\n // END NODE SELECTION OVERRIDE\n\n // PERSISTENCE LOGIC START\n if (info.hoveredNode) {\n // We have a valid node, update our cache\n this.lastValidNodeInfo = info;\n } else if (settings[\"🔍MagnifyGlass.InfoPanelPersist\"] && this.lastValidNodeInfo) {\n // No node hovered, but persistence is ON and we have a cache.\n // Merge the CACHED node info into the CURRENT info (so we keep current cursor pos/zoom but show old node)\n info = {\n ...info, // Current timestamp, cursor, zoom\n hoveredNode: this.lastValidNodeInfo.hoveredNode,\n hoveredWidget: this.lastValidNodeInfo.hoveredWidget\n };\n }\n // PERSISTENCE LOGIC END\n\n this.stateManager.setCurrentInfo(info);\n\n // Update the UI\n this.uiManager.displayInfo(info);\n\n // Update canvas highlight\n this.canvasHighlighter.setHighlightedNode(info.hoveredNode ? info.hoveredNode.id : null);\n\n // Send info to pop-out viewer if active\n if (this.magnifyGlass.popOutManager && this.magnifyGlass.popOutManager.isPopOutOpen()) {\n this.magnifyGlass.popOutManager.sendInfo(info as any);\n }\n\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n\n // Handle node hover state logic if needed for internal state\n if (info.hoveredNode) {\n this.stateManager.state.isHoveringNode = true;\n this.stateManager.expandNodeSections();\n if (this.stateManager.state.lastNodeId !== info.hoveredNode.id) {\n this.stateManager.state.lastNodeId = info.hoveredNode.id;\n }\n } else {\n this.stateManager.state.isHoveringNode = false;\n // Only collapse if we are NOT persisting\n if (!settings[\"🔍MagnifyGlass.InfoPanelPersist\"]) {\n if (this.stateManager.state.lastNodeId !== null) {\n this.stateManager.state.lastNodeId = null;\n if (!this.stateManager.state.isPanelHovered) {\n this.stateManager.scheduleAutoCollapse();\n }\n }\n }\n }\n }\n\n updateSettings(): void {\n const changes = this.stateManager.updateSettings();\n this.uiManager.applyStyles();\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] && this.magnifyGlass.state.active) {\n this.uiManager.show();\n // Ensure position is updated after potential dimension changes\n this.positionManager.positionPanel();\n } else {\n this.uiManager.hide();\n }\n }\n\n /**\n * Handle node selection from dropdown.\n * @param nodeId - ID of the selected node\n */\n onNodeSelected(nodeId: number): void {\n Logger.debug(`Node selected from dropdown: ${nodeId}`);\n\n // Set the selected node in state manager\n this.stateManager.setSelectedNode(nodeId);\n\n // Force an immediate info update (bypasses isInfoHeld)\n this.updateInfo(true);\n }\n\n /**\n * Clear the selected node, returning to hover-based detection.\n */\n clearSelectedNode(): void {\n this.stateManager.clearSelectedNode();\n this.updateInfo();\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/InformationGatherer.js b/web/info-panel/InformationGatherer.js index 2a77813..ca2702b 100644 --- a/web/info-panel/InformationGatherer.js +++ b/web/info-panel/InformationGatherer.js @@ -301,4 +301,3 @@ class InformationGatherer { export { InformationGatherer }; -//# sourceMappingURL=InformationGatherer.js.map diff --git a/web/info-panel/InformationGatherer.js.map b/web/info-panel/InformationGatherer.js.map deleted file mode 100644 index bdf806b..0000000 --- a/web/info-panel/InformationGatherer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"InformationGatherer.js","sources":["../../src/info-panel/InformationGatherer.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Information Gatherer (TypeScript)\n * \n * Complete Information Gatherer extracted from magnify_info_panel.js\n * Responsible for collecting information about nodes, widgets, and canvas state.\n */\n\nimport type { ComfyApp, ComfyNode, ComfyWidget, GatheredInfo, NodeInfo, WidgetInfo, MediaInfo } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\ndeclare const LiteGraph: any;\n\n/**\n * Information Gatherer class.\n * Collects information about the current state under the cursor.\n */\nexport class InformationGatherer {\n constructor() { }\n\n gatherInformation(): GatheredInfo {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass) {\n return {\n timestamp: Date.now(),\n cursor: { screenX: 0, screenY: 0, canvasX: 0, canvasY: 0 },\n zoom: 1,\n nodeCount: 0,\n hoveredNode: null,\n hoveredWidget: null,\n mediaElement: null\n };\n }\n\n const info: GatheredInfo = {\n timestamp: Date.now(),\n cursor: {\n screenX: magnifyGlass.lastKnownMousePosition.x,\n screenY: magnifyGlass.lastKnownMousePosition.y,\n canvasX: magnifyGlass.state.x,\n canvasY: magnifyGlass.state.y\n },\n zoom: magnifyGlass.state.canvasScale,\n nodeCount: app.graph?._nodes?.length ?? 0,\n hoveredNode: null,\n hoveredWidget: null,\n mediaElement: null\n };\n\n if (magnifyGlass.isOverMedia && magnifyGlass.currentMediaElement) {\n info.mediaElement = this.getMediaInfo(magnifyGlass.currentMediaElement);\n }\n\n const nodeUnderCursor = this.getNodeUnderCursor();\n if (nodeUnderCursor) {\n info.hoveredNode = this.getDetailedNodeInfo(nodeUnderCursor.node, nodeUnderCursor.localPos);\n\n const widget = this.getWidgetUnderCursor(nodeUnderCursor.node, nodeUnderCursor.localPos);\n if (widget) {\n info.hoveredWidget = this.getWidgetInfo(widget);\n }\n }\n\n return info;\n }\n\n getNodeUnderCursor(): { node: ComfyNode, localPos: { x: number, y: number } } | null {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!app.graph || !app.canvas || !magnifyGlass) {\n return null;\n }\n\n try {\n // Try to get the node from ComfyUI's state\n if (app.canvas.node_over) {\n const node = app.canvas.node_over;\n if (node && node.pos && node.size) {\n const canvasRect = app.canvas.canvas.getBoundingClientRect();\n const mouseX = magnifyGlass.lastKnownMousePosition.x - canvasRect.left;\n const mouseY = magnifyGlass.lastKnownMousePosition.y - canvasRect.top;\n\n let graphPos: [number, number];\n if (app.canvas.convertOffsetToCanvasPos) {\n graphPos = app.canvas.convertOffsetToCanvasPos([mouseX, mouseY]);\n } else {\n const ds = app.canvas.ds || { scale: 1, offset: [0, 0] };\n graphPos = [\n (mouseX / ds.scale) - (ds.offset[0] / ds.scale),\n (mouseY / ds.scale) - (ds.offset[1] / ds.scale)\n ];\n }\n\n return {\n node: node,\n localPos: {\n x: graphPos[0] - node.pos[0],\n y: graphPos[1] - node.pos[1]\n }\n };\n }\n }\n\n // Fallback to manual detection\n const canvasRect = app.canvas.canvas.getBoundingClientRect();\n const mouseX = magnifyGlass.lastKnownMousePosition.x - canvasRect.left;\n const mouseY = magnifyGlass.lastKnownMousePosition.y - canvasRect.top;\n\n let graphPos: [number, number];\n if (app.canvas.convertOffsetToCanvasPos) {\n graphPos = app.canvas.convertOffsetToCanvasPos([mouseX, mouseY]);\n } else {\n const ds = app.canvas.ds || { scale: 1, offset: [0, 0] };\n graphPos = [\n (mouseX / ds.scale) - (ds.offset[0] / ds.scale),\n (mouseY / ds.scale) - (ds.offset[1] / ds.scale)\n ];\n }\n\n for (let i = app.graph._nodes.length - 1; i >= 0; i--) {\n const node = app.graph._nodes[i];\n if (!node.pos || !node.size || (node.flags && node.flags.collapsed)) continue;\n\n if (graphPos[0] >= node.pos[0] &&\n graphPos[0] <= node.pos[0] + node.size[0] &&\n graphPos[1] >= node.pos[1] &&\n graphPos[1] <= node.pos[1] + node.size[1]) {\n\n return {\n node: node,\n localPos: {\n x: graphPos[0] - node.pos[0],\n y: graphPos[1] - node.pos[1]\n }\n };\n }\n }\n } catch (err) {\n console.warn(\"Error in node detection:\", err);\n }\n\n return null;\n }\n\n getDetailedNodeInfo(node: ComfyNode, localPos: { x: number, y: number }): NodeInfo {\n // Try to get author and category from node constructor or registered node types\n let author: string | undefined;\n let category: string | undefined;\n let executionOrder: number | undefined;\n let pythonModule: string | undefined;\n\n try {\n // Check for node.constructor.nodeData (ComfyUI stores metadata here)\n const nodeData = (node as any).constructor?.nodeData;\n if (nodeData) {\n pythonModule = nodeData.python_module;\n author = nodeData.author || pythonModule?.split('.')[0];\n category = nodeData.category;\n }\n\n // Try to get from LiteGraph registered node types\n if (!author && typeof LiteGraph !== 'undefined') {\n const nodeType = (LiteGraph as any).registered_node_types?.[node.type];\n if (nodeType) {\n author = nodeType.nodeData?.author || nodeType.author;\n category = nodeType.category || category;\n if (!pythonModule && nodeType.nodeData?.python_module) {\n pythonModule = nodeType.nodeData.python_module;\n }\n }\n }\n\n // Execution order - try to get from node.order (set during graph execution)\n executionOrder = (node as any).order;\n } catch (e) {\n // Silently ignore errors accessing node metadata\n }\n\n return {\n id: node.id,\n title: node.title || \"Untitled Node\",\n type: node.type,\n mode: this.getNodeModeText(node.mode),\n position: {\n x: Math.round(node.pos[0]),\n y: Math.round(node.pos[1]),\n formatted: `(${Math.round(node.pos[0])}, ${Math.round(node.pos[1])})`\n },\n size: {\n width: Math.round(node.size[0]),\n height: Math.round(node.size[1]),\n formatted: `${Math.round(node.size[0])}×${Math.round(node.size[1])}`\n },\n localPosition: {\n x: Math.round(localPos.x),\n y: Math.round(localPos.y),\n formatted: `(${Math.round(localPos.x)}, ${Math.round(localPos.y)})`,\n percentage: {\n x: ((localPos.x / node.size[0]) * 100).toFixed(1),\n y: ((localPos.y / node.size[1]) * 100).toFixed(1)\n }\n },\n counts: {\n widgets: node.widgets ? node.widgets.length : 0,\n inputs: node.inputs ? node.inputs.length : 0,\n outputs: node.outputs ? node.outputs.length : 0,\n properties: node.properties ? Object.keys(node.properties).length : 0\n },\n widgets: node.widgets || [],\n inputs: node.inputs || [],\n outputs: node.outputs || [],\n properties: node.properties || {},\n hoverRegion: this.detectNodeRegion(localPos, node),\n executionOrder: executionOrder,\n author: author,\n category: category,\n pythonModule: pythonModule\n };\n }\n\n getNodeInfo(node: ComfyNode): NodeInfo {\n return {\n id: node.id,\n title: node.title || \"Untitled\",\n type: node.type,\n mode: node.mode,\n size: node.size ? `${Math.round(node.size[0])}×${Math.round(node.size[1])}` : \"Unknown\",\n position: node.pos ? `(${Math.round(node.pos[0])}, ${Math.round(node.pos[1])})` : \"Unknown\",\n widgets: node.widgets || [],\n inputs: node.inputs || [],\n outputs: node.outputs || [],\n properties: node.properties || {}\n };\n }\n\n getWidgetUnderCursor(node: ComfyNode, localPos: { x: number, y: number }): ComfyWidget | null {\n if (!node.widgets || !node.widgets.length) return null;\n\n const titleHeight = 30;\n let currentY = titleHeight;\n\n for (const widget of node.widgets) {\n const widgetHeight = 25;\n\n if (localPos.y >= currentY && localPos.y <= currentY + widgetHeight) {\n return widget;\n }\n\n currentY += widgetHeight + 5;\n }\n\n return null;\n }\n\n getWidgetInfo(widget: ComfyWidget): WidgetInfo {\n return {\n name: widget.name,\n type: widget.type,\n value: this.formatValue(widget.value),\n options: widget.options || null,\n min: widget.min,\n max: widget.max,\n step: widget.step\n };\n }\n\n getMediaInfo(mediaElement: HTMLImageElement | HTMLVideoElement): MediaInfo {\n const info: MediaInfo = {\n type: mediaElement instanceof HTMLImageElement ? 'image' : 'video',\n tagName: mediaElement.tagName,\n src: mediaElement.src ? mediaElement.src.substring(mediaElement.src.lastIndexOf('/') + 1) : 'No source',\n naturalWidth: 0,\n naturalHeight: 0,\n displayWidth: 0,\n displayHeight: 0,\n aspectRatio: ''\n };\n\n if (mediaElement instanceof HTMLImageElement) {\n info.naturalWidth = mediaElement.naturalWidth;\n info.naturalHeight = mediaElement.naturalHeight;\n info.displayWidth = Math.round(mediaElement.width);\n info.displayHeight = Math.round(mediaElement.height);\n if (mediaElement.naturalWidth && mediaElement.naturalHeight) {\n info.aspectRatio = (mediaElement.naturalWidth / mediaElement.naturalHeight).toFixed(2);\n }\n } else if (mediaElement instanceof HTMLVideoElement) {\n info.naturalWidth = mediaElement.videoWidth;\n info.naturalHeight = mediaElement.videoHeight;\n info.displayWidth = Math.round(mediaElement.width);\n info.displayHeight = Math.round(mediaElement.height);\n info.duration = mediaElement.duration;\n info.currentTime = mediaElement.currentTime;\n if (mediaElement.videoWidth && mediaElement.videoHeight) {\n info.aspectRatio = (mediaElement.videoWidth / mediaElement.videoHeight).toFixed(2);\n }\n }\n\n return info;\n }\n\n getNodeModeText(mode: number): string {\n const modes: { [key: number]: string } = {\n 0: 'Always',\n 1: 'On Event',\n 2: 'Never',\n 3: 'On Trigger',\n 4: 'On Request'\n };\n return modes[mode] || `Mode ${mode}`;\n }\n\n detectNodeRegion(localPos: { x: number, y: number }, node: ComfyNode): string {\n const titleHeight = 30;\n const regions: string[] = [];\n\n if (localPos.y <= titleHeight) {\n regions.push(\"Title Bar\");\n }\n\n if (localPos.x <= 10) {\n regions.push(\"Left Edge\");\n } else if (localPos.x >= node.size[0] - 10) {\n regions.push(\"Right Edge\");\n }\n\n if (localPos.y <= 10) {\n regions.push(\"Top Edge\");\n } else if (localPos.y >= node.size[1] - 10) {\n regions.push(\"Bottom Edge\");\n }\n\n if (regions.length === 0) {\n if (localPos.y > titleHeight) {\n regions.push(\"Content Area\");\n }\n }\n\n if (localPos.x <= 20 && localPos.y > titleHeight) {\n regions.push(\"Input Area\");\n } else if (localPos.x >= node.size[0] - 20 && localPos.y > titleHeight) {\n regions.push(\"Output Area\");\n }\n\n return regions.length > 0 ? regions.join(\", \") : \"Unknown\";\n }\n\n formatValue(value: unknown): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"string\") {\n return value; // Show full text without truncation\n }\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? value.toString() : value.toFixed(3);\n }\n if (typeof value === \"boolean\") return value.toString();\n if (Array.isArray(value)) {\n return `Array(${value.length})`;\n }\n if (typeof value === \"object\") {\n return \"Object\";\n }\n return String(value);\n }\n}\n"],"names":["canvasRect","mouseX","mouseY","graphPos"],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/NodeDataExtractor.js b/web/info-panel/NodeDataExtractor.js index c92cb52..e5104c0 100644 --- a/web/info-panel/NodeDataExtractor.js +++ b/web/info-panel/NodeDataExtractor.js @@ -111,4 +111,3 @@ export { getImportantNodeParameters, getTextBoxContent }; -//# sourceMappingURL=NodeDataExtractor.js.map diff --git a/web/info-panel/NodeDataExtractor.js.map b/web/info-panel/NodeDataExtractor.js.map deleted file mode 100644 index a82cdfa..0000000 --- a/web/info-panel/NodeDataExtractor.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"NodeDataExtractor.js","sources":["../../src/info-panel/NodeDataExtractor.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Node Data Extractor\n * \n * Utility functions for extracting specific information from nodes.\n * Extracted from UIManager.ts for better modularity.\n */\n\nimport { formatWidgetValue } from './ValueFormatter';\nimport type { NodeInfo } from '../types/comfyui';\nimport { WidgetSyncManager } from './widget-editors/WidgetSyncManager';\n\n/**\n * Widget constraints for editors\n */\nexport interface WidgetConstraintsInfo {\n min?: number;\n max?: number;\n step?: number;\n precision?: number;\n options?: unknown[];\n}\n\n/**\n * Interface for extracted parameter items.\n * Extended to support inline editing in the inspector panel.\n */\nexport interface ParameterItem {\n label: string;\n value: string;\n // Editing metadata\n widgetName?: string; // Original widget name for syncing\n widgetType?: string; // Widget type (number, combo, text, button, etc.)\n isEditable?: boolean; // Whether this can be edited (value-based)\n isActionable?: boolean; // Whether this is an action button (callback-based)\n rawValue?: unknown; // Original unformatted value\n constraints?: WidgetConstraintsInfo; // Widget constraints for validation\n nodeId?: number; // Node ID for sync operations\n}\n\n/**\n * Get checkpoint/model info from a node.\n * @param nodeInfo - Node information object\n * @returns Model filename or null\n */\nexport function getCheckpointInfo(nodeInfo: NodeInfo): string | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes('CheckpointLoader') ||\n nodeInfo.type.includes('LoadCheckpoint') ||\n nodeInfo.type.includes('ModelLoader') ||\n nodeInfo.type.includes('UNETLoader') ||\n nodeInfo.type.includes('VAELoader') ||\n nodeInfo.type.includes('LoraLoader')\n )) {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes('model') ||\n widget.name.toLowerCase().includes('checkpoint') ||\n widget.name.toLowerCase().includes('ckpt') ||\n widget.name.toLowerCase().includes('lora') ||\n widget.name.toLowerCase().includes('vae') ||\n widget.name.toLowerCase().includes('file')\n )) {\n const value = String(widget.value);\n const filename = value.split(/[\\/\\\\]/).pop();\n return filename || value;\n }\n }\n }\n }\n return null;\n}\n\n/**\n * Image info result type.\n */\nexport interface ImageInfoResult {\n width: string | number;\n height: string | number;\n src: string;\n}\n\n/**\n * Get image info from a node.\n * @param nodeInfo - Node information object\n * @returns Image info string, object, or null\n */\nexport function getImageInfo(nodeInfo: NodeInfo): string | ImageInfoResult | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes('SaveImage') ||\n nodeInfo.type.includes('PreviewImage') ||\n nodeInfo.type.includes('VisionOutput') ||\n nodeInfo.type.includes('ImageOutput') ||\n nodeInfo.type.includes('LoadImage') ||\n nodeInfo.type.includes('Display')\n )) {\n if (nodeInfo.widgets) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes('image') ||\n widget.name.toLowerCase().includes('filename') ||\n widget.name.toLowerCase().includes('file')\n )) {\n return String(widget.value);\n }\n }\n }\n\n if (nodeInfo.properties && (nodeInfo.properties as Record).img) {\n const img = (nodeInfo.properties as Record).img as Record;\n return {\n width: (img.width as string | number) || 'unknown',\n height: (img.height as string | number) || 'unknown',\n src: img.src ? String(img.src).split(/[\\/\\\\]/).pop() || 'Preview available' : 'Preview available'\n };\n }\n\n if (nodeInfo.outputs) {\n for (const output of nodeInfo.outputs) {\n if (output.links && output.links.length > 0) {\n return 'Image connected to ' + output.links.length + ' node(s)';\n }\n }\n }\n\n return 'Image node';\n }\n return null;\n}\n\n/**\n * Get text box content from a node.\n * @param nodeInfo - Node information object\n * @returns Text content or null\n */\nexport function getTextBoxContent(nodeInfo: NodeInfo): string | null {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n if (nodeInfo.type && nodeInfo.type.includes('CLIPTextEncode')) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name === 'text' && typeof widget.value === 'string') {\n return widget.value;\n }\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.name.toLowerCase().includes('prompt') ||\n widget.name.toLowerCase().includes('conditioning')) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n return widget.value;\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.type === 'text' || widget.type === 'textarea' ||\n widget.type === 'string' || widget.name.toLowerCase().includes('text')) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n return widget.value;\n }\n }\n }\n return null;\n}\n\n/**\n * List of important parameter names for complex nodes.\n */\nconst COMPLEX_NODE_PARAMS = [\n 'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',\n 'positive', 'negative', 'width', 'height', 'denoise', 'strength',\n 'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',\n 'control_after_generate', 'control', 'after', 'generate',\n 'start_at_step', 'end_at_step', 'start', 'end',\n 'return_with_leftover_noise', 'leftover', 'noise_return',\n 'model', 'vae', 'clip', 'lora', 'checkpoint',\n 'latent', 'image', 'mask', 'filename', 'directory',\n 'prompt', 'conditioning', 'filename_prefix',\n 'resolution', 'num_chunks', 'seconds', 'aspect_ratio',\n 'style_type', 'background', 'n', 'human', 'raw', 'guidance',\n 'skip_preprocessing', 'movement_amplitude', 'animation',\n 'material_type', 'b1', 'b2', 's1', 's2', 'type', 'channel',\n 'sigma', 'rho', 'alpha', 'base_shift', 'shift', 'stretch', 'terminal',\n 'spacing', 'style', 'eta', 'norm_threshold', 'momentum',\n 'hypernetwork_name', 'reuse_threshold', 'verbose', 'layers',\n 'set_cond_area', 'audioui', 'camera_pose', 'fx', 'cx', 'fy', 'cy'\n];\n\n/**\n * Parameters for save nodes.\n */\nconst SAVE_NODE_PARAMS = [\n 'filename_prefix', 'filename', 'directory', 'path',\n 'format', 'quality', 'extension'\n];\n\n/**\n * Generic widget names to skip.\n */\nconst SKIP_WIDGET_NAMES = ['title', 'node', 'id', 'type', 'mode'];\n\n/**\n * Check if a node type should show all widgets.\n * @param nodeType - The node type string\n * @returns True if all widgets should be shown\n */\nfunction shouldShowAllWidgets(nodeType: string): boolean {\n const type = nodeType.toLowerCase();\n const isSaveNode = type.includes('save') &&\n !type.includes('checkpoint') &&\n !type.includes('model') &&\n !type.includes('preview');\n\n return (\n type.includes('ksampler') ||\n type.includes('sampler') ||\n type.includes('k_samplers') ||\n type.includes('checkpoint') ||\n type.includes('model') ||\n type.includes('lora') ||\n type.includes('controlnet') ||\n type.includes('advanced') ||\n type.includes('detailer') ||\n type.includes('inpaint') ||\n type.includes('upscale') ||\n type.includes('clip') ||\n type.includes('text') ||\n type.includes('encode')\n ) && !isSaveNode;\n}\n\n/**\n * Get important node parameters based on node type.\n * @param nodeInfo - Node information object\n * @returns Array of parameter items with editing metadata\n */\nexport function getImportantNodeParameters(nodeInfo: NodeInfo): ParameterItem[] {\n const parameters: ParameterItem[] = [];\n\n if (!nodeInfo.widgets || nodeInfo.widgets.length === 0) {\n return parameters;\n }\n\n const nodeType = nodeInfo.type || '';\n const typeLower = nodeType.toLowerCase();\n const isSaveNode = typeLower.includes('save') &&\n !typeLower.includes('checkpoint') &&\n !typeLower.includes('model') &&\n !typeLower.includes('preview');\n\n // Track seen values to deduplicate widgets with identical values\n const seenValues = new Set();\n\n // Helper to normalize value for comparison (handles different types)\n const normalizeValue = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n if (typeof value === 'string') return value.trim();\n if (typeof value === 'number' || typeof value === 'boolean') return String(value);\n return JSON.stringify(value);\n };\n\n // Helper to check if value is meaningful (not empty/default)\n const isMeaningfulValue = (value: unknown): boolean => {\n if (value === null || value === undefined) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n return true;\n };\n\n // Helper to create parameter item with full metadata\n const createParameterItem = (widget: any): ParameterItem => {\n const widgetType = WidgetSyncManager.getWidgetType(widget);\n const constraints = WidgetSyncManager.extractConstraints(widget);\n const isEditable = WidgetSyncManager.isWidgetEditable(widget);\n // Button widgets are actionable (can be clicked to trigger callbacks)\n const isActionable = widgetType === 'button';\n\n return {\n label: widget.name,\n value: formatWidgetValue(widget.value),\n // Editing metadata\n widgetName: widget.name,\n widgetType: widgetType,\n isEditable: isEditable,\n isActionable: isActionable,\n rawValue: widget.value,\n constraints: constraints,\n nodeId: nodeInfo.id\n };\n };\n\n // Helper to add widget (no longer deduplicating - each widget is unique by name)\n const addWidget = (widget: any): boolean => {\n parameters.push(createParameterItem(widget));\n return true;\n };\n\n // Show ALL widgets for all nodes (with skip list only)\n // This matches the popout viewer's behavior\n for (const widget of nodeInfo.widgets) {\n if (widget.name && widget.name !== '') {\n const widgetName = widget.name.toLowerCase();\n // Use exact matching for skip list to avoid false positives\n if (!SKIP_WIDGET_NAMES.includes(widgetName)) {\n addWidget(widget);\n }\n }\n }\n\n return parameters;\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/NodeSelector.js b/web/info-panel/NodeSelector.js index d02100d..49e9a50 100644 --- a/web/info-panel/NodeSelector.js +++ b/web/info-panel/NodeSelector.js @@ -62,4 +62,3 @@ class NodeSelector { export { NodeSelector }; -//# sourceMappingURL=NodeSelector.js.map diff --git a/web/info-panel/NodeSelector.js.map b/web/info-panel/NodeSelector.js.map deleted file mode 100644 index 04afb4e..0000000 --- a/web/info-panel/NodeSelector.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"NodeSelector.js","sources":["../../src/info-panel/NodeSelector.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Node Selector Utility\n * \n * Provides methods to fetch and sort nodes from the canvas\n * for use in the node search/selection feature.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\n\ndeclare const app: ComfyApp;\n\n/**\n * Node entry for dropdown display.\n */\nexport interface NodeListEntry {\n id: number;\n title: string;\n type: string;\n}\n\n/**\n * Node entry with execution order.\n */\nexport interface NodeExecOrderEntry extends NodeListEntry {\n order: number;\n}\n\n/**\n * NodeSelector class.\n * Provides methods to fetch and sort nodes from the canvas.\n */\nexport class NodeSelector {\n /**\n * Get all nodes from the graph.\n */\n getAllNodes(): ComfyNode[] {\n return app?.graph?._nodes ?? [];\n }\n\n /**\n * Get nodes sorted alphabetically by title.\n */\n getNodesSortedByTitle(): NodeListEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a, b) => a.title.localeCompare(b.title));\n }\n\n /**\n * Get nodes sorted by execution order.\n * Only includes nodes that have a valid execution order.\n */\n getNodesSortedByExecOrder(): NodeExecOrderEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown',\n order: (n as any).order ?? -1\n }))\n .filter(n => n.order >= 0)\n .sort((a, b) => a.order - b.order);\n }\n\n /**\n * Get nodes sorted by ID number (ascending).\n */\n getNodesSortedById(): NodeListEntry[] {\n return this.getAllNodes()\n .map(n => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a, b) => a.id - b.id);\n }\n\n /**\n * Get a node by its ID.\n */\n getNodeById(id: number): ComfyNode | null {\n return app?.graph?.getNodeById(id) ?? null;\n }\n\n /**\n * Search nodes by title (case-insensitive partial match).\n */\n searchByTitle(query: string): NodeListEntry[] {\n const lowerQuery = query.toLowerCase();\n return this.getNodesSortedByTitle()\n .filter(n => n.title.toLowerCase().includes(lowerQuery));\n }\n\n /**\n * Get total node count.\n */\n getNodeCount(): number {\n return this.getAllNodes().length;\n }\n}\n"],"names":[],"mappings":"AA+BO,MAAM,aAAa;AAAA;AAAA;AAAA;AAAA,EAItB,cAA2B;AAJxB;AAKC,aAAO,gCAAK,UAAL,mBAAY,WAAU,CAAA;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKA,wBAAyC;AACrC,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,IAAA,EAClB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,4BAAkD;AAC9C,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,MAChB,OAAQ,EAAU,SAAS;AAAA,IAAA,EAC7B,EACD,OAAO,CAAA,MAAK,EAAE,SAAS,CAAC,EACxB,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,qBAAsC;AAClC,WAAO,KAAK,YAAA,EACP,IAAI,CAAA,OAAM;AAAA,MACP,IAAI,EAAE;AAAA,MACN,OAAO,EAAE,SAAS;AAAA,MAClB,MAAM,EAAE,QAAQ;AAAA,IAAA,EAClB,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,IAA8B;AArDvC;AAsDC,aAAO,gCAAK,UAAL,mBAAY,YAAY,QAAO;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAAgC;AAC1C,UAAM,aAAa,MAAM,YAAA;AACzB,WAAO,KAAK,wBACP,OAAO,CAAA,MAAK,EAAE,MAAM,YAAA,EAAc,SAAS,UAAU,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACnB,WAAO,KAAK,cAAc;AAAA,EAC9B;AACJ;"} \ No newline at end of file diff --git a/web/info-panel/PositionManager.js b/web/info-panel/PositionManager.js index 64549a6..d0faf14 100644 --- a/web/info-panel/PositionManager.js +++ b/web/info-panel/PositionManager.js @@ -178,4 +178,3 @@ class PositionManager { export { PositionManager }; -//# sourceMappingURL=PositionManager.js.map diff --git a/web/info-panel/PositionManager.js.map b/web/info-panel/PositionManager.js.map deleted file mode 100644 index f052024..0000000 --- a/web/info-panel/PositionManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PositionManager.js","sources":["../../src/info-panel/PositionManager.ts"],"sourcesContent":["/**\n * Position Manager class.\n * Handles all positioning logic for the info panel.\n */\n\nimport { StateManager } from './StateManager';\n\nexport class PositionManager {\n stateManager: StateManager;\n panelElement: HTMLElement;\n // Cache the calculated position to avoid getBoundingClientRect() and layout thrashing\n // This also ensures controls move in perfect sync with the panel, ignoring CSS transitions\n cachedPanelPosition: { x: number, y: number, width: number, height: number } | null = null;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n }\n\n positionPanel(): void {\n if (!this.panelElement) return;\n\n // If panel is pinned, keep it at its pinned position\n if (this.stateManager.state.isPanelPinned) {\n this.applyPinnedPosition();\n return;\n }\n\n // Normal positioning logic\n this.calculateNormalPosition();\n }\n\n applyPinnedPosition(): void {\n if (!this.panelElement) return;\n\n let { x, y } = this.stateManager.state.pinnedPosition;\n\n // If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position\n if (x === 0 && y === 0) {\n this.calculateNormalPosition();\n // Save the calculated position as the new pinned position\n if (this.panelElement) {\n const rect = this.panelElement.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n return;\n }\n\n const panelWidth = this.panelElement.offsetWidth || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n const margin = 4;\n\n const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));\n const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));\n\n this.panelElement.style.left = `${boundedX}px`;\n this.panelElement.style.top = `${boundedY}px`;\n\n this.cachedPanelPosition = { x: boundedX, y: boundedY, width: panelWidth, height: panelHeight };\n }\n\n calculateNormalPosition(): void {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass || !this.panelElement) return;\n\n const settings = this.stateManager.state.settings;\n const panelWidth = Number(settings[\"🔍MagnifyGlass.InfoPanelWidth\"]) || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n\n let left: number;\n let top: number;\n const margin = 4;\n\n // Check for valid glass rect first\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);\n\n // Check for valid mouse position\n const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;\n const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;\n const hasValidMousePosition = mouseX > 0 || mouseY > 0;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass is hidden, use mouse position\n if (hasValidMousePosition) {\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n } else if (hasValidGlassRect) {\n // Position relative to glass\n const position = settings[\"🔍MagnifyGlass.InfoPanelPosition\"];\n switch (position) {\n case \"Right\":\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n case \"Left\":\n left = glassRect!.left - panelWidth - margin;\n top = glassRect!.top;\n break;\n case \"Top\":\n left = glassRect!.left;\n top = glassRect!.top - panelHeight - margin;\n break;\n case \"Bottom\":\n left = glassRect!.left;\n top = glassRect!.bottom + margin;\n break;\n default:\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n }\n } else if (hasValidMousePosition) {\n // Fall back to mouse position\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Final fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n\n left = Math.max(4, Math.min(left, window.innerWidth - panelWidth - 4));\n top = Math.max(4, Math.min(top, window.innerHeight - panelHeight - 4));\n\n this.panelElement.style.left = `${left}px`;\n this.panelElement.style.top = `${top}px`;\n\n this.cachedPanelPosition = { x: left, y: top, width: panelWidth, height: panelHeight };\n }\n\n /**\n * Position the floating controls relative to the panel.\n */\n positionFloatingControls(controlsElement: HTMLElement | null): void {\n if (!controlsElement) return;\n\n // Check if controls should be visible based on settings\n const settings = this.stateManager.state.settings;\n if (settings[\"🔍MagnifyGlass.ShowHoveringControls\"] === false) {\n controlsElement.style.display = 'none';\n return;\n }\n\n // NEW: Check if Magnify Glass is active globally\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (magnifyGlass && !magnifyGlass.state.active) {\n controlsElement.style.display = 'none';\n return;\n }\n\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n\n let referenceRect: DOMRect | null = null;\n\n // 1. Position relative to PANEL (Preferred if visible)\n if (isPanelVisible && this.panelElement) {\n // Priority: Use Cached Position (Logical) -> Avoids 'chasing' CSS transitions\n if (this.cachedPanelPosition) {\n // Convert simple object to Rect-like interface for compatibility\n referenceRect = {\n left: this.cachedPanelPosition.x,\n top: this.cachedPanelPosition.y,\n right: this.cachedPanelPosition.x + this.cachedPanelPosition.width,\n bottom: this.cachedPanelPosition.y + this.cachedPanelPosition.height,\n width: this.cachedPanelPosition.width,\n height: this.cachedPanelPosition.height\n } as DOMRect;\n } else {\n // Fallback: Read DOM (May cause layout thrashing/lag)\n referenceRect = this.panelElement.getBoundingClientRect();\n }\n }\n // 2. Position relative to GLASS (Fallback if panel hidden)\n else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) {\n referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect();\n // Don't show controls if glass is hidden/invalid\n if (referenceRect.width === 0 || referenceRect.height === 0) {\n controlsElement.style.display = 'none';\n return;\n }\n }\n\n if (!referenceRect) return;\n\n // Enforce no transition on controls to prevent 'rubber-banding'\n controlsElement.style.transition = 'none';\n\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"right\";\n const margin = 4;\n\n let left: number;\n let top: number;\n\n switch (controlsPosition) {\n case \"left\":\n left = referenceRect.left - controlsElement.offsetWidth - margin;\n top = referenceRect.top;\n break;\n case \"right\":\n left = referenceRect.right + margin;\n top = referenceRect.top;\n break;\n case \"top\":\n left = referenceRect.left;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = referenceRect.left;\n top = referenceRect.bottom + margin;\n break;\n case \"top-right\":\n left = referenceRect.right - controlsElement.offsetWidth;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = referenceRect.right + margin;\n top = referenceRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":";;;AAOO,MAAM,gBAAgB;AAAA,EAOzB,YAAY,cAA4B,cAA2B;AANnE;AACA;AAGA;AAAA;AAAA,+CAAsF;AAGlF,SAAK,eAAe;AACpB,SAAK,eAAe;AAAA,EACxB;AAAA,EAEA,gBAAsB;AAClB,QAAI,CAAC,KAAK,aAAc;AAGxB,QAAI,KAAK,aAAa,MAAM,eAAe;AACvC,WAAK,oBAAA;AACL;AAAA,IACJ;AAGA,SAAK,wBAAA;AAAA,EACT;AAAA,EAEA,sBAA4B;AACxB,QAAI,CAAC,KAAK,aAAc;AAExB,QAAI,EAAE,GAAG,EAAA,IAAM,KAAK,aAAa,MAAM;AAGvC,QAAI,MAAM,KAAK,MAAM,GAAG;AACpB,WAAK,wBAAA;AAEL,UAAI,KAAK,cAAc;AACnB,cAAM,OAAO,KAAK,aAAa,sBAAA;AAC/B,aAAK,aAAa,MAAM,iBAAiB,EAAE,GAAG,KAAK,MAAM,GAAG,KAAK,IAAA;AAAA,MACrE;AACA;AAAA,IACJ;AAEA,UAAM,aAAa,KAAK,aAAa,eAAe;AACpD,UAAM,cAAc,KAAK,aAAa,gBAAgB;AACtD,UAAM,SAAS;AAEf,UAAM,WAAW,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,aAAa,aAAa,MAAM,CAAC;AACtF,UAAM,WAAW,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,cAAc,cAAc,MAAM,CAAC;AAExF,SAAK,aAAa,MAAM,OAAO,GAAG,QAAQ;AAC1C,SAAK,aAAa,MAAM,MAAM,GAAG,QAAQ;AAEzC,SAAK,sBAAsB,EAAE,GAAG,UAAU,GAAG,UAAU,OAAO,YAAY,QAAQ,YAAA;AAAA,EACtF;AAAA,EAEA,0BAAgC;AAtD7B;AAuDC,UAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,gBAAgB,CAAC,KAAK,aAAc;AAEzC,UAAM,WAAW,KAAK,aAAa,MAAM;AACzC,UAAM,aAAa,OAAO,SAAS,+BAA+B,CAAC,KAAK;AACxE,UAAM,cAAc,KAAK,aAAa,gBAAgB;AAEtD,QAAI;AACJ,QAAI;AACJ,UAAM,SAAS;AAGf,UAAM,aAAY,kBAAa,GAAG,aAAhB,mBAA0B;AAC5C,UAAM,oBAAoB,cAAc,UAAU,QAAQ,KAAK,UAAU,MAAM;AAG/E,UAAM,WAAS,kBAAa,2BAAb,mBAAqC,MAAK;AACzD,UAAM,WAAS,kBAAa,2BAAb,mBAAqC,MAAK;AACzD,UAAM,wBAAwB,SAAS,KAAK,SAAS;AAErD,QAAI,CAAC,KAAK,aAAa,MAAM,uBAAuB;AAEhD,UAAI,uBAAuB;AACvB,eAAO,SAAU,aAAa;AAC9B,cAAM,SAAS;AAAA,MACnB,OAAO;AAEH,eAAO,OAAO,aAAa,aAAa;AACxC,cAAM;AAAA,MACV;AAAA,IACJ,WAAW,mBAAmB;AAE1B,YAAM,WAAW,SAAS,kCAAkC;AAC5D,cAAQ,UAAA;AAAA,QACJ,KAAK;AACD,iBAAO,UAAW,QAAQ;AAC1B,gBAAM,UAAW;AACjB;AAAA,QACJ,KAAK;AACD,iBAAO,UAAW,OAAO,aAAa;AACtC,gBAAM,UAAW;AACjB;AAAA,QACJ,KAAK;AACD,iBAAO,UAAW;AAClB,gBAAM,UAAW,MAAM,cAAc;AACrC;AAAA,QACJ,KAAK;AACD,iBAAO,UAAW;AAClB,gBAAM,UAAW,SAAS;AAC1B;AAAA,QACJ;AACI,iBAAO,UAAW,QAAQ;AAC1B,gBAAM,UAAW;AACjB;AAAA,MAAA;AAAA,IAEZ,WAAW,uBAAuB;AAE9B,aAAO,SAAU,aAAa;AAC9B,YAAM,SAAS;AAAA,IACnB,OAAO;AAEH,aAAO,OAAO,aAAa,aAAa;AACxC,YAAM;AAAA,IACV;AAEA,WAAO,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,OAAO,aAAa,aAAa,CAAC,CAAC;AACrE,UAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,OAAO,cAAc,cAAc,CAAC,CAAC;AAErE,SAAK,aAAa,MAAM,OAAO,GAAG,IAAI;AACtC,SAAK,aAAa,MAAM,MAAM,GAAG,GAAG;AAEpC,SAAK,sBAAsB,EAAE,GAAG,MAAM,GAAG,KAAK,OAAO,YAAY,QAAQ,YAAA;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA,EAKA,yBAAyB,iBAA2C;AAChE,QAAI,CAAC,gBAAiB;AAGtB,UAAM,WAAW,KAAK,aAAa,MAAM;AACzC,QAAI,SAAS,qCAAqC,MAAM,OAAO;AAC3D,sBAAgB,MAAM,UAAU;AAChC;AAAA,IACJ;AAGA,UAAM,eAAe,OAAO;AAC5B,QAAI,gBAAgB,CAAC,aAAa,MAAM,QAAQ;AAC5C,sBAAgB,MAAM,UAAU;AAChC;AAAA,IACJ;AAEA,UAAM,iBAAiB,KAAK,aAAa,MAAM;AAE/C,QAAI,gBAAgC;AAGpC,QAAI,kBAAkB,KAAK,cAAc;AAErC,UAAI,KAAK,qBAAqB;AAE1B,wBAAgB;AAAA,UACZ,MAAM,KAAK,oBAAoB;AAAA,UAC/B,KAAK,KAAK,oBAAoB;AAAA,UAC9B,OAAO,KAAK,oBAAoB,IAAI,KAAK,oBAAoB;AAAA,UAC7D,QAAQ,KAAK,oBAAoB,IAAI,KAAK,oBAAoB;AAAA,UAC9D,OAAO,KAAK,oBAAoB;AAAA,UAChC,QAAQ,KAAK,oBAAoB;AAAA,QAAA;AAAA,MAEzC,OAAO;AAEH,wBAAgB,KAAK,aAAa,sBAAA;AAAA,MACtC;AAAA,IACJ,WAES,gBAAgB,aAAa,MAAM,aAAa,GAAG,UAAU;AAClE,sBAAgB,aAAa,GAAG,SAAS,sBAAA;AAEzC,UAAI,cAAc,UAAU,KAAK,cAAc,WAAW,GAAG;AACzD,wBAAgB,MAAM,UAAU;AAChC;AAAA,MACJ;AAAA,IACJ;AAEA,QAAI,CAAC,cAAe;AAGpB,oBAAgB,MAAM,aAAa;AAEnC,UAAM,mBAAmB,KAAK,aAAa,MAAM,SAAS,iCAAiC,KAAK;AAChG,UAAM,SAAS;AAEf,QAAI;AACJ,QAAI;AAEJ,YAAQ,kBAAA;AAAA,MACJ,KAAK;AACD,eAAO,cAAc,OAAO,gBAAgB,cAAc;AAC1D,cAAM,cAAc;AACpB;AAAA,MACJ,KAAK;AACD,eAAO,cAAc,QAAQ;AAC7B,cAAM,cAAc;AACpB;AAAA,MACJ,KAAK;AACD,eAAO,cAAc;AACrB,cAAM,cAAc,MAAM,gBAAgB,eAAe;AACzD;AAAA,MACJ,KAAK;AACD,eAAO,cAAc;AACrB,cAAM,cAAc,SAAS;AAC7B;AAAA,MACJ,KAAK;AACD,eAAO,cAAc,QAAQ,gBAAgB;AAC7C,cAAM,cAAc,MAAM,gBAAgB,eAAe;AACzD;AAAA,MACJ;AACI,eAAO,cAAc,QAAQ;AAC7B,cAAM,cAAc;AAAA,IAAA;AAI5B,WAAO,KAAK,IAAI,IAAI,KAAK,IAAI,MAAM,OAAO,aAAa,gBAAgB,cAAc,EAAE,CAAC;AACxF,UAAM,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,OAAO,cAAc,gBAAgB,eAAe,EAAE,CAAC;AAExF,oBAAgB,MAAM,OAAO,GAAG,IAAI;AACpC,oBAAgB,MAAM,MAAM,GAAG,GAAG;AAGlC,oBAAgB,MAAM,UAAU;AAChC,oBAAgB,MAAM,aAAa;AAAA,EACvC;AACJ;"} \ No newline at end of file diff --git a/web/info-panel/StateManager.js b/web/info-panel/StateManager.js index 5591d62..93681f0 100644 --- a/web/info-panel/StateManager.js +++ b/web/info-panel/StateManager.js @@ -412,4 +412,3 @@ class StateManager { export { StateManager }; -//# sourceMappingURL=StateManager.js.map diff --git a/web/info-panel/StateManager.js.map b/web/info-panel/StateManager.js.map deleted file mode 100644 index 9970987..0000000 --- a/web/info-panel/StateManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"StateManager.js","sources":["../../src/info-panel/StateManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel State Manager (TypeScript)\n * \n * Centralized state management for the information panel.\n */\n\nimport type { GatheredInfo, SettingsChange } from '../types/comfyui';\nimport { getSettingValue, clamp } from '../shared/utils';\nimport { DEFAULT_PANEL_SETTINGS } from '../shared/settings';\n\nexport interface InfoPanelStateData {\n // Panel visibility and positioning\n isPanelVisible: boolean;\n wasPanelVisibleBeforeHide: boolean; // Persist state across glass toggles\n isPanelMinimized: boolean;\n isPanelPinned: boolean;\n isPanelLocked: boolean; // New lock state\n isAutoPinned: boolean; // Track if panel was auto-pinned by glass hide\n pinnedPosition: { x: number; y: number };\n lastPinnedPosition: { x: number; y: number } | null; // Remember last pinned location\n\n // Glass visibility\n isGlassPreviewVisible: boolean;\n\n // Info freezing\n isInfoHeld: boolean;\n\n // Section expansion\n expandedSections: Set;\n\n // Interaction states\n isPanelHovered: boolean;\n isHoveringNode: boolean;\n\n // Auto-collapse\n autoExpandTimer: ReturnType | null;\n lastNodeId: string | number | null;\n\n // Update scheduling\n updateScheduled: boolean;\n isInitialLoading: boolean;\n\n // Current data\n currentInfo: GatheredInfo | null;\n\n // Node selection override (user-selected node from dropdown)\n selectedNodeId: number | null;\n\n // Settings cache - using index signature for dynamic ComfyUI settings\n settings: { [key: string]: string | number | boolean };\n\n // Auto-detected theme\n currentTheme: string;\n}\n\n/**\n * State Manager class.\n * Handles all state-related operations and provides clean state transitions.\n */\nexport class StateManager {\n state: InfoPanelStateData;\n\n constructor() {\n this.state = {\n // Panel visibility and positioning\n isPanelVisible: false,\n wasPanelVisibleBeforeHide: false,\n isPanelMinimized: false,\n isPanelPinned: false,\n isPanelLocked: false, // New lock state\n isAutoPinned: false, // Track if panel was auto-pinned by glass hide\n pinnedPosition: { x: 0, y: 0 },\n lastPinnedPosition: null, // Remember last pinned location\n\n // Glass visibility\n isGlassPreviewVisible: true,\n isInfoHeld: false,\n\n // Section expansion\n expandedSections: new Set(['node']),\n\n // Interaction states\n isPanelHovered: false,\n isHoveringNode: false,\n\n // Auto-collapse\n autoExpandTimer: null,\n lastNodeId: null,\n\n // Update scheduling\n updateScheduled: false,\n isInitialLoading: false,\n\n // Current data\n currentInfo: null,\n\n // Node selection override\n selectedNodeId: null,\n\n // Settings cache\n settings: {},\n\n // Auto-detected theme\n currentTheme: 'dark' // Will be auto-detected\n };\n\n this.initThemeDetection();\n this.loadSettings();\n }\n\n initThemeDetection(): void {\n // Detect ComfyUI's current theme\n this.detectCurrentTheme();\n\n // Force an immediate check in case we missed the initial state\n setTimeout(() => {\n if (this.detectCurrentTheme()) {\n this.notifyThemeChange();\n }\n }, 1000);\n\n // Set up mutation observer to watch for theme changes\n this.setupThemeObserver();\n\n // Listen for system theme changes as fallback\n if (window.matchMedia) {\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');\n if (prefersDark.addListener) {\n prefersDark.addListener(() => this.detectCurrentTheme());\n } else if (prefersDark.addEventListener) {\n prefersDark.addEventListener('change', () => this.detectCurrentTheme());\n }\n }\n\n // Add click listener to theme toggle buttons for immediate detection\n this.setupThemeButtonListeners();\n }\n\n detectCurrentTheme(): boolean {\n let detectedTheme = 'dark'; // Default fallback\n\n // Method 1: Try to read from ComfyUI's color palette setting\n try {\n // ComfyUI stores theme in 'Comfy.ColorPalette' setting\n const colorPalette = getSettingValue('Comfy.ColorPalette', '');\n\n if (colorPalette) {\n // Extract theme name from various possible formats\n const paletteStr = String(colorPalette).toLowerCase();\n\n if (paletteStr.includes('obsidian') && paletteStr.includes('dark')) {\n detectedTheme = 'obsidian-dark';\n } else if (paletteStr.includes('obsidian')) {\n detectedTheme = 'obsidian';\n } else if (paletteStr.includes('milk') || paletteStr.includes('white')) {\n detectedTheme = 'milk-white';\n } else if (paletteStr.includes('solarized')) {\n detectedTheme = 'solarized';\n } else if (paletteStr.includes('arc')) {\n detectedTheme = 'arc';\n } else if (paletteStr.includes('nord')) {\n detectedTheme = 'nord';\n } else if (paletteStr.includes('github')) {\n detectedTheme = 'github';\n } else if (paletteStr.includes('light')) {\n detectedTheme = 'light';\n } else if (paletteStr.includes('dark')) {\n detectedTheme = 'dark';\n }\n }\n } catch (e) {\n // Fallback to brightness-based detection\n console.warn('ComfyUI Info Panel: Could not read theme from settings, using fallback detection');\n }\n\n // Method 2: Fallback - check body/html for theme classes or data attributes\n if (detectedTheme === 'dark') {\n const body = document.body;\n const html = document.documentElement;\n\n // Check for data-theme attribute\n const dataTheme = body.getAttribute('data-theme') || html.getAttribute('data-theme');\n if (dataTheme) {\n detectedTheme = dataTheme.toLowerCase();\n }\n\n // Check for theme classes\n const classes = (body.className + ' ' + html.className).toLowerCase();\n if (classes.includes('theme-obsidian-dark') || classes.includes('obsidian-dark')) {\n detectedTheme = 'obsidian-dark';\n } else if (classes.includes('theme-obsidian') || classes.includes('obsidian')) {\n detectedTheme = 'obsidian';\n } else if (classes.includes('theme-milk-white') || classes.includes('milk-white') || classes.includes('milk_white')) {\n detectedTheme = 'milk-white';\n } else if (classes.includes('theme-solarized') || classes.includes('solarized')) {\n detectedTheme = 'solarized';\n } else if (classes.includes('theme-arc') || classes.includes('arc-theme')) {\n detectedTheme = 'arc';\n } else if (classes.includes('theme-nord') || classes.includes('nord-theme')) {\n detectedTheme = 'nord';\n } else if (classes.includes('theme-github') || classes.includes('github-theme')) {\n detectedTheme = 'github';\n } else if (classes.includes('theme-light') || classes.includes('light-theme')) {\n detectedTheme = 'light';\n } else if (classes.includes('theme-dracula') || classes.includes('dracula-theme')) {\n detectedTheme = 'dracula';\n } else if (classes.includes('theme-gruvbox') || classes.includes('gruvbox-theme')) {\n detectedTheme = 'gruvbox';\n } else if (classes.includes('theme-monokai') || classes.includes('monokai-theme')) {\n detectedTheme = 'monokai';\n }\n }\n\n // Method 3: Last resort - brightness detection for light vs dark\n if (detectedTheme === 'dark') {\n const bodyStyles = window.getComputedStyle(document.body);\n const bgColor = bodyStyles.backgroundColor;\n if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {\n const rgbMatch = bgColor.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);\n if (rgbMatch) {\n const [, r, g, b] = rgbMatch.map(Number);\n const brightness = (r * 299 + g * 587 + b * 114) / 1000;\n if (brightness > 180) {\n detectedTheme = 'light';\n }\n }\n }\n }\n\n // Update theme if it changed\n if (this.state.currentTheme !== detectedTheme) {\n this.state.currentTheme = detectedTheme;\n return true; // Theme changed\n }\n\n return false; // No change\n }\n\n setupThemeObserver(): void {\n // Watch for changes to body/html classes and attributes\n const observer = new MutationObserver((mutations) => {\n let shouldCheck = false;\n mutations.forEach((mutation) => {\n if (mutation.type === 'attributes' &&\n (mutation.attributeName === 'class' ||\n mutation.attributeName === 'data-theme' ||\n mutation.attributeName === 'style')) {\n shouldCheck = true;\n }\n });\n\n if (shouldCheck && this.detectCurrentTheme()) {\n // Theme changed, update UI immediately\n this.notifyThemeChange();\n }\n });\n\n // Observe changes to body, html, and vue app\n observer.observe(document.body, { attributes: true, attributeOldValue: true });\n observer.observe(document.documentElement, { attributes: true, attributeOldValue: true });\n\n const vueApp = document.querySelector('#vue-app');\n if (vueApp) {\n observer.observe(vueApp, { attributes: true, attributeOldValue: true });\n }\n\n // Also watch for changes to any element with theme-related classes\n const themeElements = document.querySelectorAll('[class*=\"theme\"], [class*=\"dark\"], [class*=\"light\"], [data-theme]');\n themeElements.forEach(el => {\n observer.observe(el, { attributes: true, attributeOldValue: true });\n });\n\n // Periodic fallback check every 2 seconds\n setInterval(() => {\n if (this.detectCurrentTheme()) {\n this.notifyThemeChange();\n }\n }, 2000);\n }\n\n setupThemeButtonListeners(): void {\n // Listen for clicks on theme toggle buttons\n document.addEventListener('click', (e) => {\n // Check if clicked element might be a theme toggle\n const target = e.target as HTMLElement;\n const isThemeButton = target.textContent?.toLowerCase().includes('theme') ||\n target.textContent?.toLowerCase().includes('light') ||\n target.textContent?.toLowerCase().includes('dark') ||\n target.title?.toLowerCase().includes('theme') ||\n (typeof target.className === 'string' && target.className.toLowerCase().includes('theme'));\n\n if (isThemeButton) {\n setTimeout(() => {\n if (this.detectCurrentTheme()) {\n this.notifyThemeChange();\n }\n }, 100);\n }\n });\n }\n\n notifyThemeChange(): void {\n // Update settings to reflect new theme\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTheme\"] = this.state.currentTheme;\n\n // Notify UI components of theme change immediately\n if (window.infoPanelManager && window.infoPanelManager.uiManager) {\n window.infoPanelManager.uiManager.updateTheme(this.state.currentTheme);\n }\n\n // Notify Pop-Out Manager (if it exists on the main glass instance)\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.popOutManager) {\n magnifyGlass.popOutManager.updateTheme(this.state.currentTheme);\n }\n }\n\n loadSettings(): void {\n const defaults = DEFAULT_PANEL_SETTINGS;\n\n Object.keys(defaults).forEach(key => {\n // Skip the theme setting since we auto-detect it\n if (key !== \"🔍MagnifyGlass.InfoPanelTheme\") {\n this.state.settings[key] = getSettingValue(key, (defaults as any)[key]);\n }\n });\n\n // Validate numeric settings\n this.state.settings[\"🔍MagnifyGlass.InfoPanelWidth\"] = this.validateNumber(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelWidth\"], 200, 600, defaults[\"🔍MagnifyGlass.InfoPanelWidth\"] as number\n );\n\n this.state.settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"] = this.validateNumber(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"], 200, 1500, defaults[\"🔍MagnifyGlass.InfoPanelMaxHeight\"] as number\n );\n\n this.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"] = this.validateNumber(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"], 10, 100, defaults[\"🔍MagnifyGlass.InfoPanelOpacity\"] as number\n );\n\n this.state.settings[\"🔍MagnifyGlass.InfoPanelFontSize\"] = this.validateNumber(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelFontSize\"], 8, 24, defaults[\"🔍MagnifyGlass.InfoPanelFontSize\"] as number\n );\n\n // Validate colors\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTextColor\"] = this.validateColor(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTextColor\"], defaults[\"🔍MagnifyGlass.InfoPanelTextColor\"] as string\n );\n\n this.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] = this.validateColor(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"], defaults[\"🔍MagnifyGlass.InfoPanelAccentColor\"] as string\n );\n\n // Validate Enums\n const validPanelPositions = ['Bottom', 'Top', 'Left', 'Right'];\n this.state.settings[\"🔍MagnifyGlass.InfoPanelPosition\"] = this.validateStringOption(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelPosition\"], validPanelPositions, defaults[\"🔍MagnifyGlass.InfoPanelPosition\"] as string\n );\n\n const validControlPositions = ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top', 'bottom', 'left', 'right'];\n this.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] = this.validateStringOption(\n this.state.settings[\"🔍MagnifyGlass.ControlsPosition\"], validControlPositions, defaults[\"🔍MagnifyGlass.ControlsPosition\"] as string\n );\n\n const validFonts = [\n 'System Default', 'Inter', 'Roboto', 'JetBrains Mono', 'Fira Code',\n 'IBM Plex Sans', 'Space Grotesk', 'Lexend', 'Outfit', 'monospace', 'system-ui'\n ];\n this.state.settings[\"🔍MagnifyGlass.InfoPanelFontFamily\"] = this.validateStringOption(\n this.state.settings[\"🔍MagnifyGlass.InfoPanelFontFamily\"], validFonts, defaults[\"🔍MagnifyGlass.InfoPanelFontFamily\"] as string\n );\n\n // Set theme to auto-detected value\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTheme\"] = this.state.currentTheme;\n }\n\n /**\n * Validate numeric input with bounds checking.\n */\n private validateNumber(value: unknown, min: number, max: number, fallback: number): number {\n const num = Number(value);\n if (isNaN(num)) return fallback;\n return clamp(num, min, max);\n }\n\n /**\n * Validate color string (hex).\n */\n private validateColor(color: unknown, fallback: string): string {\n if (!color || typeof color !== 'string') return fallback;\n // Basic hex validation: #RGB, #RRGGBB, #RRGGBBAA\n if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color)) {\n return color;\n }\n return fallback;\n }\n\n /**\n * Validate string against an allowlist.\n */\n private validateStringOption(value: unknown, options: string[], fallback: string): string {\n if (typeof value !== 'string') return fallback;\n return options.includes(value) ? value : fallback;\n }\n\n updateSettings(): Record {\n const oldSettings = { ...this.state.settings };\n this.loadSettings();\n\n // Return what changed for reactive updates\n const changes: Record = {};\n Object.keys(this.state.settings).forEach(key => {\n if (oldSettings[key] !== this.state.settings[key]) {\n changes[key] = {\n old: oldSettings[key],\n new: this.state.settings[key]\n };\n }\n });\n\n return changes;\n }\n\n togglePanelVisibility(): boolean {\n this.state.isPanelVisible = !this.state.isPanelVisible;\n return this.state.isPanelVisible;\n }\n\n toggleGlassPreview(): boolean {\n this.state.isGlassPreviewVisible = !this.state.isGlassPreviewVisible;\n return this.state.isGlassPreviewVisible;\n }\n\n togglePinning(): boolean {\n const wasPinned = this.state.isPanelPinned;\n this.state.isPanelPinned = !this.state.isPanelPinned;\n\n // When unpinning, also unlock and clear auto-pin flag\n if (!this.state.isPanelPinned) {\n this.state.isPanelLocked = false;\n this.state.isAutoPinned = false; // Clear auto-pin flag when manually unpinning\n } else {\n // When manually pinning, also clear auto-pin flag (this is a user-initiated pin)\n this.state.isAutoPinned = false;\n }\n\n if (this.state.isPanelPinned && this.state.lastPinnedPosition) {\n // Restore to last pinned position\n this.state.pinnedPosition = { ...this.state.lastPinnedPosition };\n } else if (!this.state.isPanelPinned && wasPinned) {\n // Save current position when unpinning\n this.state.lastPinnedPosition = { ...this.state.pinnedPosition };\n }\n\n return this.state.isPanelPinned;\n }\n\n toggleLocking(): boolean {\n // Only allow locking when pinned\n if (!this.state.isPanelPinned) {\n return false;\n }\n\n this.state.isPanelLocked = !this.state.isPanelLocked;\n return this.state.isPanelLocked;\n }\n\n toggleMinimized(): boolean {\n this.state.isPanelMinimized = !this.state.isPanelMinimized;\n return this.state.isPanelMinimized;\n }\n\n toggleHold(): boolean {\n this.state.isInfoHeld = !this.state.isInfoHeld;\n return this.state.isInfoHeld;\n }\n\n toggleSection(sectionId: string): boolean {\n if (sectionId === 'node') return false; // Node section always expanded\n\n if (this.state.expandedSections.has(sectionId)) {\n this.state.expandedSections.delete(sectionId);\n } else {\n this.state.expandedSections.add(sectionId);\n }\n return true;\n }\n\n setPinnedPosition(x: number, y: number): void {\n this.state.pinnedPosition = { x, y };\n // Also save as last pinned position for memory\n this.state.lastPinnedPosition = { x, y };\n }\n\n setCurrentInfo(info: GatheredInfo): void {\n this.state.currentInfo = info;\n }\n\n /**\n * Set the selected node ID for manual override.\n * When set, the inspector will display this node instead of the hovered node.\n */\n setSelectedNode(id: number | null): void {\n this.state.selectedNodeId = id;\n }\n\n /**\n * Clear the selected node, returning to hover-based detection.\n */\n clearSelectedNode(): void {\n this.state.selectedNodeId = null;\n }\n\n scheduleAutoCollapse(): void {\n this.clearAutoExpandTimer();\n if (this.state.settings[\"🔍MagnifyGlass.InfoPanelAnimations\"]) {\n this.state.autoExpandTimer = setTimeout(() => {\n if (!this.state.isPanelHovered) {\n this.collapseNodeSections();\n }\n }, 1500);\n }\n }\n\n clearAutoExpandTimer(): void {\n if (this.state.autoExpandTimer) {\n clearTimeout(this.state.autoExpandTimer);\n this.state.autoExpandTimer = null;\n }\n }\n\n expandNodeSections(): void {\n this.state.expandedSections.add('hoveredNode');\n this.state.expandedSections.add('node');\n this.state.expandedSections.add('cursor');\n this.state.expandedSections.add('canvas');\n this.state.expandedSections.add('magnifier');\n this.clearAutoExpandTimer();\n }\n\n collapseNodeSections(): void {\n this.state.expandedSections.delete('hoveredNode');\n this.state.expandedSections.delete('node');\n this.state.expandedSections.delete('widget');\n }\n\n cleanup(): void {\n this.clearAutoExpandTimer();\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/UIManager.js b/web/info-panel/UIManager.js index 9bb83e3..16c1439 100644 --- a/web/info-panel/UIManager.js +++ b/web/info-panel/UIManager.js @@ -1396,4 +1396,3 @@ class UIManager { export { UIManager }; -//# sourceMappingURL=UIManager.js.map diff --git a/web/info-panel/UIManager.js.map b/web/info-panel/UIManager.js.map deleted file mode 100644 index d652df9..0000000 --- a/web/info-panel/UIManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"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 { escapeHtml } from '../shared/utils';\nimport { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';\nimport {\n getCheckpointInfo,\n getImageInfo,\n getTextBoxContent,\n getImportantNodeParameters,\n type ImageInfoResult,\n type ParameterItem\n} from './NodeDataExtractor';\nimport { NodeSelector, type NodeListEntry, type NodeExecOrderEntry } from './NodeSelector';\nimport {\n WidgetEditorFactory,\n WidgetSyncManager,\n InlineControlFactory,\n DragValueController,\n type WidgetEditorInstance,\n type InlineControlInstance\n} from './widget-editors';\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 private currentDropdownCleanup: (() => void) | null = null;\n onNodeSelected: ((nodeId: number) => void) | null = null;\n // Track active widget editors for cleanup\n private activeEditors: Map = new Map();\n // Track active inline controls for cleanup\n private activeInlineControls: Map = new Map();\n // Track active drag controllers for cleanup\n private activeDragControllers: Map = new Map();\n // Hotkey handler for Focus Node\n private hotkeyHandler: ((e: KeyboardEvent) => 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 this.setupHotkeys();\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
${Icons.magnifyGlass}
\n
Inspector
\n
Real-time analysis
\n
\n
\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 this.updateMinimizedState(); // Correctly set aria-expanded state\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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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 holdBtn.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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.setAttribute('aria-pressed', String(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 // 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 // 7. Apply High Contrast Text Class\n const isHighContrastText = !!settings[\"🔍MagnifyGlass.HighContrastText\"];\n if (isHighContrastText) {\n this.elements.panel.classList.add('high-contrast-text');\n } else {\n this.elements.panel.classList.remove('high-contrast-text');\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 minimizeBtn.setAttribute('aria-expanded', String(!this.stateManager.state.isPanelMinimized));\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 // Use [\\w-]+ to match hyphenated theme names like milk-white, obsidian-dark\n this.elements.panel.className = this.elements.panel.className.replace(/theme-[\\w-]+/, `theme-${newTheme.toLowerCase()}`);\n\n // Reapply opacity setting after theme change\n const opacityPercent = Number(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\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 // Skip re-rendering if user is actively editing a value\n // This prevents the editor from being destroyed mid-edit\n if (this.activeEditors.size > 0) {\n // Only update the header subtitle, don't re-render content\n this.updateHeaderSubtitle(info);\n return;\n }\n\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 isHtml: true\n });\n\n // Add category if available\n if (info.hoveredNode.category) {\n nodeContent.push({ label: 'Category', value: info.hoveredNode.category, copyable: true });\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, copyable: true });\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 // Cleanup existing editors before re-rendering\n this.cleanupEditors();\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 // Check if Sticky Info is enabled (editing only works when sticky)\n const isStickyEnabled = !!this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelPersist\"];\n\n this.elements.content.innerHTML = sections.map(section => `\n
\n
\n ${section.icon}\n ${escapeHtml(section.title)}\n ${section.badge ? `${escapeHtml(section.badge)}` : ''}\n ${section.id !== 'node' ? `${Icons.chevronRight}` : ''}\n
\n
\n
\n ${section.content.map((item: any) => {\n const value = item.isHtml ? item.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\n // Editable widget detection\n const isEditable = item.isEditable && isStickyEnabled && item.widgetName && item.nodeId !== undefined;\n const editableClass = isEditable ? 'editable' : '';\n // Serialize constraints and rawValue properly (handle boolean specially to avoid \"false\" -> true coercion)\n const constraintsJson = isEditable && item.constraints ? escapeHtml(JSON.stringify(item.constraints)) : '';\n const rawValueStr = isEditable ? (typeof item.rawValue === 'boolean' ? (item.rawValue ? 'true' : 'false') : escapeHtml(String(item.rawValue ?? ''))) : '';\n const editableAttrs = isEditable ? `data-editable=\"true\" data-widget-name=\"${escapeHtml(item.widgetName)}\" data-widget-type=\"${escapeHtml(item.widgetType || 'text')}\" data-raw-value=\"${rawValueStr}\" data-constraints=\"${constraintsJson}\"` : '';\n\n // Actionable (button) widget detection - these have callbacks but aren't value-editable\n const isActionable = item.isActionable && item.widgetName && item.nodeId !== undefined;\n const actionableClass = isActionable ? 'actionable' : '';\n const actionableAttrs = isActionable ? `data-actionable=\"true\" data-widget-name=\"${escapeHtml(item.widgetName)}\" data-widget-type=\"button\"` : '';\n\n // Only show dropdown arrow for actual dropdowns, not actions like zoom\n const dropdownIcon = (item.clickable && item.clickable !== 'zoom') ? '▼' : '';\n // Add copy button for copyable items (category, path) - placed as direct child of row for absolute positioning\n const copyButton = item.copyable ? `` : '';\n return `\n
\n ${copyButton}\n ${escapeHtml(item.label)}\n ${value}${dropdownIcon}\n
\n
\n
`;\n }).join('')}\n
\n
\n
`).join('');\n\n // Add click handlers for clickable rows\n this.attachDropdownClickHandlers();\n\n // Add click handlers for copy buttons\n this.attachCopyButtonHandlers();\n\n // Add click handlers for editable rows (only when sticky is enabled)\n if (isStickyEnabled) {\n this.attachEditableRowHandlers();\n }\n\n // Add handlers for actionable (button) rows - always active\n this.attachActionableRowHandlers();\n }\n\n /**\n * Attach handlers for actionable (button) widget rows.\n * Creates inline button controls that invoke widget callbacks.\n */\n private attachActionableRowHandlers(): void {\n if (!this.elements.content) return;\n\n const actionableRows = this.elements.content.querySelectorAll('[data-actionable=\"true\"]');\n actionableRows.forEach(row => {\n const rowEl = row as HTMLElement;\n const valueEl = rowEl.querySelector('.info-value.original') as HTMLElement;\n const inlineContainer = rowEl.querySelector('.inline-control-container') as HTMLElement;\n\n if (!valueEl || !inlineContainer) return;\n\n const nodeId = parseInt(rowEl.dataset.nodeId || '0', 10);\n const widgetName = rowEl.dataset.widgetName || '';\n\n if (isNaN(nodeId) || !widgetName) return;\n\n const controlKey = `action:${nodeId}:${widgetName}`;\n\n // Create inline button control\n const control = InlineControlFactory.createControl({\n nodeId,\n widgetName,\n widgetType: 'button',\n currentValue: widgetName, // Use widget name as button context\n onChange: () => { } // Buttons don't change values\n });\n\n if (control) {\n this.activeInlineControls.set(controlKey, control);\n inlineContainer.appendChild(control.element);\n inlineContainer.style.display = 'flex';\n // Hide the text value display (which shows null/true/false)\n valueEl.style.display = 'none';\n }\n });\n }\n\n /**\n * Cleanup active widget editors, inline controls, and drag controllers.\n */\n private cleanupEditors(): void {\n this.activeEditors.forEach(editor => {\n try {\n editor.destroy();\n } catch (e) {\n // Ignore cleanup errors\n }\n });\n this.activeEditors.clear();\n\n this.activeInlineControls.forEach(control => {\n try {\n control.destroy();\n } catch (e) {\n // Ignore cleanup errors\n }\n });\n this.activeInlineControls.clear();\n\n this.activeDragControllers.forEach(controller => {\n try {\n controller.destroy();\n } catch (e) {\n // Ignore cleanup errors\n }\n });\n this.activeDragControllers.clear();\n }\n\n /**\n * Attach click handlers to editable widget rows.\n * Also creates inline controls for toggle/combo widgets and drag controllers.\n */\n private attachEditableRowHandlers(): void {\n if (!this.elements.content) return;\n\n const editableRows = this.elements.content.querySelectorAll('[data-editable=\"true\"]');\n editableRows.forEach(row => {\n const rowEl = row as HTMLElement;\n const valueEl = rowEl.querySelector('.info-value.original') as HTMLElement;\n const editorContainer = rowEl.querySelector('.widget-editor-container') as HTMLElement;\n const inlineContainer = rowEl.querySelector('.inline-control-container') as HTMLElement;\n\n if (!valueEl) return;\n\n const nodeId = parseInt(rowEl.dataset.nodeId || '0', 10);\n const widgetName = rowEl.dataset.widgetName || '';\n const widgetType = rowEl.dataset.widgetType || 'text';\n const rawValue = rowEl.dataset.rawValue;\n\n // Parse constraints\n let constraints: any = {};\n try {\n const constraintsStr = rowEl.dataset.constraints;\n if (constraintsStr) {\n constraints = JSON.parse(constraintsStr);\n }\n } catch (e) {\n // Ignore parse errors\n }\n\n if (isNaN(nodeId) || !widgetName) return;\n\n const controlKey = `${nodeId}:${widgetName}`;\n\n // Check if this widget type should use inline controls\n if (InlineControlFactory.shouldUseInlineControl(widgetType) && inlineContainer) {\n // Create inline control\n const control = InlineControlFactory.createControl({\n nodeId,\n widgetName,\n widgetType,\n currentValue: widgetType.toLowerCase() === 'boolean' || widgetType.toLowerCase() === 'toggle'\n ? rawValue === 'true'\n : rawValue,\n constraints,\n onChange: (value) => {\n rowEl.dataset.rawValue = String(value);\n }\n });\n\n if (control) {\n this.activeInlineControls.set(controlKey, control);\n inlineContainer.appendChild(control.element);\n inlineContainer.style.display = 'flex';\n // Hide the text value display\n valueEl.style.display = 'none';\n }\n } else if (editorContainer) {\n // Fall back to click-to-edit for text/number types\n valueEl.addEventListener('click', (e) => {\n e.stopPropagation();\n this.enterEditMode(rowEl, valueEl, editorContainer);\n });\n }\n\n // Attach drag controller if widget type supports it and we don't have an inline control\n if (DragValueController.isTypeSupported(widgetType) &&\n !InlineControlFactory.shouldUseInlineControl(widgetType)) {\n const dragController = new DragValueController(rowEl, {\n nodeId,\n widgetName,\n widgetType,\n currentValue: rawValue,\n constraints,\n onChange: (value) => {\n rowEl.dataset.rawValue = String(value);\n // Update displayed value\n valueEl.textContent = formatWidgetValue(value);\n }\n });\n this.activeDragControllers.set(controlKey, dragController);\n }\n });\n }\n\n /**\n * Enter edit mode for a row.\n */\n private enterEditMode(row: HTMLElement, valueEl: HTMLElement, container: HTMLElement): void {\n // Exit if already editing\n if (row.classList.contains('editing')) return;\n\n const nodeId = parseInt(row.dataset.nodeId || '0', 10);\n const widgetName = row.dataset.widgetName || '';\n const widgetType = row.dataset.widgetType || 'text';\n const rawValue = row.dataset.rawValue;\n\n // Use isNaN check instead of !nodeId to allow nodeId 0\n if (isNaN(nodeId) || !widgetName) return;\n\n // Get current constraints from the data extractor\n let constraints: any = {};\n try {\n // Try to parse constraints if they were stored\n const constraintsStr = row.dataset.constraints;\n if (constraintsStr) {\n constraints = JSON.parse(constraintsStr);\n }\n } catch (e) {\n // Ignore parse errors\n }\n\n // Create the editor\n const editorKey = `${nodeId}:${widgetName}`;\n const editor = WidgetEditorFactory.createEditor({\n nodeId,\n widgetName,\n widgetType,\n currentValue: rawValue,\n constraints,\n onChange: (value) => {\n // Update the raw value attribute for next time\n row.dataset.rawValue = String(value);\n },\n onBlur: () => {\n // Exit edit mode after a small delay (allows for clicks within editor)\n // Capture the current editor reference to avoid race condition\n const currentEditor = this.activeEditors.get(editorKey);\n setTimeout(() => {\n // Only exit if the same editor is still active (not replaced by a new one)\n if (currentEditor && this.activeEditors.get(editorKey) === currentEditor) {\n if (!container.contains(document.activeElement)) {\n this.exitEditMode(row, valueEl, container);\n }\n }\n }, 100);\n }\n });\n\n // Track the editor\n this.activeEditors.set(editorKey, editor);\n\n // Show editor, hide value\n row.classList.add('editing');\n valueEl.style.display = 'none';\n container.style.display = 'block';\n container.innerHTML = '';\n container.appendChild(editor.element);\n\n // Focus the editor\n editor.focus();\n }\n\n /**\n * Exit edit mode for a row.\n */\n private exitEditMode(row: HTMLElement, valueEl: HTMLElement, container: HTMLElement): void {\n if (!row.classList.contains('editing')) return;\n\n const nodeId = row.dataset.nodeId || '';\n const widgetName = row.dataset.widgetName || '';\n const editorKey = `${nodeId}:${widgetName}`;\n\n // Get and destroy the editor\n const editor = this.activeEditors.get(editorKey);\n if (editor) {\n // Get the actual constrained value from the widget (not the unconstrained input value)\n // This ensures displayed value matches what was actually stored\n const actualValue = WidgetSyncManager.getWidgetValue(parseInt(nodeId, 10), widgetName);\n valueEl.textContent = formatWidgetValue(actualValue ?? editor.getValue());\n editor.destroy();\n this.activeEditors.delete(editorKey);\n }\n\n // Hide editor, show value\n row.classList.remove('editing');\n valueEl.style.display = '';\n container.style.display = 'none';\n container.innerHTML = '';\n }\n\n /**\n * Attach click handlers to copy buttons for copying values to clipboard.\n */\n private attachCopyButtonHandlers(): void {\n if (!this.elements.content) return;\n\n const copyButtons = this.elements.content.querySelectorAll('.copy-btn');\n copyButtons.forEach(btn => {\n btn.addEventListener('click', async (e) => {\n e.stopPropagation();\n e.preventDefault();\n\n const button = btn as HTMLButtonElement;\n const valueToCopy = button.dataset.copyValue || '';\n\n try {\n await navigator.clipboard.writeText(valueToCopy);\n\n // Visual feedback - temporarily change to checkmark\n const originalHtml = button.innerHTML;\n button.innerHTML = '✓';\n button.classList.add('copied');\n\n setTimeout(() => {\n button.innerHTML = originalHtml;\n button.classList.remove('copied');\n }, 1500);\n } catch (err) {\n Logger.error('[UIManager] Failed to copy to clipboard:', err);\n }\n });\n });\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\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 // Accessibility attributes\n dropdown.setAttribute('role', 'listbox');\n dropdown.setAttribute('tabindex', '-1');\n dropdown.setAttribute('aria-label', type === 'title' ? 'Select Node by Title' : (type === 'execOrder' ? 'Select Node by Execution Order' : 'Select Node by ID'));\n\n dropdown.style.cssText = `\n position: fixed;\n z-index: 100000;\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 outline: none;\n `;\n\n let activeIndex = 0; // Default to first item\n\n // Build dropdown items\n nodes.forEach((node, index) => {\n const item = document.createElement('div');\n item.className = 'dropdown-item';\n item.setAttribute('role', 'option');\n item.setAttribute('aria-selected', index === activeIndex ? 'true' : 'false');\n if (index === activeIndex) {\n item.classList.add('focused');\n }\n\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 background: ${index === activeIndex ? 'var(--comfy-input-bg, #3a3a3a)' : ''};\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 ${escapeHtml(execNode.title)} (#${execNode.id})\n ${escapeHtml(execNode.type)}\n `;\n } else {\n item.innerHTML = `\n ${escapeHtml(node.title)} (#${node.id})\n ${escapeHtml(node.type)}\n `;\n }\n\n // Hover effect - also update active state for keyboard compatibility\n item.addEventListener('mouseenter', () => {\n // Update visual state of previously active item\n if (activeIndex !== index) {\n const items = dropdown.querySelectorAll('.dropdown-item');\n if (items[activeIndex]) {\n const prev = items[activeIndex] as HTMLElement;\n prev.style.background = '';\n prev.classList.remove('focused');\n prev.setAttribute('aria-selected', 'false');\n }\n activeIndex = index;\n item.classList.add('focused');\n item.setAttribute('aria-selected', 'true');\n }\n item.style.background = 'var(--comfy-input-bg, #3a3a3a)';\n });\n item.addEventListener('mouseleave', () => {\n // Don't clear background if it's the active item (keep it focused)\n if (index !== activeIndex) {\n item.style.background = '';\n }\n });\n\n // Selection handler\n item.addEventListener('click', (e) => {\n e.stopPropagation();\n this.hideDropdown();\n // cleanup() is called by 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 within viewport bounds\n document.body.appendChild(dropdown);\n this.positionDropdownWithinViewport(dropdown, anchorElement);\n\n this.currentDropdown = dropdown;\n\n // Cleanup function to remove all listeners\n const cleanup = () => {\n document.removeEventListener('mousedown', closeHandler, true);\n window.removeEventListener('keydown', keyHandler, true); // Match window listener\n if (this.elements.panel) {\n this.elements.panel.removeEventListener('mousedown', panelCloseHandler, true);\n }\n // Remove canvas listener\n const canvas = document.querySelector('canvas.main-canvas, canvas') as HTMLCanvasElement;\n if (canvas) {\n canvas.removeEventListener('pointerdown', canvasCloseHandler, true);\n }\n // Clear the global reference\n if (this.currentDropdownCleanup === cleanup) {\n this.currentDropdownCleanup = null;\n }\n };\n\n // Store cleanup globally so hideDropdown can call it\n this.currentDropdownCleanup = cleanup;\n\n // Close on click outside - use mousedown with capture to ensure it fires before other handlers\n const closeHandler = (e: MouseEvent) => {\n if (!dropdown.contains(e.target as Node) && !anchorElement.contains(e.target as Node)) {\n this.hideDropdown();\n // Cleanup is now handled by hideDropdown calling this.currentDropdownCleanup\n }\n };\n\n // Close when clicking within the panel but outside dropdown\n const panelCloseHandler = (e: MouseEvent) => {\n if (!dropdown.contains(e.target as Node) && !anchorElement.contains(e.target as Node)) {\n this.hideDropdown();\n // Cleanup is now handled by hideDropdown calling this.currentDropdownCleanup\n }\n };\n\n // Close when clicking on the canvas (canvas intercepts events before document receives them)\n const canvasCloseHandler = (_e: PointerEvent) => {\n this.hideDropdown();\n };\n\n // Helper to update active item\n const updateActiveItem = (newIndex: number) => {\n const items = dropdown.querySelectorAll('.dropdown-item');\n if (items.length === 0) return;\n\n // Clamp index\n if (newIndex < 0) newIndex = 0;\n if (newIndex >= items.length) newIndex = items.length - 1;\n\n // Remove focus from old\n if (activeIndex >= 0 && activeIndex < items.length) {\n const oldItem = items[activeIndex] as HTMLElement;\n oldItem.classList.remove('focused');\n oldItem.setAttribute('aria-selected', 'false');\n oldItem.style.background = '';\n }\n\n activeIndex = newIndex;\n\n // Add focus to new\n const newItem = items[activeIndex] as HTMLElement;\n newItem.classList.add('focused');\n newItem.setAttribute('aria-selected', 'true');\n newItem.style.background = 'var(--comfy-input-bg, #3a3a3a)';\n\n // Scroll into view\n newItem.scrollIntoView({ block: 'nearest' });\n };\n\n // Keyboard handler - works while dropdown is open regardless of focus\n // Uses window-level listener with stopImmediatePropagation to intercept before ComfyUI/LiteGraph\n const keyHandler = (e: KeyboardEvent) => {\n // DEBUG: Log all key events while dropdown is open\n console.debug('[DropdownKeyHandler] Key pressed:', e.key, 'activeIndex:', activeIndex);\n\n // Handle arrow keys and Enter for navigation regardless of focus\n // Only Escape should check focus to avoid intercepting when user tabs away\n\n if (e.key === 'Escape') {\n // For Escape, require focus to avoid breaking other components\n if (document.activeElement !== dropdown && !dropdown.contains(document.activeElement)) {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n this.hideDropdown();\n return;\n }\n\n // Arrow keys and Enter work while dropdown is visible\n if (e.key === 'ArrowDown') {\n console.debug('[DropdownKeyHandler] ArrowDown - moving to', activeIndex + 1);\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n updateActiveItem(activeIndex + 1);\n } else if (e.key === 'ArrowUp') {\n console.debug('[DropdownKeyHandler] ArrowUp - moving to', activeIndex - 1);\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n updateActiveItem(activeIndex - 1);\n } else if (e.key === 'Enter') {\n console.debug('[DropdownKeyHandler] Enter - selecting', activeIndex);\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n const items = dropdown.querySelectorAll('.dropdown-item');\n if (activeIndex >= 0 && activeIndex < items.length) {\n (items[activeIndex] as HTMLElement).click();\n }\n }\n };\n\n // Delay adding the listener to avoid immediate closure\n setTimeout(() => {\n // Check if dropdown was already closed (cleanup reference changed or nulled)\n // or if it was removed from DOM\n if (this.currentDropdownCleanup !== cleanup || !dropdown.parentNode) {\n return;\n }\n\n document.addEventListener('mousedown', closeHandler, true); // true = capture phase\n // Use window-level listener with capture phase to intercept before ComfyUI canvas handlers\n window.addEventListener('keydown', keyHandler, true);\n\n // Also focus the dropdown for accessibility\n dropdown.focus();\n\n // Also listen on the panel itself with capture phase\n if (this.elements.panel) {\n this.elements.panel.addEventListener('mousedown', panelCloseHandler, true);\n }\n\n // Listen on canvas for clicks (canvas intercepts pointer events)\n const canvas = document.querySelector('canvas.main-canvas, canvas') as HTMLCanvasElement;\n if (canvas) {\n canvas.addEventListener('pointerdown', canvasCloseHandler, true);\n }\n }, 10);\n }\n\n /**\n * Hide the current dropdown.\n */\n hideDropdown(): void {\n // Run cleanup if it exists\n if (this.currentDropdownCleanup) {\n this.currentDropdownCleanup();\n this.currentDropdownCleanup = null;\n }\n\n if (this.currentDropdown && this.currentDropdown.parentNode) {\n this.currentDropdown.parentNode.removeChild(this.currentDropdown);\n this.currentDropdown = null;\n }\n }\n\n /**\n * Position a dropdown within viewport bounds.\n * Allows dropdown to expand to fit content, constrained by available space.\n */\n private positionDropdownWithinViewport(\n dropdown: HTMLElement,\n anchor: HTMLElement\n ): void {\n const margin = 10;\n const gap = 4;\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n const anchorRect = anchor.getBoundingClientRect();\n\n // Temporarily remove constraints to measure natural size\n dropdown.style.maxWidth = 'none';\n dropdown.style.maxHeight = 'none';\n const naturalRect = dropdown.getBoundingClientRect();\n\n // Calculate available space in each direction\n const spaceBelow = viewportHeight - anchorRect.bottom - margin - gap;\n const spaceAbove = anchorRect.top - margin - gap;\n const spaceRight = viewportWidth - anchorRect.left - margin;\n const spaceLeft = anchorRect.right - margin;\n\n // Vertical positioning: prefer below, flip above if needed\n let top: number;\n let maxHeight: number;\n\n if (spaceBelow >= naturalRect.height || spaceBelow >= spaceAbove) {\n // Position below anchor\n top = anchorRect.bottom + gap;\n maxHeight = Math.max(100, spaceBelow);\n } else {\n // Position above anchor\n maxHeight = Math.max(100, spaceAbove);\n top = anchorRect.top - gap - Math.min(naturalRect.height, maxHeight);\n }\n\n // Horizontal positioning: prefer left-aligned, shift if needed\n let left = anchorRect.left;\n let maxWidth: number;\n\n if (naturalRect.width <= spaceRight) {\n // Fits when left-aligned\n maxWidth = spaceRight;\n } else if (naturalRect.width <= spaceLeft) {\n // Right-align to anchor\n left = anchorRect.right - naturalRect.width;\n maxWidth = spaceLeft;\n } else {\n // Constrain to available viewport width\n left = margin;\n maxWidth = viewportWidth - margin * 2;\n }\n\n // Ensure left position is within bounds\n left = Math.max(margin, Math.min(left, viewportWidth - margin - naturalRect.width));\n\n // Apply final position and constraints\n dropdown.style.top = `${Math.max(margin, top)}px`;\n dropdown.style.left = `${Math.max(margin, left)}px`;\n dropdown.style.maxWidth = `${maxWidth}px`;\n dropdown.style.maxHeight = `${maxHeight}px`;\n }\n\n /**\n * Setup global hotkeys for the info panel\n */\n private setupHotkeys(): void {\n this.hotkeyHandler = (e: KeyboardEvent) => {\n // Don't trigger if user is typing in an input field\n const activeElement = document.activeElement;\n if (activeElement && (\n activeElement.tagName === 'INPUT' ||\n activeElement.tagName === 'TEXTAREA' ||\n (activeElement as HTMLElement).isContentEditable\n )) {\n return;\n }\n\n // Check for * key (Shift+8 on US keyboard or NumPad *) - Focus Node\n if (e.key === '*') {\n e.preventDefault();\n e.stopPropagation();\n this.focusCurrentNode();\n return;\n }\n\n // Left arrow - Previous node in execution order\n if (e.key === 'ArrowLeft') {\n // Only if panel is visible\n if (!this.elements.panel || this.elements.panel.style.display === 'none') {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n this.navigateExecOrder(-1);\n return;\n }\n\n // Right arrow - Next node in execution order\n if (e.key === 'ArrowRight') {\n // Only if panel is visible\n if (!this.elements.panel || this.elements.panel.style.display === 'none') {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n this.navigateExecOrder(1);\n return;\n }\n };\n\n // Use capture phase to get the event before other handlers\n window.addEventListener('keydown', this.hotkeyHandler, true);\n }\n\n /**\n * Remove hotkey listeners\n */\n private cleanupHotkeys(): void {\n if (this.hotkeyHandler) {\n window.removeEventListener('keydown', this.hotkeyHandler, true);\n this.hotkeyHandler = null;\n }\n }\n\n /**\n * Focus/center the canvas on the currently displayed node\n */\n focusCurrentNode(): void {\n // Get current node from state or content\n const nodeRow = this.elements.content?.querySelector('[data-node-id]') as HTMLElement;\n if (!nodeRow) return;\n\n const nodeId = nodeRow.dataset.nodeId;\n if (!nodeId) return;\n\n const app = (window as any).app;\n if (!app?.graph) return;\n\n const node = app.graph.getNodeById(parseInt(nodeId));\n if (node && app.canvas) {\n app.canvas.centerOnNode(node);\n }\n }\n\n /**\n * Navigate to previous/next node by execution order\n * @param direction -1 for previous, +1 for next\n */\n navigateExecOrder(direction: number): void {\n // Get current node ID from content\n const nodeRow = this.elements.content?.querySelector('[data-node-id]') as HTMLElement;\n if (!nodeRow) return;\n\n const currentNodeId = parseInt(nodeRow.dataset.nodeId || '0');\n if (!currentNodeId && currentNodeId !== 0) return;\n\n // Get nodes sorted by execution order\n const nodes = this.nodeSelector.getNodesSortedByExecOrder();\n if (nodes.length === 0) return;\n\n // Find current node's position in the list\n const currentIndex = nodes.findIndex(n => n.id === currentNodeId);\n if (currentIndex === -1) {\n // Current node not in exec order list, select first node\n this.selectNodeById(nodes[0].id);\n return;\n }\n\n // Calculate new index with wrapping\n let newIndex = currentIndex + direction;\n if (newIndex < 0) newIndex = nodes.length - 1;\n if (newIndex >= nodes.length) newIndex = 0;\n\n // Select the new node\n this.selectNodeById(nodes[newIndex].id);\n }\n\n /**\n * Select a node by ID and update the inspector panel\n */\n private selectNodeById(nodeId: number): void {\n // Set selected node in state\n this.stateManager.setSelectedNode(nodeId);\n\n // Notify callback if set\n if (this.onNodeSelected) {\n this.onNodeSelected(nodeId);\n }\n }\n\n cleanup(): void {\n // Clean up active editors to prevent memory leaks\n this.cleanupEditors();\n\n // Clean up hotkey listeners\n this.cleanupHotkeys();\n\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/info-panel/ValueFormatter.js b/web/info-panel/ValueFormatter.js index 3271770..addda5b 100644 --- a/web/info-panel/ValueFormatter.js +++ b/web/info-panel/ValueFormatter.js @@ -48,4 +48,3 @@ export { getValueAttributes, getValueClass }; -//# sourceMappingURL=ValueFormatter.js.map diff --git a/web/info-panel/ValueFormatter.js.map b/web/info-panel/ValueFormatter.js.map deleted file mode 100644 index a20f407..0000000 --- a/web/info-panel/ValueFormatter.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ValueFormatter.js","sources":["../../src/info-panel/ValueFormatter.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Value Formatter Utilities\n * \n * Pure utility functions for formatting values in the info panel display.\n * Extracted from UIManager.ts for better modularity.\n */\n\nimport { escapeHtml } from '../shared/utils';\n\n/**\n * Format a value for display in the info panel.\n * @param value - The value to format\n * @param label - Optional label to determine formatting rules\n * @returns Formatted string representation\n */\nexport function formatValue(value: unknown, label?: string): string {\n if (value === null || value === undefined) return '';\n\n const str = String(value);\n\n // Show full text for prompts, text content, model names, and file paths\n if (label && (\n label.toLowerCase().includes('text') ||\n label.toLowerCase().includes('prompt') ||\n label.toLowerCase().includes('model') ||\n label.toLowerCase().includes('file') ||\n label.toLowerCase().includes('conditioning') ||\n label.toLowerCase().includes('positive') ||\n label.toLowerCase().includes('negative')\n )) {\n return escapeHtml(str);\n }\n\n // Show full text for very long values (no truncation)\n return escapeHtml(str);\n}\n\n/**\n * Get CSS class names for styling a value.\n * @param value - The value to get classes for\n * @returns CSS class string\n */\nexport function getValueClass(value: unknown): string {\n if (!value) return '';\n\n const str = String(value);\n const classes: string[] = [];\n\n // Mark text that might benefit from special styling for readability\n if (str.length > 100) {\n classes.push('long-text');\n }\n\n return classes.join(' ');\n}\n\n/**\n * Get HTML attributes for a value element.\n * @param value - The value to get attributes for\n * @returns HTML attribute string\n */\nexport function getValueAttributes(value: unknown): string {\n // Since we show full text now, we don't need title attributes for long text\n // Only add title for very long text that might benefit from tooltips\n if (!value) return '';\n\n const str = String(value);\n if (str.length > 500) { // Only for extremely long text\n return `title=\"${escapeHtml(str)}\"`;\n }\n\n return '';\n}\n\n/**\n * Format widget value for display.\n * @param value - Widget value to format\n * @returns Formatted string representation\n */\nexport function formatWidgetValue(value: unknown): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (typeof value === 'string') {\n return value; // Show full text without truncation\n }\n if (typeof value === 'number') {\n return Number.isInteger(value) ? value.toString() : value.toFixed(3);\n }\n if (typeof value === 'boolean') return value.toString();\n if (Array.isArray(value)) {\n return `Array(${value.length})`;\n }\n if (typeof value === 'object') {\n return 'Object';\n }\n return String(value);\n}\n"],"names":[],"mappings":";AAeO,SAAS,YAAY,OAAgB,OAAwB;AAChE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAElD,QAAM,MAAM,OAAO,KAAK;AAGxB,MAAI,UACA,MAAM,YAAA,EAAc,SAAS,MAAM,KACnC,MAAM,YAAA,EAAc,SAAS,QAAQ,KACrC,MAAM,YAAA,EAAc,SAAS,OAAO,KACpC,MAAM,YAAA,EAAc,SAAS,MAAM,KACnC,MAAM,cAAc,SAAS,cAAc,KAC3C,MAAM,cAAc,SAAS,UAAU,KACvC,MAAM,cAAc,SAAS,UAAU,IACxC;AACC,WAAO,WAAW,GAAG;AAAA,EACzB;AAGA,SAAO,WAAW,GAAG;AACzB;AAOO,SAAS,cAAc,OAAwB;AAClD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,OAAO,KAAK;AACxB,QAAM,UAAoB,CAAA;AAG1B,MAAI,IAAI,SAAS,KAAK;AAClB,YAAQ,KAAK,WAAW;AAAA,EAC5B;AAEA,SAAO,QAAQ,KAAK,GAAG;AAC3B;AAOO,SAAS,mBAAmB,OAAwB;AAGvD,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,MAAM,OAAO,KAAK;AACxB,MAAI,IAAI,SAAS,KAAK;AAClB,WAAO,UAAU,WAAW,GAAG,CAAC;AAAA,EACpC;AAEA,SAAO;AACX;AAOO,SAAS,kBAAkB,OAAwB;AACtD,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,OAAO,UAAU,UAAU;AAC3B,WAAO;AAAA,EACX;AACA,MAAI,OAAO,UAAU,UAAU;AAC3B,WAAO,OAAO,UAAU,KAAK,IAAI,MAAM,aAAa,MAAM,QAAQ,CAAC;AAAA,EACvE;AACA,MAAI,OAAO,UAAU,UAAW,QAAO,MAAM,SAAA;AAC7C,MAAI,MAAM,QAAQ,KAAK,GAAG;AACtB,WAAO,SAAS,MAAM,MAAM;AAAA,EAChC;AACA,MAAI,OAAO,UAAU,UAAU;AAC3B,WAAO;AAAA,EACX;AACA,SAAO,OAAO,KAAK;AACvB;"} \ No newline at end of file diff --git a/web/info-panel/widget-editors/DragValueController.js b/web/info-panel/widget-editors/DragValueController.js index ec6a9eb..68944f9 100644 --- a/web/info-panel/widget-editors/DragValueController.js +++ b/web/info-panel/widget-editors/DragValueController.js @@ -220,4 +220,3 @@ class DragValueController { export { DragValueController }; -//# sourceMappingURL=DragValueController.js.map diff --git a/web/info-panel/widget-editors/DragValueController.js.map b/web/info-panel/widget-editors/DragValueController.js.map deleted file mode 100644 index f03d1d0..0000000 --- a/web/info-panel/widget-editors/DragValueController.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"DragValueController.js","sources":["../../../src/info-panel/widget-editors/DragValueController.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Drag Value Controller\n * \n * Handles horizontal drag gestures on inspector rows for quick value changes.\n * Allows users to drag left/right to adjust numbers or cycle through options.\n */\n\nimport { WidgetSyncManager, WidgetConstraints } from './WidgetSyncManager';\nimport { Logger } from '../../shared/logger';\n\n/**\n * Configuration for drag behavior\n */\nexport interface DragConfig {\n nodeId: number;\n widgetName: string;\n widgetType: string;\n currentValue: unknown;\n constraints?: WidgetConstraints;\n onChange?: (value: unknown) => void;\n}\n\n/**\n * Default sensitivity settings\n */\nconst DRAG_SENSITIVITY = {\n // Pixels to drag for one step change (applies to all numeric types)\n pixelsPerStep: 10,\n // Minimum pixels to trigger option cycle for combos (increased to prevent accidental cycling)\n comboThreshold: 60,\n};\n\n/**\n * Drag Value Controller\n * Manages drag interactions for inline value editing\n */\nexport class DragValueController {\n private element: HTMLElement;\n private config: DragConfig;\n private isDragging: boolean = false;\n private startX: number = 0;\n private startValue: number = 0;\n private startOptionIndex: number = 0;\n private accumulatedDelta: number = 0;\n\n // Bound event handlers for cleanup\n private boundPointerMove: (e: PointerEvent) => void;\n private boundPointerUp: (e: PointerEvent) => void;\n private boundPointerCancel: (e: PointerEvent) => void;\n private boundLostCapture: (e: PointerEvent) => void;\n\n constructor(element: HTMLElement, config: DragConfig) {\n this.element = element;\n this.config = config;\n\n this.boundPointerMove = this.onPointerMove.bind(this);\n this.boundPointerUp = this.onPointerUp.bind(this);\n this.boundPointerCancel = this.onPointerCancel.bind(this);\n this.boundLostCapture = this.onLostCapture.bind(this);\n\n this.init();\n }\n\n /**\n * Initialize drag handling\n */\n private init(): void {\n this.element.classList.add('draggable');\n\n this.element.addEventListener('pointerdown', this.onPointerDown.bind(this));\n\n // Add visual indicator\n // this.addDragIndicator();\n }\n\n /**\n * Add visual drag indicator to the row\n */\n private addDragIndicator(): void {\n // Check if already has indicator\n if (this.element.querySelector('.drag-indicator')) return;\n\n const indicator = document.createElement('span');\n indicator.className = 'drag-indicator';\n indicator.innerHTML = '⟷'; // Left-right arrow\n indicator.setAttribute('aria-hidden', 'true');\n this.element.appendChild(indicator);\n }\n\n /**\n * Handle pointer down - start drag tracking\n */\n private onPointerDown(e: PointerEvent): void {\n // Only handle left mouse button or touch\n if (e.button !== 0) return;\n\n // Don't start drag if clicking on interactive elements or value/editor areas\n // Only the label area should initiate drag - all controls should be clickable\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' ||\n target.tagName === 'SELECT' ||\n target.tagName === 'BUTTON' ||\n target.closest('.inline-control') ||\n target.closest('.inline-control-container') ||\n target.closest('.info-value') ||\n target.closest('.widget-editor') ||\n target.closest('.widget-editor-container') ||\n target.closest('.widget-editor-stepper') ||\n target.closest('.widget-editor-number') ||\n target.closest('.widget-editor-input')) {\n return;\n }\n\n this.isDragging = true;\n this.startX = e.clientX;\n this.accumulatedDelta = 0;\n\n // Capture current value\n const type = this.config.widgetType.toLowerCase();\n if (type === 'number' || type === 'int' || type === 'float' || type === 'slider') {\n this.startValue = Number(this.config.currentValue) || 0;\n } else if (type === 'combo') {\n const options = this.config.constraints?.options ?? [];\n const currentStr = String(this.config.currentValue);\n this.startOptionIndex = options.findIndex(opt => String(opt) === currentStr);\n if (this.startOptionIndex === -1) this.startOptionIndex = 0;\n }\n\n // Add dragging class\n this.element.classList.add('dragging');\n\n // Capture pointer for reliable tracking\n this.element.setPointerCapture(e.pointerId);\n\n // Add document-level listeners with capture phase for reliability\n document.addEventListener('pointermove', this.boundPointerMove, true);\n document.addEventListener('pointerup', this.boundPointerUp, true);\n document.addEventListener('pointercancel', this.boundPointerCancel, true);\n this.element.addEventListener('lostpointercapture', this.boundLostCapture);\n\n e.preventDefault();\n e.stopPropagation();\n Logger.debug(`[DragValue] Started drag on ${this.config.widgetName}`);\n }\n\n /**\n * Handle pointer move - update value based on drag distance\n */\n private onPointerMove(e: PointerEvent): void {\n if (!this.isDragging) return;\n\n // Prevent browser from interpreting as text selection or native drag\n e.preventDefault();\n e.stopPropagation();\n\n const deltaX = e.clientX - this.startX;\n const type = this.config.widgetType.toLowerCase();\n\n if (type === 'number' || type === 'int' || type === 'float' || type === 'slider') {\n this.handleNumberDrag(deltaX);\n } else if (type === 'combo') {\n this.handleComboDrag(deltaX);\n }\n }\n\n /**\n * Handle number value drag\n * Uses discrete stepping: every N pixels = exactly 1 step change\n */\n private handleNumberDrag(deltaX: number): void {\n const constraints = this.config.constraints;\n const isInt = this.config.widgetType.toLowerCase() === 'int';\n\n // Determine the step size (1 for integers, 0.1 or widget step for floats)\n const step = constraints?.step ?? (isInt ? 1 : 0.1);\n\n // Calculate discrete number of steps (truncate, don't round)\n const steps = Math.trunc(deltaX / DRAG_SENSITIVITY.pixelsPerStep);\n\n // Calculate new value\n let newValue = this.startValue + (steps * step);\n\n // Apply constraints\n if (constraints?.min !== undefined) {\n newValue = Math.max(constraints.min, newValue);\n }\n if (constraints?.max !== undefined) {\n newValue = Math.min(constraints.max, newValue);\n }\n\n // Round for display precision\n if (isInt) {\n newValue = Math.round(newValue);\n } else {\n const precision = constraints?.precision ?? 2;\n newValue = Number(newValue.toFixed(precision));\n }\n\n // Sync the value\n WidgetSyncManager.syncWidgetValue(this.config.nodeId, this.config.widgetName, newValue);\n this.config.currentValue = newValue;\n this.config.onChange?.(newValue);\n }\n\n /**\n * Handle combo value drag - cycle through options\n */\n private handleComboDrag(deltaX: number): void {\n const options = this.config.constraints?.options ?? [];\n if (options.length === 0) return;\n\n this.accumulatedDelta = deltaX;\n\n // Calculate how many options to move\n const optionDelta = Math.floor(Math.abs(this.accumulatedDelta) / DRAG_SENSITIVITY.comboThreshold);\n\n if (optionDelta > 0) {\n const direction = deltaX > 0 ? 1 : -1;\n let newIndex = this.startOptionIndex + (optionDelta * direction);\n\n // Wrap around\n newIndex = ((newIndex % options.length) + options.length) % options.length;\n\n const newValue = options[newIndex];\n WidgetSyncManager.syncWidgetValue(this.config.nodeId, this.config.widgetName, newValue);\n this.config.currentValue = newValue;\n this.config.onChange?.(newValue);\n }\n }\n\n /**\n * Handle pointer up - end drag\n */\n private onPointerUp(e: PointerEvent): void {\n if (!this.isDragging) return;\n this.endDrag(e.pointerId);\n Logger.debug(`[DragValue] Ended drag on ${this.config.widgetName}`);\n }\n\n /**\n * Handle pointer cancel - browser cancelled the pointer (e.g., touch scroll)\n */\n private onPointerCancel(e: PointerEvent): void {\n if (!this.isDragging) return;\n this.endDrag(e.pointerId);\n Logger.debug(`[DragValue] Drag cancelled on ${this.config.widgetName}`);\n }\n\n /**\n * Handle lost pointer capture - capture was taken by another element\n */\n private onLostCapture(_e: PointerEvent): void {\n if (!this.isDragging) return;\n this.endDrag();\n Logger.debug(`[DragValue] Lost capture on ${this.config.widgetName}`);\n }\n\n /**\n * Common cleanup for ending drag\n */\n private endDrag(pointerId?: number): void {\n this.isDragging = false;\n this.element.classList.remove('dragging');\n\n // Release pointer capture if we have a pointer ID\n if (pointerId !== undefined) {\n try {\n this.element.releasePointerCapture(pointerId);\n } catch {\n // Ignore if already released\n }\n }\n\n // Remove all listeners\n document.removeEventListener('pointermove', this.boundPointerMove, true);\n document.removeEventListener('pointerup', this.boundPointerUp, true);\n document.removeEventListener('pointercancel', this.boundPointerCancel, true);\n this.element.removeEventListener('lostpointercapture', this.boundLostCapture);\n }\n\n /**\n * Update the current value (call when value changes externally)\n */\n updateValue(value: unknown): void {\n this.config.currentValue = value;\n }\n\n /**\n * Cleanup and remove drag handling\n */\n destroy(): void {\n this.element.classList.remove('draggable', 'dragging');\n\n // Remove indicator\n const indicator = this.element.querySelector('.drag-indicator');\n if (indicator) {\n indicator.remove();\n }\n\n // Remove document listeners if still attached (with capture flag)\n document.removeEventListener('pointermove', this.boundPointerMove, true);\n document.removeEventListener('pointerup', this.boundPointerUp, true);\n document.removeEventListener('pointercancel', this.boundPointerCancel, true);\n this.element.removeEventListener('lostpointercapture', this.boundLostCapture);\n }\n\n /**\n * Check if a widget type supports drag editing\n */\n static isTypeSupported(widgetType: string): boolean {\n const type = widgetType.toLowerCase();\n return [\n 'number', 'int', 'float', 'slider', 'combo'\n ].includes(type);\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/widget-editors/InlineControlFactory.js b/web/info-panel/widget-editors/InlineControlFactory.js index 09e4ca1..40b9b63 100644 --- a/web/info-panel/widget-editors/InlineControlFactory.js +++ b/web/info-panel/widget-editors/InlineControlFactory.js @@ -235,4 +235,3 @@ class InlineControlFactory { export { InlineControlFactory }; -//# sourceMappingURL=InlineControlFactory.js.map diff --git a/web/info-panel/widget-editors/InlineControlFactory.js.map b/web/info-panel/widget-editors/InlineControlFactory.js.map deleted file mode 100644 index 6c1085c..0000000 --- a/web/info-panel/widget-editors/InlineControlFactory.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"InlineControlFactory.js","sources":["../../../src/info-panel/widget-editors/InlineControlFactory.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Inline Control Factory\n * \n * Creates compact inline controls that render directly in inspector rows\n * for immediate value editing without entering edit mode.\n */\n\nimport { WidgetSyncManager, WidgetConstraints } from './WidgetSyncManager';\nimport { Logger } from '../../shared/logger';\nimport { CustomDropdown, createDropdownTrigger, updateDropdownTriggerValue } from '../../shared/CustomDropdown';\n\n/**\n * Configuration for creating an inline control\n */\nexport interface InlineControlConfig {\n nodeId: number;\n widgetName: string;\n widgetType: string;\n currentValue: unknown;\n constraints?: WidgetConstraints;\n onChange?: (value: unknown) => void;\n}\n\n/**\n * Interface for inline control instances\n */\nexport interface InlineControlInstance {\n element: HTMLElement;\n getValue(): unknown;\n setValue(value: unknown): void;\n destroy(): void;\n}\n\n/**\n * Inline Control Factory\n * Creates compact inline controls for immediate value editing\n */\nexport class InlineControlFactory {\n /**\n * Check if a widget type should use inline controls\n */\n static shouldUseInlineControl(widgetType: string): boolean {\n const type = widgetType.toLowerCase();\n return type === 'toggle' || type === 'boolean' || type === 'combo' || type === 'button';\n }\n\n /**\n * Create an inline control for the given configuration\n */\n static createControl(config: InlineControlConfig): InlineControlInstance | null {\n const type = config.widgetType.toLowerCase();\n\n switch (type) {\n case 'toggle':\n case 'boolean':\n return this.createInlineToggle(config);\n case 'combo':\n return this.createInlineDropdown(config);\n case 'button':\n return this.createInlineButton(config);\n default:\n return null;\n }\n }\n\n /**\n * Create compact inline toggle for boolean values\n */\n private static createInlineToggle(config: InlineControlConfig): InlineControlInstance {\n const container = document.createElement('div');\n container.className = 'inline-control inline-toggle';\n\n const label = document.createElement('label');\n label.className = 'inline-toggle-switch';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'inline-toggle-input';\n // Handle boolean coercion properly\n const boolValue = config.currentValue === true || config.currentValue === 'true';\n checkbox.checked = boolValue;\n\n const slider = document.createElement('span');\n slider.className = 'inline-toggle-slider';\n\n // Stop propagation to prevent row click handlers\n container.addEventListener('click', (e) => e.stopPropagation());\n\n // Sync on change\n checkbox.addEventListener('change', () => {\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, checkbox.checked);\n config.onChange?.(checkbox.checked);\n Logger.debug(`[InlineControl] Toggle ${config.widgetName}: ${checkbox.checked}`);\n });\n\n label.appendChild(checkbox);\n label.appendChild(slider);\n container.appendChild(label);\n\n return {\n element: container,\n getValue: () => checkbox.checked,\n setValue: (v) => { checkbox.checked = Boolean(v); },\n destroy: () => { container.remove(); }\n };\n }\n\n /**\n * Create compact inline dropdown for combo values\n * Uses custom dropdown for viewport-aware positioning\n */\n private static createInlineDropdown(config: InlineControlConfig): InlineControlInstance {\n const container = document.createElement('div');\n container.className = 'inline-control inline-dropdown';\n\n const options = config.constraints?.options ?? [];\n let currentValue = String(config.currentValue);\n\n // Create trigger element that looks like a select\n const trigger = createDropdownTrigger(currentValue, 'inline-dropdown-trigger');\n trigger.style.cssText = `\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n background: rgba(160, 212, 104, 0.12);\n border: 1px solid transparent;\n border-radius: 6px;\n color: var(--info-panel-accent-color, #74b9ff);\n cursor: pointer;\n min-width: 100px;\n max-width: 180px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n transition: all 0.2s ease;\n `;\n\n let activeDropdown: CustomDropdown | null = null;\n\n // Stop propagation to prevent parent handlers from interfering\n const stopProp = (e: Event) => e.stopPropagation();\n container.addEventListener('click', stopProp);\n container.addEventListener('mousedown', stopProp);\n container.addEventListener('mouseup', stopProp);\n\n // Handle trigger click to show dropdown\n trigger.addEventListener('click', (e) => {\n e.stopPropagation();\n\n if (activeDropdown) {\n activeDropdown.hide();\n activeDropdown = null;\n return;\n }\n\n activeDropdown = new CustomDropdown({\n options: options.map(String),\n currentValue,\n anchor: trigger,\n onChange: (value) => {\n currentValue = value;\n updateDropdownTriggerValue(trigger, value);\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, value);\n config.onChange?.(value);\n Logger.debug(`[InlineControl] Dropdown ${config.widgetName}: ${value}`);\n },\n onClose: () => {\n activeDropdown = null;\n }\n });\n activeDropdown.show();\n });\n\n // Hover effect\n trigger.addEventListener('mouseenter', () => {\n trigger.style.background = 'rgba(160, 212, 104, 0.18)';\n trigger.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.15)';\n });\n trigger.addEventListener('mouseleave', () => {\n trigger.style.background = 'rgba(160, 212, 104, 0.12)';\n trigger.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.1)';\n });\n\n // Keyboard support\n trigger.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n trigger.click();\n }\n });\n\n container.appendChild(trigger);\n\n return {\n element: container,\n getValue: () => currentValue,\n setValue: (v) => {\n currentValue = String(v);\n updateDropdownTriggerValue(trigger, currentValue);\n },\n destroy: () => {\n activeDropdown?.hide();\n container.remove();\n }\n };\n }\n\n /**\n * Create inline button for action widgets\n * Invokes the widget's callback when clicked\n */\n private static createInlineButton(config: InlineControlConfig): InlineControlInstance {\n const container = document.createElement('div');\n container.className = 'inline-control inline-button';\n\n const button = document.createElement('button');\n button.className = 'inline-action-button';\n\n // Determine button label - use meaningful text, not raw values like null/true\n let buttonLabel = 'Click';\n const val = config.currentValue;\n // Only use value if it's a meaningful string (not null, undefined, or boolean artifacts)\n if (typeof val === 'string' && val.length > 0 && val !== 'null' && val !== 'true' && val !== 'false') {\n buttonLabel = val;\n }\n button.textContent = buttonLabel;\n button.type = 'button';\n\n // Style the button to match theme\n button.style.cssText = `\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 4px 12px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 13px;\n background: var(--info-panel-accent-color, #a0d468);\n color: var(--comfy-menu-bg, #1a1a1a);\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-weight: 600;\n transition: all 0.2s ease;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);\n `;\n\n // Stop propagation on container to prevent parent handlers from interfering\n container.addEventListener('click', (e) => e.stopPropagation());\n container.addEventListener('mousedown', (e) => e.stopPropagation());\n container.addEventListener('mouseup', (e) => e.stopPropagation());\n\n // Click handler - invoke widget callback\n button.addEventListener('click', (e) => {\n e.stopPropagation();\n e.preventDefault();\n\n // Visual feedback\n button.style.transform = 'scale(0.95)';\n setTimeout(() => { button.style.transform = ''; }, 100);\n\n // Access the widget callback through the node\n const app = (window as any).app;\n if (!app?.graph) {\n Logger.warn(`[InlineControl] No app.graph available`);\n return;\n }\n\n const node = app.graph.getNodeById(config.nodeId);\n if (!node?.widgets) {\n Logger.warn(`[InlineControl] Node ${config.nodeId} not found or has no widgets`);\n return;\n }\n\n const widget = node.widgets.find((w: any) => w.name === config.widgetName);\n if (widget && typeof widget.callback === 'function') {\n try {\n widget.callback(widget.value, app.canvas, node, [0, 0], null);\n Logger.debug(`[InlineControl] Button ${config.widgetName} clicked successfully`);\n } catch (error) {\n Logger.warn(`[InlineControl] Button callback failed:`, error);\n }\n } else {\n Logger.warn(`[InlineControl] Widget ${config.widgetName} not found or has no callback`);\n }\n });\n\n // Hover effects\n button.addEventListener('mouseenter', () => {\n button.style.transform = 'translateY(-1px)';\n button.style.boxShadow = '0 3px 8px rgba(0, 0, 0, 0.25)';\n });\n button.addEventListener('mouseleave', () => {\n button.style.transform = '';\n button.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.2)';\n });\n\n container.appendChild(button);\n\n return {\n element: container,\n getValue: () => config.currentValue,\n setValue: () => { /* Buttons don't have editable values */ },\n destroy: () => { container.remove(); }\n };\n }\n}\n"],"names":["_a"],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/widget-editors/WidgetEditorFactory.js b/web/info-panel/widget-editors/WidgetEditorFactory.js index 2438901..e801df7 100644 --- a/web/info-panel/widget-editors/WidgetEditorFactory.js +++ b/web/info-panel/widget-editors/WidgetEditorFactory.js @@ -407,4 +407,3 @@ class WidgetEditorFactory { export { WidgetEditorFactory }; -//# sourceMappingURL=WidgetEditorFactory.js.map diff --git a/web/info-panel/widget-editors/WidgetEditorFactory.js.map b/web/info-panel/widget-editors/WidgetEditorFactory.js.map deleted file mode 100644 index 343761d..0000000 --- a/web/info-panel/widget-editors/WidgetEditorFactory.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"WidgetEditorFactory.js","sources":["../../../src/info-panel/widget-editors/WidgetEditorFactory.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Widget Editor Factory\n * \n * Factory class that creates the appropriate editor component for each widget type.\n * Acts as the single entry point for creating editable widget UIs in the inspector.\n */\n\nimport { WidgetSyncManager, WidgetConstraints } from './WidgetSyncManager';\nimport { Logger } from '../../shared/logger';\nimport { CustomDropdown, createDropdownTrigger, updateDropdownTriggerValue } from '../../shared/CustomDropdown';\n\n/**\n * Configuration for creating a widget editor\n */\nexport interface WidgetEditorConfig {\n nodeId: number;\n widgetName: string;\n widgetType: string;\n currentValue: unknown;\n constraints?: WidgetConstraints;\n onChange?: (value: unknown) => void;\n onFocus?: () => void;\n onBlur?: () => void;\n}\n\n/**\n * Base interface for all editor instances\n */\nexport interface WidgetEditorInstance {\n element: HTMLElement;\n getValue(): unknown;\n setValue(value: unknown): void;\n focus(): void;\n destroy(): void;\n}\n\n/**\n * Widget Editor Factory\n * Creates appropriate editor components based on widget type\n */\nexport class WidgetEditorFactory {\n /**\n * Create an editor element for the given configuration\n */\n static createEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const type = config.widgetType.toLowerCase();\n\n switch (type) {\n case 'number':\n case 'int':\n case 'float':\n return this.createNumberEditor(config);\n\n case 'combo':\n return this.createComboEditor(config);\n\n case 'toggle':\n case 'boolean':\n return this.createBooleanEditor(config);\n\n case 'slider':\n return this.createSliderEditor(config);\n\n case 'text':\n case 'string':\n case 'customtext':\n default:\n return this.createTextEditor(config);\n }\n }\n\n /**\n * Create a number input editor\n */\n private static createNumberEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const container = document.createElement('div');\n container.className = 'widget-editor widget-editor-number';\n\n const input = document.createElement('input');\n input.type = 'number';\n input.className = 'widget-editor-input';\n input.value = String(config.currentValue ?? 0);\n\n // Apply constraints\n if (config.constraints) {\n if (typeof config.constraints.min === 'number') {\n input.min = String(config.constraints.min);\n }\n if (typeof config.constraints.max === 'number') {\n input.max = String(config.constraints.max);\n }\n if (typeof config.constraints.step === 'number') {\n input.step = String(config.constraints.step);\n }\n }\n\n // Create stepper buttons\n const decrementBtn = document.createElement('button');\n decrementBtn.className = 'widget-editor-stepper decrement';\n decrementBtn.innerHTML = '−';\n decrementBtn.type = 'button';\n\n const incrementBtn = document.createElement('button');\n incrementBtn.className = 'widget-editor-stepper increment';\n incrementBtn.innerHTML = '+';\n incrementBtn.type = 'button';\n\n // Event handlers\n const syncValue = () => {\n const numValue = parseFloat(input.value);\n if (!isNaN(numValue)) {\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, numValue);\n config.onChange?.(numValue);\n }\n };\n\n input.addEventListener('input', syncValue);\n input.addEventListener('change', syncValue);\n input.addEventListener('focus', () => config.onFocus?.());\n input.addEventListener('blur', () => config.onBlur?.());\n\n decrementBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n // Always use step=1 for fine-grained control via stepper buttons\n const step = 1;\n const currentVal = parseFloat(input.value);\n // Handle NaN (empty input) - fall back to current value or min or 0\n const baseValue = isNaN(currentVal) ? (config.constraints?.min ?? Number(config.currentValue) ?? 0) : currentVal;\n let newValue = baseValue - step;\n // Clamp to constraints\n if (config.constraints?.min !== undefined) {\n newValue = Math.max(config.constraints.min, newValue);\n }\n input.value = String(newValue);\n syncValue();\n });\n\n incrementBtn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n // Always use step=1 for fine-grained control via stepper buttons\n const step = 1;\n const currentVal = parseFloat(input.value);\n // Handle NaN (empty input) - fall back to current value or min or 0\n const baseValue = isNaN(currentVal) ? (config.constraints?.min ?? Number(config.currentValue) ?? 0) : currentVal;\n let newValue = baseValue + step;\n // Clamp to constraints\n if (config.constraints?.max !== undefined) {\n newValue = Math.min(config.constraints.max, newValue);\n }\n input.value = String(newValue);\n syncValue();\n });\n\n // Handle keyboard - Enter to confirm, Escape to cancel\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape' || e.key === 'Enter') {\n e.preventDefault();\n input.blur();\n }\n });\n\n container.appendChild(decrementBtn);\n container.appendChild(input);\n container.appendChild(incrementBtn);\n\n return {\n element: container,\n getValue: () => parseFloat(input.value),\n setValue: (v) => { input.value = String(v); },\n focus: () => { input.focus(); input.select(); },\n destroy: () => { container.remove(); }\n };\n }\n\n /**\n * Create a text input editor\n */\n private static createTextEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const value = String(config.currentValue ?? '');\n const isLongText = value.length > 50 || value.includes('\\n');\n\n const container = document.createElement('div');\n container.className = 'widget-editor widget-editor-text';\n\n let inputElement: HTMLInputElement | HTMLTextAreaElement;\n\n if (isLongText) {\n // Use textarea for long text (prompts)\n const textarea = document.createElement('textarea');\n textarea.className = 'widget-editor-textarea';\n textarea.value = value;\n textarea.rows = Math.min(8, Math.max(3, value.split('\\n').length + 1));\n inputElement = textarea;\n\n // Add character counter\n const counter = document.createElement('span');\n counter.className = 'widget-editor-counter';\n counter.textContent = `${value.length} chars`;\n container.appendChild(counter);\n\n textarea.addEventListener('input', () => {\n counter.textContent = `${textarea.value.length} chars`;\n // Auto-resize\n textarea.rows = Math.min(8, Math.max(3, textarea.value.split('\\n').length + 1));\n });\n } else {\n // Use input for short text\n const input = document.createElement('input');\n input.type = 'text';\n input.className = 'widget-editor-input';\n input.value = value;\n inputElement = input;\n }\n\n // Event handlers\n const syncValue = () => {\n WidgetSyncManager.syncWidgetValueDebounced(\n config.nodeId,\n config.widgetName,\n inputElement.value,\n 100\n );\n config.onChange?.(inputElement.value);\n };\n\n inputElement.addEventListener('input', syncValue);\n inputElement.addEventListener('focus', () => config.onFocus?.());\n inputElement.addEventListener('blur', () => config.onBlur?.());\n\n inputElement.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Escape') {\n inputElement.blur();\n }\n // Allow Enter in textarea, but not in input\n if (e.key === 'Enter' && inputElement instanceof HTMLInputElement) {\n inputElement.blur();\n }\n });\n\n container.insertBefore(inputElement, container.firstChild);\n\n return {\n element: container,\n getValue: () => inputElement.value,\n setValue: (v) => { inputElement.value = String(v); },\n focus: () => { inputElement.focus(); if (inputElement instanceof HTMLInputElement) inputElement.select(); },\n destroy: () => { container.remove(); }\n };\n }\n\n /**\n * Create a combo/dropdown editor\n * Uses custom dropdown for viewport-aware positioning\n */\n private static createComboEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const container = document.createElement('div');\n container.className = 'widget-editor widget-editor-combo';\n\n const options = config.constraints?.options ?? [];\n let currentValue = String(config.currentValue);\n\n // Create trigger element that looks like a select\n const trigger = createDropdownTrigger(currentValue, 'widget-editor-dropdown-trigger');\n trigger.style.cssText = `\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 4px 28px 4px 10px;\n font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;\n font-size: 14px;\n color: var(--info-panel-accent-color, #a0d468);\n background: rgba(160, 212, 104, 0.12);\n border: 1px solid transparent;\n border-radius: 6px;\n cursor: pointer;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n transition: all 0.2s ease;\n `;\n\n let activeDropdown: CustomDropdown | null = null;\n\n // Handle trigger click to show dropdown\n const showDropdown = () => {\n if (activeDropdown) {\n activeDropdown.hide();\n activeDropdown = null;\n return;\n }\n\n config.onFocus?.();\n\n activeDropdown = new CustomDropdown({\n options: options.map(String),\n currentValue,\n anchor: trigger,\n onChange: (value) => {\n currentValue = value;\n updateDropdownTriggerValue(trigger, value);\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, value);\n config.onChange?.(value);\n },\n onClose: () => {\n activeDropdown = null;\n config.onBlur?.();\n }\n });\n activeDropdown.show();\n };\n\n trigger.addEventListener('click', showDropdown);\n\n // Hover effect\n trigger.addEventListener('mouseenter', () => {\n trigger.style.background = 'rgba(160, 212, 104, 0.18)';\n trigger.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.15)';\n });\n trigger.addEventListener('mouseleave', () => {\n trigger.style.background = 'rgba(160, 212, 104, 0.12)';\n trigger.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.1)';\n });\n\n // Keyboard support\n trigger.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n showDropdown();\n } else if (e.key === 'Escape' && activeDropdown) {\n activeDropdown.hide();\n }\n });\n\n container.appendChild(trigger);\n\n return {\n element: container,\n getValue: () => currentValue,\n setValue: (v) => {\n currentValue = String(v);\n updateDropdownTriggerValue(trigger, currentValue);\n },\n focus: () => trigger.focus(),\n destroy: () => {\n activeDropdown?.hide();\n container.remove();\n }\n };\n }\n\n /**\n * Create a boolean toggle editor\n */\n private static createBooleanEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const container = document.createElement('div');\n container.className = 'widget-editor widget-editor-boolean';\n\n const label = document.createElement('label');\n label.className = 'widget-editor-toggle';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'widget-editor-checkbox';\n // Handle boolean coercion properly - string 'false' should be false, not true\n const boolValue = config.currentValue === true || config.currentValue === 'true';\n checkbox.checked = boolValue;\n\n const slider = document.createElement('span');\n slider.className = 'widget-editor-toggle-slider';\n\n // Event handlers\n checkbox.addEventListener('change', () => {\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, checkbox.checked);\n config.onChange?.(checkbox.checked);\n });\n\n checkbox.addEventListener('focus', () => config.onFocus?.());\n checkbox.addEventListener('blur', () => config.onBlur?.());\n\n label.appendChild(checkbox);\n label.appendChild(slider);\n container.appendChild(label);\n\n return {\n element: container,\n getValue: () => checkbox.checked,\n setValue: (v) => { checkbox.checked = Boolean(v); },\n focus: () => checkbox.focus(),\n destroy: () => { container.remove(); }\n };\n }\n\n /**\n * Create a slider editor\n */\n private static createSliderEditor(config: WidgetEditorConfig): WidgetEditorInstance {\n const container = document.createElement('div');\n container.className = 'widget-editor widget-editor-slider';\n\n const slider = document.createElement('input');\n slider.type = 'range';\n slider.className = 'widget-editor-range';\n slider.value = String(config.currentValue ?? 0);\n\n // Apply constraints\n if (config.constraints) {\n slider.min = String(config.constraints.min ?? 0);\n slider.max = String(config.constraints.max ?? 100);\n slider.step = String(config.constraints.step ?? 0.01);\n }\n\n // Value display\n const valueDisplay = document.createElement('span');\n valueDisplay.className = 'widget-editor-slider-value';\n valueDisplay.textContent = String(config.currentValue ?? 0);\n\n // Event handlers\n const syncValue = () => {\n const numValue = parseFloat(slider.value);\n valueDisplay.textContent = numValue.toFixed(config.constraints?.precision ?? 2);\n WidgetSyncManager.syncWidgetValue(config.nodeId, config.widgetName, numValue);\n config.onChange?.(numValue);\n };\n\n slider.addEventListener('input', syncValue);\n slider.addEventListener('focus', () => config.onFocus?.());\n slider.addEventListener('blur', () => config.onBlur?.());\n\n // Handle Escape to close the editor\n slider.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n slider.blur();\n config.onBlur?.();\n }\n });\n\n container.appendChild(slider);\n container.appendChild(valueDisplay);\n\n return {\n element: container,\n getValue: () => parseFloat(slider.value),\n setValue: (v) => {\n slider.value = String(v);\n valueDisplay.textContent = String(v);\n },\n focus: () => slider.focus(),\n destroy: () => { container.remove(); }\n };\n }\n\n /**\n * Check if a widget type is supported for editing\n */\n static isTypeSupported(widgetType: string): boolean {\n const supported = [\n 'number', 'int', 'float',\n 'text', 'string', 'customtext',\n 'combo',\n 'toggle', 'boolean',\n 'slider'\n ];\n return supported.includes(widgetType.toLowerCase());\n }\n}\n"],"names":["_a"],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/widget-editors/WidgetSyncManager.js b/web/info-panel/widget-editors/WidgetSyncManager.js index 9c213ba..fcd3875 100644 --- a/web/info-panel/widget-editors/WidgetSyncManager.js +++ b/web/info-panel/widget-editors/WidgetSyncManager.js @@ -254,4 +254,3 @@ __publicField(WidgetSyncManager, "DEBOUNCE_MS", 50); export { WidgetSyncManager }; -//# sourceMappingURL=WidgetSyncManager.js.map diff --git a/web/info-panel/widget-editors/WidgetSyncManager.js.map b/web/info-panel/widget-editors/WidgetSyncManager.js.map deleted file mode 100644 index 4a0e056..0000000 --- a/web/info-panel/widget-editors/WidgetSyncManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"WidgetSyncManager.js","sources":["../../../src/info-panel/widget-editors/WidgetSyncManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Widget Sync Manager\n * \n * Handles synchronization between inspector edits and canvas node widgets.\n * Provides the bridge between the editable inspector UI and the actual node widgets.\n */\n\nimport { Logger } from '../../shared/logger';\n\ndeclare const app: any;\n\n/**\n * Constraints for widget values\n */\nexport interface WidgetConstraints {\n min?: number;\n max?: number;\n step?: number;\n precision?: number;\n options?: unknown[]; // For combo widgets\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n success: boolean;\n error?: string;\n previousValue?: unknown;\n newValue?: unknown;\n}\n\n/**\n * Widget Sync Manager\n * Handles bidirectional sync between inspector and canvas nodes\n */\nexport class WidgetSyncManager {\n private static debounceTimers: Map = new Map();\n private static readonly DEBOUNCE_MS = 50;\n\n /**\n * Update a widget value on a node\n * @param nodeId - The node ID\n * @param widgetName - The widget name\n * @param value - The new value to set\n * @returns SyncResult indicating success or failure\n */\n static syncWidgetValue(nodeId: number, widgetName: string, value: unknown): SyncResult {\n try {\n if (!app?.graph) {\n return { success: false, error: 'ComfyUI app not available' };\n }\n\n const node = app.graph.getNodeById(nodeId);\n if (!node) {\n return { success: false, error: `Node ${nodeId} not found` };\n }\n\n if (!node.widgets || node.widgets.length === 0) {\n return { success: false, error: `Node ${nodeId} has no widgets` };\n }\n\n const widget = node.widgets.find((w: any) => w.name === widgetName);\n if (!widget) {\n return { success: false, error: `Widget \"${widgetName}\" not found on node ${nodeId}` };\n }\n\n const previousValue = widget.value;\n\n // Apply constraints before setting value\n const constrainedValue = this.applyConstraints(value, widget);\n\n // Update widget value\n widget.value = constrainedValue;\n\n // Trigger widget callback if it exists (some widgets need this for side effects)\n if (typeof widget.callback === 'function') {\n try {\n widget.callback(constrainedValue, app.canvas, node, [0, 0], null);\n } catch (callbackError) {\n Logger.warn(`Widget callback failed for ${widgetName}:`, callbackError);\n // Continue anyway - the value is set\n }\n }\n\n // Mark node as dirty to trigger redraw\n if (typeof node.setDirtyCanvas === 'function') {\n node.setDirtyCanvas(true, true);\n }\n\n // Force canvas redraw\n this.triggerCanvasRedraw();\n\n Logger.debug(`[WidgetSync] Updated ${widgetName} on node ${nodeId}: ${previousValue} → ${constrainedValue}`);\n\n return {\n success: true,\n previousValue,\n newValue: constrainedValue\n };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n Logger.error(`[WidgetSync] Error syncing widget:`, error);\n return { success: false, error: message };\n }\n }\n\n /**\n * Debounced version of syncWidgetValue for text input\n * @param nodeId - The node ID\n * @param widgetName - The widget name \n * @param value - The new value to set\n * @param debounceMs - Debounce delay in milliseconds (default 50ms)\n */\n static syncWidgetValueDebounced(\n nodeId: number,\n widgetName: string,\n value: unknown,\n debounceMs: number = this.DEBOUNCE_MS\n ): void {\n const key = `${nodeId}:${widgetName}`;\n\n // Clear existing timer\n const existingTimer = this.debounceTimers.get(key);\n if (existingTimer) {\n window.clearTimeout(existingTimer);\n }\n\n // Set new timer\n const timer = window.setTimeout(() => {\n this.syncWidgetValue(nodeId, widgetName, value);\n this.debounceTimers.delete(key);\n }, debounceMs);\n\n this.debounceTimers.set(key, timer);\n }\n\n /**\n * Get widget constraints for validation\n * @param nodeId - The node ID\n * @param widgetName - The widget name\n * @returns WidgetConstraints or null if widget not found\n */\n static getWidgetConstraints(nodeId: number, widgetName: string): WidgetConstraints | null {\n try {\n if (!app?.graph) return null;\n\n const node = app.graph.getNodeById(nodeId);\n if (!node?.widgets) return null;\n\n const widget = node.widgets.find((w: any) => w.name === widgetName);\n if (!widget) return null;\n\n return this.extractConstraints(widget);\n } catch (error) {\n Logger.warn(`[WidgetSync] Error getting constraints:`, error);\n return null;\n }\n }\n\n /**\n * Extract constraints from a widget object\n */\n static extractConstraints(widget: any): WidgetConstraints {\n const constraints: WidgetConstraints = {};\n\n // Direct properties\n if (typeof widget.min === 'number') constraints.min = widget.min;\n if (typeof widget.max === 'number') constraints.max = widget.max;\n if (typeof widget.step === 'number') constraints.step = widget.step;\n\n // Options object (ComfyUI sometimes nests these)\n if (widget.options) {\n if (typeof widget.options.min === 'number') constraints.min = widget.options.min;\n if (typeof widget.options.max === 'number') constraints.max = widget.options.max;\n if (typeof widget.options.step === 'number') constraints.step = widget.options.step;\n if (typeof widget.options.precision === 'number') constraints.precision = widget.options.precision;\n if (Array.isArray(widget.options.values)) {\n constraints.options = widget.options.values;\n }\n }\n\n // Combo widget options (array of values)\n if (Array.isArray(widget.options)) {\n constraints.options = widget.options;\n }\n\n return constraints;\n }\n\n /**\n * Apply constraints to a value\n */\n static applyConstraints(value: unknown, widget: any): unknown {\n const constraints = this.extractConstraints(widget);\n const widgetType = this.getWidgetType(widget);\n\n // Handle number types\n if (widgetType === 'number' || widgetType === 'INT' || widgetType === 'FLOAT' || widgetType === 'slider') {\n let numValue = typeof value === 'number' ? value : parseFloat(String(value));\n\n if (isNaN(numValue)) {\n return widget.value; // Return current value if invalid\n }\n\n // Apply min/max\n if (typeof constraints.min === 'number') {\n numValue = Math.max(constraints.min, numValue);\n }\n if (typeof constraints.max === 'number') {\n numValue = Math.min(constraints.max, numValue);\n }\n\n // Note: Skip step rounding to preserve user's exact input value\n // The widget callback or ComfyUI pipeline will handle step constraints if needed\n\n // Apply precision\n if (typeof constraints.precision === 'number') {\n numValue = parseFloat(numValue.toFixed(constraints.precision));\n }\n\n return numValue;\n }\n\n // Handle combo (validate against options)\n if (widgetType === 'combo' && constraints.options) {\n // Use string comparison since HTML select values are always strings\n const valueStr = String(value);\n const hasMatch = constraints.options.some(opt => String(opt) === valueStr);\n if (!hasMatch) {\n Logger.warn(`[WidgetSync] Invalid combo value \"${value}\", keeping current`);\n return widget.value;\n }\n // Return the original option type (may be number)\n const matchedOption = constraints.options.find(opt => String(opt) === valueStr);\n if (matchedOption !== undefined) {\n return matchedOption;\n }\n }\n\n // Handle boolean\n if (widgetType === 'toggle' || widgetType === 'boolean') {\n return Boolean(value);\n }\n\n // Text and other types - return as-is\n return value;\n }\n\n /**\n * Determine widget type from widget object\n */\n static getWidgetType(widget: any): string {\n if (!widget) return 'unknown';\n\n const type = (widget.type || '').toLowerCase();\n\n // Button widgets - explicit type check\n if (type === 'button') return 'button';\n\n // Map ComfyUI widget types to our categories\n if (type === 'number' || type === 'int' || type === 'float') return 'number';\n if (type === 'combo' || type === 'string' && Array.isArray(widget.options)) return 'combo';\n if (type === 'toggle' || type === 'boolean') return 'boolean';\n if (type === 'slider') return 'slider';\n if (type === 'text' || type === 'string' || type === 'customtext') return 'text';\n\n // Check for array options (combo indicator)\n if (Array.isArray(widget.options) && widget.options.length > 0) {\n return 'combo';\n }\n\n // Heuristic: detect button widgets by name patterns and callback presence\n if (typeof widget.callback === 'function' && widget.name) {\n const name = widget.name.toLowerCase();\n // Button-like name patterns\n const buttonPatterns = ['show', 'save', 'open', 'load', 'export', 'import', 'reset', 'clear', 'apply', 'run', 'execute', 'preview', 'refresh', 'update'];\n const hasButtonName = buttonPatterns.some(pattern => name.includes(pattern));\n // Non-meaningful values suggest it's an action button, not an editable field\n const hasNonEditableValue = widget.value === undefined || widget.value === null || typeof widget.value === 'boolean';\n\n if (hasButtonName && hasNonEditableValue) {\n return 'button';\n }\n }\n\n // Fallback: widgets with callback but no value and no options are likely buttons\n if (typeof widget.callback === 'function' && widget.value === undefined && !Array.isArray(widget.options)) {\n return 'button';\n }\n\n // Default to text for unknown types\n return 'text';\n }\n\n /**\n * Check if a widget is editable\n */\n static isWidgetEditable(widget: any): boolean {\n if (!widget) return false;\n\n // Hidden widgets\n if (widget.hidden) return false;\n\n // Widgets marked as readonly\n if (widget.readonly) return false;\n\n // Check type\n const type = this.getWidgetType(widget);\n\n // Button widgets are actionable, not editable\n if (type === 'button') return false;\n\n const editableTypes = ['number', 'text', 'combo', 'boolean', 'slider', 'INT', 'FLOAT'];\n\n return editableTypes.includes(type) || editableTypes.includes(type.toUpperCase());\n }\n\n /**\n * Force canvas redraw after widget update\n */\n static triggerCanvasRedraw(): void {\n try {\n if (app?.canvas) {\n if (typeof app.canvas.setDirty === 'function') {\n app.canvas.setDirty(true, true);\n }\n if (typeof app.canvas.draw === 'function') {\n app.canvas.draw(true, true);\n }\n }\n } catch (error) {\n Logger.warn(`[WidgetSync] Canvas redraw failed:`, error);\n }\n }\n\n /**\n * Get current value of a widget\n */\n static getWidgetValue(nodeId: number, widgetName: string): unknown | null {\n try {\n if (!app?.graph) return null;\n\n const node = app.graph.getNodeById(nodeId);\n if (!node?.widgets) return null;\n\n const widget = node.widgets.find((w: any) => w.name === widgetName);\n return widget?.value ?? null;\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Validate a value against widget constraints without applying\n */\n static validateValue(nodeId: number, widgetName: string, value: unknown): { valid: boolean; error?: string } {\n const constraints = this.getWidgetConstraints(nodeId, widgetName);\n if (!constraints) {\n return { valid: true }; // No constraints = valid\n }\n\n // Number validation\n if (typeof value === 'number') {\n if (typeof constraints.min === 'number' && value < constraints.min) {\n return { valid: false, error: `Value must be at least ${constraints.min}` };\n }\n if (typeof constraints.max === 'number' && value > constraints.max) {\n return { valid: false, error: `Value must be at most ${constraints.max}` };\n }\n }\n\n // Combo validation - use string comparison\n if (constraints.options) {\n const valueStr = String(value);\n const hasMatch = constraints.options.some(opt => String(opt) === valueStr);\n if (!hasMatch) {\n return { valid: false, error: `Invalid option selected` };\n }\n }\n\n return { valid: true };\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/ConfigManager.js b/web/magnify-glass/ConfigManager.js index c656ed6..30ee220 100644 --- a/web/magnify-glass/ConfigManager.js +++ b/web/magnify-glass/ConfigManager.js @@ -253,4 +253,3 @@ class ConfigManager { export { ConfigManager }; -//# sourceMappingURL=ConfigManager.js.map diff --git a/web/magnify-glass/ConfigManager.js.map b/web/magnify-glass/ConfigManager.js.map deleted file mode 100644 index 4f3f670..0000000 --- a/web/magnify-glass/ConfigManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"ConfigManager.js","sources":["../../src/magnify-glass/ConfigManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - ConfigManager (TypeScript)\n * \n * Manages configuration and settings for the magnifying glass.\n */\n\nimport { getSettingValue, clamp } from '../shared/utils';\nimport { STORAGE_KEYS } from '../shared/constants';\nimport type { GlassPosition, GlassShape, TextureFilter } from '../shared/constants';\n\n// Default settings (we'll import these properly later)\nconst DEFAULT_GLASS_SETTINGS = {\n \"🔍MagnifyGlass.ZoomFactor\": 300,\n \"🔍MagnifyGlass.GlassSize\": 300,\n \"🔍MagnifyGlass.BorderColor\": \"#6b7280\",\n \"🔍MagnifyGlass.BorderWidth\": 1,\n \"🔍MagnifyGlass.ActivationKey\": \"x\",\n \"🔍MagnifyGlass.AltRequired\": false,\n \"🔍MagnifyGlass.FollowCursor\": false,\n \"🔍MagnifyGlass.OffsetStep\": 5,\n \"🔍MagnifyGlass.GlassPosition\": \"Top-Right\" as GlassPosition,\n \"🔍MagnifyGlass.ResetKey\": \"o\",\n \"🔍MagnifyGlass.GlassShape\": \"Rounded Square\" as GlassShape,\n \"🔍MagnifyGlass.BorderEnabled\": true,\n \"🔍MagnifyGlass.TextureFiltering\": \"Linear\" as TextureFilter,\n \"🔍MagnifyGlass.AlwaysActiveMode\": true,\n \"🔍MagnifyGlass.ToggleFollowCursorKey\": \"h\",\n \"🔍MagnifyGlass.GlassPreviewToggleHotkey\": \"g\",\n \"🔍MagnifyGlass.ShowCursorPreview\": false,\n \"🔍MagnifyGlass.ForceDirectCaptureKey\": \"d\",\n \"🔍MagnifyGlass.ForceDirectCapture\": false,\n // Accessibility defaults\n \"🔍MagnifyGlass.AccessibilityEnabled\": false,\n \"🔍MagnifyGlass.HighContrastMode\": false,\n \"🔍MagnifyGlass.TextGlowEnabled\": false,\n \"🔍MagnifyGlass.TextGlowColor\": \"#ffff00\",\n \"🔍MagnifyGlass.TextGlowIntensity\": 5,\n \"🔍MagnifyGlass.FontScaleFactor\": 100,\n \"🔍MagnifyGlass.BoldTextEnabled\": false,\n \"🔍MagnifyGlass.TextOutlineEnabled\": false,\n \"🔍MagnifyGlass.TextOutlineColor\": \"#000000\",\n \"🔍MagnifyGlass.NodeTitleEmphasis\": false,\n \"🔍MagnifyGlass.InvertColors\": false,\n \"🔍MagnifyGlass.GrayscaleMode\": false,\n \"🔍MagnifyGlass.ReduceMotion\": false,\n};\n\n/**\n * Configuration manager class.\n * Reads settings from ComfyUI and manages persistent offsets.\n */\nexport class ConfigManager {\n /** Zoom factor (1.0 = 100%, 3.0 = 300%) */\n zoomFactor: number;\n\n /** Glass size in pixels */\n glassSize: number;\n\n /** Border color (CSS color string) */\n borderColor: string;\n\n /** Border width in pixels */\n borderWidth: number;\n\n /** Activation key (lowercase) */\n activationKey: string;\n\n /** Whether Alt key is required for activation */\n altRequired: boolean;\n\n /** Whether glass follows cursor */\n followCursor: boolean;\n\n\n\n /** Offset step for arrow key adjustments */\n offsetStep: number;\n\n /** Glass position relative to cursor */\n glassPosition: string;\n\n /** Reset key (lowercase) */\n resetKey: string;\n\n /** Glass shape */\n glassShape: string;\n\n /** Whether border is enabled */\n borderEnabled: boolean;\n\n /** Texture filtering mode */\n textureFiltering: string;\n\n /** Always active mode */\n alwaysActiveMode: boolean;\n\n /** Toggle follow cursor key */\n toggleFollowCursorKey: string;\n\n /** Glass preview toggle key */\n toggleGlassPreviewKey: string;\n\n /** Show cursor preview in glass */\n showCursorPreview: boolean;\n\n /** Force Direct Capture key */\n forceDirectCaptureKey: string;\n\n /** Runtime flag: Force Direct Capture mode (not persisted) */\n forceDirectCapture: boolean;\n\n // ===== Accessibility Settings =====\n\n /** Master toggle for accessibility mode */\n accessibilityEnabled: boolean;\n\n /** High contrast mode - boost text contrast */\n highContrastMode: boolean;\n\n /** Text glow effect enabled */\n textGlowEnabled: boolean;\n\n /** Color of the text glow effect */\n textGlowColor: string;\n\n /** Blur radius for text glow (1-15px) */\n textGlowIntensity: number;\n\n /** Font size multiplier (100-200%) */\n fontScaleFactor: number;\n\n /** Force bolder font weight */\n boldTextEnabled: boolean;\n\n /** Add contrasting stroke around text */\n textOutlineEnabled: boolean;\n\n /** Color of the text outline */\n textOutlineColor: string;\n\n /** Extra styling for node names */\n nodeTitleEmphasis: boolean;\n\n /** Invert all colors */\n invertColors: boolean;\n\n /** Desaturate all colors */\n grayscaleMode: boolean;\n\n /** Disable smooth transitions */\n reduceMotion: boolean;\n\n /** Manual offset X in graph units */\n offsetX: number;\n\n /** Manual offset Y in graph units */\n offsetY: number;\n\n constructor() {\n // Initialize properties with defaults\n this.zoomFactor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ZoomFactor\"] / 100;\n this.glassSize = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.GlassSize\"];\n this.borderColor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.BorderColor\"];\n this.borderWidth = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.BorderWidth\"];\n this.activationKey = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ActivationKey\"];\n this.altRequired = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.AltRequired\"];\n this.followCursor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.FollowCursor\"];\n\n this.offsetStep = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.OffsetStep\"];\n this.glassPosition = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.GlassPosition\"];\n this.resetKey = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ResetKey\"];\n this.glassShape = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.GlassShape\"];\n this.borderEnabled = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.BorderEnabled\"];\n this.textureFiltering = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextureFiltering\"];\n this.alwaysActiveMode = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.AlwaysActiveMode\"];\n this.toggleFollowCursorKey = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ToggleFollowCursorKey\"];\n this.toggleGlassPreviewKey = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.GlassPreviewToggleHotkey\"];\n this.showCursorPreview = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ShowCursorPreview\"];\n this.forceDirectCaptureKey = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ForceDirectCaptureKey\"];\n this.forceDirectCapture = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ForceDirectCapture\"]; // Initialize from default\n\n // Accessibility settings\n this.accessibilityEnabled = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.AccessibilityEnabled\"];\n this.highContrastMode = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.HighContrastMode\"];\n this.textGlowEnabled = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextGlowEnabled\"];\n this.textGlowColor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextGlowColor\"];\n this.textGlowIntensity = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextGlowIntensity\"];\n this.fontScaleFactor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.FontScaleFactor\"];\n this.boldTextEnabled = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.BoldTextEnabled\"];\n this.textOutlineEnabled = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextOutlineEnabled\"];\n this.textOutlineColor = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.TextOutlineColor\"];\n this.nodeTitleEmphasis = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.NodeTitleEmphasis\"];\n this.invertColors = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.InvertColors\"];\n this.grayscaleMode = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.GrayscaleMode\"];\n this.reduceMotion = DEFAULT_GLASS_SETTINGS[\"🔍MagnifyGlass.ReduceMotion\"];\n\n // Alignment adjustment parameters - managed separately\n this.offsetX = 0;\n this.offsetY = 0;\n }\n\n /**\n * Load settings from ComfyUI settings system.\n */\n loadSettings(): void {\n const rawZoom = getSettingValue(\"🔍MagnifyGlass.ZoomFactor\", this.zoomFactor * 100);\n this.zoomFactor = this.validateNumber(rawZoom, 10, 5000, 300) / 100;\n\n const rawGlassSize = getSettingValue(\"🔍MagnifyGlass.GlassSize\", this.glassSize);\n this.glassSize = this.validateNumber(rawGlassSize, 50, 2000, 300);\n\n const rawBorderColor = getSettingValue(\"🔍MagnifyGlass.BorderColor\", this.borderColor);\n this.borderColor = this.validateColor(rawBorderColor, \"#6b7280\");\n\n const rawBorderWidth = getSettingValue(\"🔍MagnifyGlass.BorderWidth\", this.borderWidth);\n this.borderWidth = this.validateNumber(rawBorderWidth, 0, 50, 1);\n\n this.activationKey = getSettingValue(\"🔍MagnifyGlass.ActivationKey\", this.activationKey);\n this.altRequired = getSettingValue(\"🔍MagnifyGlass.AltRequired\", this.altRequired);\n this.followCursor = getSettingValue(\"🔍MagnifyGlass.FollowCursor\", this.followCursor);\n\n const rawOffsetStep = getSettingValue(\"🔍MagnifyGlass.OffsetStep\", this.offsetStep);\n this.offsetStep = this.validateNumber(rawOffsetStep, 1, 100, 5);\n\n this.glassPosition = getSettingValue(\"🔍MagnifyGlass.GlassPosition\", this.glassPosition);\n this.resetKey = getSettingValue(\"🔍MagnifyGlass.ResetKey\", this.resetKey);\n this.glassShape = getSettingValue(\"🔍MagnifyGlass.GlassShape\", this.glassShape);\n this.borderEnabled = getSettingValue(\"🔍MagnifyGlass.BorderEnabled\", this.borderEnabled);\n this.textureFiltering = getSettingValue(\"🔍MagnifyGlass.TextureFiltering\", this.textureFiltering);\n this.alwaysActiveMode = getSettingValue(\"🔍MagnifyGlass.AlwaysActiveMode\", this.alwaysActiveMode);\n this.toggleFollowCursorKey = getSettingValue(\"🔍MagnifyGlass.ToggleFollowCursorKey\", this.toggleFollowCursorKey);\n this.showCursorPreview = getSettingValue(\"🔍MagnifyGlass.ShowCursorPreview\", this.showCursorPreview);\n this.forceDirectCaptureKey = getSettingValue(\"🔍MagnifyGlass.ForceDirectCaptureKey\", this.forceDirectCaptureKey);\n this.toggleGlassPreviewKey = getSettingValue(\"🔍MagnifyGlass.GlassPreviewToggleHotkey\", this.toggleGlassPreviewKey);\n\n // Accessibility settings\n this.accessibilityEnabled = getSettingValue(\"🔍MagnifyGlass.AccessibilityEnabled\", this.accessibilityEnabled);\n this.highContrastMode = getSettingValue(\"🔍MagnifyGlass.HighContrastMode\", this.highContrastMode);\n this.textGlowEnabled = getSettingValue(\"🔍MagnifyGlass.TextGlowEnabled\", this.textGlowEnabled);\n\n const rawGlowColor = getSettingValue(\"🔍MagnifyGlass.TextGlowColor\", this.textGlowColor);\n this.textGlowColor = this.validateColor(rawGlowColor, \"#ffff00\");\n\n const rawGlowIntensity = getSettingValue(\"🔍MagnifyGlass.TextGlowIntensity\", this.textGlowIntensity);\n this.textGlowIntensity = this.validateNumber(rawGlowIntensity, 1, 50, 5);\n\n const rawFontScale = getSettingValue(\"🔍MagnifyGlass.FontScaleFactor\", this.fontScaleFactor);\n this.fontScaleFactor = this.validateNumber(rawFontScale, 50, 500, 100);\n\n this.boldTextEnabled = getSettingValue(\"🔍MagnifyGlass.BoldTextEnabled\", this.boldTextEnabled);\n this.textOutlineEnabled = getSettingValue(\"🔍MagnifyGlass.TextOutlineEnabled\", this.textOutlineEnabled);\n\n const rawOutlineColor = getSettingValue(\"🔍MagnifyGlass.TextOutlineColor\", this.textOutlineColor);\n this.textOutlineColor = this.validateColor(rawOutlineColor, \"#000000\");\n\n this.nodeTitleEmphasis = getSettingValue(\"🔍MagnifyGlass.NodeTitleEmphasis\", this.nodeTitleEmphasis);\n this.invertColors = getSettingValue(\"🔍MagnifyGlass.InvertColors\", this.invertColors);\n this.grayscaleMode = getSettingValue(\"🔍MagnifyGlass.GrayscaleMode\", this.grayscaleMode);\n this.reduceMotion = getSettingValue(\"🔍MagnifyGlass.ReduceMotion\", this.reduceMotion);\n }\n\n /**\n * Validate numeric input with bounds checking.\n */\n private validateNumber(value: unknown, min: number, max: number, fallback: number): number {\n const num = Number(value);\n if (isNaN(num)) return fallback;\n return clamp(num, min, max);\n }\n\n /**\n * Validate color string (hex).\n */\n private validateColor(color: unknown, fallback: string): string {\n if (!color || typeof color !== 'string') return fallback;\n // Basic hex validation: #RGB, #RRGGBB, #RRGGBBAA\n // We permit 3, 4, 6, or 8 hex digits\n if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color)) {\n return color;\n }\n return fallback;\n }\n\n /**\n * Load saved offsets from localStorage.\n */\n loadSavedOffsets(): void {\n try {\n const savedOffsetX = localStorage.getItem(STORAGE_KEYS.OFFSET_X);\n const savedOffsetY = localStorage.getItem(STORAGE_KEYS.OFFSET_Y);\n\n if (savedOffsetX !== null) {\n this.offsetX = parseInt(savedOffsetX, 10);\n } else {\n this.offsetX = 0;\n }\n\n if (savedOffsetY !== null) {\n this.offsetY = parseInt(savedOffsetY, 10);\n } else {\n this.offsetY = 0;\n }\n } catch (e) {\n console.error(\"ComfyUI Magnifying Glass ERROR: Error loading saved offsets:\", e);\n this.offsetX = 0;\n this.offsetY = 0;\n }\n }\n\n /**\n * Save current offsets to localStorage.\n */\n saveOffsets(): void {\n try {\n localStorage.setItem(STORAGE_KEYS.OFFSET_X, this.offsetX.toString());\n localStorage.setItem(STORAGE_KEYS.OFFSET_Y, this.offsetY.toString());\n } catch (e) {\n console.error(\"ComfyUI Magnifying Glass ERROR: Error saving offsets:\", e);\n }\n }\n\n /**\n * Reset offsets to default (zero).\n */\n resetOffsets(): void {\n this.offsetX = 0;\n this.offsetY = 0;\n this.saveOffsets();\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/DebugManager.js b/web/magnify-glass/DebugManager.js index b4a6cf7..ea47945 100644 --- a/web/magnify-glass/DebugManager.js +++ b/web/magnify-glass/DebugManager.js @@ -25,4 +25,3 @@ class DebugManager { export { DebugManager }; -//# sourceMappingURL=DebugManager.js.map diff --git a/web/magnify-glass/DebugManager.js.map b/web/magnify-glass/DebugManager.js.map deleted file mode 100644 index 9b60c9a..0000000 --- a/web/magnify-glass/DebugManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"DebugManager.js","sources":["../../src/magnify-glass/DebugManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - DebugManager (TypeScript)\n * \n * Handles debug visualization and logging.\n */\n\nimport type { ComfyApp } from '../types/comfyui';\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\nimport type { UiManager } from './UiManager';\n\ndeclare const app: ComfyApp;\n\n/**\n * Debug Manager class.\n * Provides debug logging and canvas visualization.\n */\nexport class DebugManager {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n\n constructor(config: ConfigManager, state: MagnifierState, ui: UiManager) {\n this.config = config;\n this.state = state;\n this.ui = ui;\n }\n\n /**\n * Log a message if debug mode is enabled.\n */\n log(...args: unknown[]): void {\n // Debug logging disabled by default\n // console.log(\"ComfyUI Magnifying Glass:\", ...args);\n }\n\n /**\n * Log an error message.\n */\n error(...args: unknown[]): void {\n console.error(\"ComfyUI Magnifying Glass ERROR:\", ...args);\n }\n}\n"],"names":[],"mappings":";;;AAiBO,MAAM,aAAa;AAAA,EAKtB,YAAY,QAAuB,OAAuB,IAAe;AAJzE;AACA;AACA;AAGI,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,MAAuB;AAAA,EAG9B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAuB;AAC5B,YAAQ,MAAM,mCAAmC,GAAG,IAAI;AAAA,EAC5D;AACJ;"} \ No newline at end of file diff --git a/web/magnify-glass/EventHandler.js b/web/magnify-glass/EventHandler.js index 11b1ffb..679a17f 100644 --- a/web/magnify-glass/EventHandler.js +++ b/web/magnify-glass/EventHandler.js @@ -230,4 +230,3 @@ class EventHandler { export { EventHandler }; -//# sourceMappingURL=EventHandler.js.map diff --git a/web/magnify-glass/EventHandler.js.map b/web/magnify-glass/EventHandler.js.map deleted file mode 100644 index 14371a8..0000000 --- a/web/magnify-glass/EventHandler.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"EventHandler.js","sources":["../../src/magnify-glass/EventHandler.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - EventHandler (TypeScript)\n * \n * Handles keyboard and mouse events for the magnifying glass.\n */\n\nimport { isUserTyping } from '../shared/utils';\nimport { Logger } from '../shared/logger';\nimport { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';\nimport type { MagnifyGlass } from './MagnifyGlass';\n\n/**\n * Event Handler class.\n * Manages all keyboard and mouse event handling for the magnifier.\n */\nexport class EventHandler {\n magnifyGlass: MagnifyGlass;\n\n private rafId: number | null = null;\n\n constructor(magnifyGlass: MagnifyGlass) {\n this.magnifyGlass = magnifyGlass;\n\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleKeyUp = this.handleKeyUp.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n this.handleResize = this.handleResize.bind(this);\n }\n\n /**\n * Attach all event listeners.\n */\n attachListeners(): void {\n document.addEventListener(\"keydown\", this.handleKeyDown);\n document.addEventListener(\"keyup\", this.handleKeyUp);\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n window.addEventListener(\"resize\", this.handleResize);\n }\n\n /**\n * Detach all event listeners.\n */\n detachListeners(): void {\n document.removeEventListener(\"keydown\", this.handleKeyDown);\n document.removeEventListener(\"keyup\", this.handleKeyUp);\n document.removeEventListener(\"mousemove\", this.handleMouseMove);\n window.removeEventListener(\"resize\", this.handleResize);\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n /**\n * Handle window resize events.\n */\n handleResize(): void {\n if (this.magnifyGlass && this.magnifyGlass.ui) {\n this.magnifyGlass.ui.updateResponsivePosition();\n // Also update panel position if it exists\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel && infoPanel.positionManager) {\n // Determine if we should move it\n infoPanel.positionManager.positionPanel();\n }\n }\n }\n\n /**\n * Handle keydown events.\n */\n handleKeyDown(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n // Don't handle hotkeys if user is typing in an input field\n if (isUserTyping()) {\n return;\n }\n\n // Magnifier activation\n Logger.debug(`KeyDown: ${e.key} (config: ${config.activationKey}, active: ${state.active})`);\n if (e.key.toLowerCase() === config.activationKey &&\n (!config.altRequired || e.altKey)) {\n Logger.debug('Activation key matched!');\n if (config.alwaysActiveMode) {\n this.magnifyGlass.toggle();\n } else {\n if (!state.active) {\n this.magnifyGlass.toggle();\n }\n }\n }\n\n // Manual offset adjustment keys (only when magnifier is active)\n if (state.active) {\n let offsetChanged = false;\n const stepSize = config.offsetStep;\n\n if (e.key === \"ArrowUp\") {\n config.offsetY -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowDown\") {\n config.offsetY += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n config.offsetX -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n config.offsetX += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key.toLowerCase() === config.resetKey.toLowerCase() &&\n (!config.altRequired || e.altKey)) {\n this.magnifyGlass.resetOffsets();\n offsetChanged = true;\n e.preventDefault();\n }\n\n // Toggle Follow Cursor Key\n if (e.key.toLowerCase() === config.toggleFollowCursorKey &&\n (!config.altRequired || e.altKey)) {\n config.followCursor = !config.followCursor;\n this.magnifyGlass.debugger.log(`Follow Cursor Toggled: ${config.followCursor ? 'ON' : 'OFF'}`);\n if (state.active && config.followCursor) {\n this.magnifyGlass.ui.positionGlass(\n this.magnifyGlass.lastKnownMousePosition.x,\n this.magnifyGlass.lastKnownMousePosition.y\n );\n }\n e.preventDefault();\n }\n\n // Toggle Glass Preview Key (G) - mimics the hover controls button\n if (e.key.toLowerCase() === config.toggleGlassPreviewKey &&\n (!config.altRequired || e.altKey)) {\n // Find and click the toggle-glass button in the info panel hover controls\n const toggleGlassBtn = document.querySelector('[data-action=\"toggle-glass\"]') as HTMLElement;\n if (toggleGlassBtn) {\n toggleGlassBtn.click();\n this.magnifyGlass.debugger.log(`Glass Preview Toggled (via button)`);\n } else {\n // Fallback: toggle via setGlassPreviewActive directly if button not found\n const glassDiv = this.magnifyGlass.ui.glassDiv;\n if (glassDiv) {\n const isVisible = glassDiv.style.display !== 'none' && glassDiv.style.opacity !== '0';\n this.magnifyGlass.setGlassPreviewActive(!isVisible);\n this.magnifyGlass.debugger.log(`Glass Preview Toggled (fallback): ${!isVisible ? 'ON' : 'OFF'}`);\n }\n }\n e.preventDefault();\n }\n\n if (offsetChanged) {\n config.saveOffsets();\n this.magnifyGlass.updateMagnifiedView();\n }\n\n // Force Direct Capture Toggle Key\n if (e.key.toLowerCase() === config.forceDirectCaptureKey &&\n (!config.altRequired || e.altKey)) {\n // Toggle via settings to sync with UI\n const currentVal = !!config.forceDirectCapture;\n // @ts-ignore\n const app = window.app || (window.scripts && window.scripts.app);\n if (app && app.ui && app.ui.settings) {\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ForceDirectCapture', !currentVal);\n // The setting onChange handler will update config.forceDirectCapture and view\n } else {\n // Fallback if app settings not available\n config.forceDirectCapture = !currentVal;\n if ((window as any).comfyUIMagnifyGlass) {\n (window as any).comfyUIMagnifyGlass.updateMagnifiedView();\n }\n }\n\n Logger.info(`Force Direct Capture toggled: ${!currentVal}`);\n e.preventDefault();\n }\n\n // Pop-out tab toggle (Shift+P)\n if (e.shiftKey && e.key.toLowerCase() === 'p') {\n this.magnifyGlass.popOutManager.toggle();\n e.preventDefault();\n }\n }\n }\n\n /**\n * Handle keyup events.\n */\n handleKeyUp(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n if (!config.alwaysActiveMode &&\n (e.key.toLowerCase() === config.activationKey ||\n (config.altRequired && e.key === \"Alt\"))) {\n\n let shouldDeactivate = false;\n if (config.altRequired) {\n if (e.key === \"Alt\" || e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n } else {\n if (e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n }\n\n if (state.active && shouldDeactivate) {\n state.active = false;\n this.magnifyGlass.ui.hide();\n }\n }\n }\n\n /**\n * Handle mousemove events.\n */\n handleMouseMove(e: MouseEvent): void {\n // Always track mouse position\n this.magnifyGlass.lastKnownMousePosition.x = e.clientX;\n this.magnifyGlass.lastKnownMousePosition.y = e.clientY;\n\n if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return;\n\n // Throttle updates using requestAnimationFrame to prevent excessive calculations and layout thrashing\n if (this.rafId === null) {\n this.rafId = requestAnimationFrame(() => {\n this.rafId = null;\n\n // Re-check active state in case it changed while waiting for frame\n if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return;\n\n const clientX = this.magnifyGlass.lastKnownMousePosition.x;\n const clientY = this.magnifyGlass.lastKnownMousePosition.y;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const cssMouseXOnCanvas = clientX - rect.left;\n const cssMouseYOnCanvas = clientY - rect.top;\n\n // Check if the cursor is over the canvas element\n if (cssMouseXOnCanvas >= 0 && cssMouseXOnCanvas <= rect.width &&\n cssMouseYOnCanvas >= 0 && cssMouseYOnCanvas <= rect.height) {\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n this.magnifyGlass.ui.positionGlass(clientX, clientY);\n this.magnifyGlass.updateMagnifiedView();\n }\n });\n }\n }\n\n /**\n * Update the initial position when magnifier is activated.\n */\n updateInitialPosition(): void {\n if (!this.magnifyGlass.litegraphCanvas) return;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const clientX = this.magnifyGlass.lastKnownMousePosition.x;\n const clientY = this.magnifyGlass.lastKnownMousePosition.y;\n\n const isOverCanvas = clientX >= rect.left && clientX <= rect.right &&\n clientY >= rect.top && clientY <= rect.bottom;\n\n if (this.magnifyGlass.config.alwaysActiveMode || isOverCanvas) {\n const cssMouseXOnCanvas = clientX - rect.left;\n const cssMouseYOnCanvas = clientY - rect.top;\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n const glassSize = this.magnifyGlass.config.glassSize;\n\n if (!this.magnifyGlass.state.wasActivatedBefore && this.magnifyGlass.ui.glassDiv) {\n // First activation - position at top right of the window (Anchored)\n // Using style.right ensures it stays on the right even if window resizes before first usage\n this.magnifyGlass.ui.glassDiv.style.right = `${DEFAULT_PADDING}px`;\n this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.magnifyGlass.ui.glassDiv.style.left = 'auto'; // Ensure left is unset\n this.magnifyGlass.state.wasActivatedBefore = true;\n } else {\n this.magnifyGlass.ui.positionGlass(clientX, clientY);\n }\n\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/MagnifierState.js b/web/magnify-glass/MagnifierState.js index 079951a..5585fd1 100644 --- a/web/magnify-glass/MagnifierState.js +++ b/web/magnify-glass/MagnifierState.js @@ -85,4 +85,3 @@ class MagnifierState { export { MagnifierState }; -//# sourceMappingURL=MagnifierState.js.map diff --git a/web/magnify-glass/MagnifierState.js.map b/web/magnify-glass/MagnifierState.js.map deleted file mode 100644 index a68f66e..0000000 --- a/web/magnify-glass/MagnifierState.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"MagnifierState.js","sources":["../../src/magnify-glass/MagnifierState.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - MagnifierState (TypeScript)\n * \n * Manages the state of the magnifying glass.\n */\n\n/**\n * Magnifier state class.\n * Tracks activation state, cursor position, and canvas transforms.\n */\nexport class MagnifierState {\n /** Whether the magnifier is currently active */\n active: boolean;\n\n /** Track if the glass has been activated before */\n wasActivatedBefore: boolean;\n\n /** Cursor X relative to litegraphCanvas */\n x: number;\n\n /** Cursor Y relative to litegraphCanvas */\n y: number;\n\n /** Calculated source area X */\n sourceX: number;\n\n /** Calculated source area Y */\n sourceY: number;\n\n /** Calculated source area width */\n sourceWidth: number;\n\n /** Calculated source area height */\n sourceHeight: number;\n\n /** Current canvas scale/zoom */\n canvasScale: number;\n\n /** Canvas translation X */\n canvasOffsetX: number;\n\n /** Canvas translation Y */\n canvasOffsetY: number;\n\n /** Flag to manage requestAnimationFrame */\n isRenderScheduled: boolean;\n\n /** Whether glass drag mode is enabled (move icon on hover controls) */\n isDragModeEnabled: boolean;\n\n /** Whether glass preview is hidden via hover controls (but tool still active) */\n isPreviewHidden: boolean;\n\n // --- Debug Metrics for Virtual Zoom ---\n virtualMouseCssX: number;\n virtualMouseCssY: number;\n virtualGraphX: number;\n virtualGraphY: number;\n virtualNewOffsetX: number;\n virtualNewOffsetY: number;\n\n // --- Manual Calibration ---\n virtualDebugOffsetX: number;\n virtualDebugOffsetY: number;\n\n constructor() {\n this.active = false;\n this.wasActivatedBefore = false;\n this.x = 0;\n this.y = 0;\n this.sourceX = 0;\n this.sourceY = 0;\n this.sourceWidth = 0;\n this.sourceHeight = 0;\n this.canvasScale = 1.0;\n this.canvasOffsetX = 0;\n this.canvasOffsetY = 0;\n this.isRenderScheduled = false;\n this.isDragModeEnabled = false;\n this.isPreviewHidden = false;\n\n this.virtualMouseCssX = 0;\n this.virtualMouseCssY = 0;\n this.virtualGraphX = 0;\n this.virtualGraphY = 0;\n this.virtualNewOffsetX = 0;\n this.virtualNewOffsetY = 0;\n this.virtualDebugOffsetX = 0;\n this.virtualDebugOffsetY = 0;\n }\n\n /**\n * Reset state to initial values.\n */\n reset(): void {\n this.active = false;\n this.wasActivatedBefore = false;\n this.x = 0;\n this.y = 0;\n this.sourceX = 0;\n this.sourceY = 0;\n this.sourceWidth = 0;\n this.sourceHeight = 0;\n this.canvasScale = 1.0;\n this.canvasOffsetX = 0;\n this.canvasOffsetY = 0;\n this.isRenderScheduled = false;\n }\n}\n"],"names":[],"mappings":";;;AAUO,MAAM,eAAe;AAAA,EAuDxB,cAAc;AArDd;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AAGA;AAAA;AACA;AAGI,SAAK,SAAS;AACd,SAAK,qBAAqB;AAC1B,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AACrB,SAAK,oBAAoB;AACzB,SAAK,oBAAoB;AACzB,SAAK,kBAAkB;AAEvB,SAAK,mBAAmB;AACxB,SAAK,mBAAmB;AACxB,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AACrB,SAAK,oBAAoB;AACzB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAC3B,SAAK,sBAAsB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACV,SAAK,SAAS;AACd,SAAK,qBAAqB;AAC1B,SAAK,IAAI;AACT,SAAK,IAAI;AACT,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,gBAAgB;AACrB,SAAK,gBAAgB;AACrB,SAAK,oBAAoB;AAAA,EAC7B;AACJ;"} \ No newline at end of file diff --git a/web/magnify-glass/MagnifyGlass.js b/web/magnify-glass/MagnifyGlass.js index 167974a..3930c62 100644 --- a/web/magnify-glass/MagnifyGlass.js +++ b/web/magnify-glass/MagnifyGlass.js @@ -582,4 +582,3 @@ class MagnifyGlass { export { MagnifyGlass }; -//# sourceMappingURL=MagnifyGlass.js.map diff --git a/web/magnify-glass/MagnifyGlass.js.map b/web/magnify-glass/MagnifyGlass.js.map deleted file mode 100644 index 8e0871d..0000000 --- a/web/magnify-glass/MagnifyGlass.js.map +++ /dev/null @@ -1 +0,0 @@ -{"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 /** Animation loop ID for continuous video rendering */\n private animationLoopId: number | null;\n\n /** Whether videos are currently visible in magnified view */\n private hasVisibleVideos: boolean;\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 // Animation loop for video rendering\n this.animationLoopId = null;\n this.hasVisibleVideos = false;\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 // Start continuous animation loop for smooth video playback\n this.startAnimationLoop();\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.stopAnimationLoop();\n this.hasVisibleVideos = 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 // Removed early clear to batch reads before writes\n // this.ui.htmlOverlayContainer.innerHTML = '';\n\n // Track if any videos are found for animation loop management\n let foundVideos = false;\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) {\n // Ensure any previous overlays are cleared if we exit early\n this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n // Calculate source region in graph coordinates for culling\n // This mirrors calculateSourceRegion() but stops at graph coords\n if (this.state.canvasScale === 0) {\n // Ensure any previous overlays are cleared if we exit early\n this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n const rect = this.litegraphCanvas.getBoundingClientRect();\n const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1;\n\n // Pre-calculate invariant values for the frame\n const currentScale = this.state.canvasScale;\n const isVirtualZoomMode = currentScale < 0.7;\n const canvasRect = rect;\n // Pivot point (mouse position) in CSS coordinates\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n const cursorCssX = pivotCssX;\n const cursorCssY = pivotCssY;\n const cursorGraphX = (cursorCssX - this.state.canvasOffsetX) / this.state.canvasScale;\n const cursorGraphY = (cursorCssY - this.state.canvasOffsetY) / this.state.canvasScale;\n const targetGraphCenterX = cursorGraphX + this.config.offsetX;\n const targetGraphCenterY = cursorGraphY + this.config.offsetY;\n const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale;\n const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale;\n\n const sourceGraphRect = {\n x: targetGraphCenterX - (sourceGraphWidth / 2),\n y: targetGraphCenterY - (sourceGraphHeight / 2),\n width: sourceGraphWidth,\n height: sourceGraphHeight\n };\n\n // Batch optimization: Collect all render tasks first (Read Phase)\n interface RenderTask {\n element: HTMLElement;\n type: 'text' | 'video' | 'image';\n // Store the calculated overlay geometry so we don't recalculate in loop 2\n overlayRect: Rectangle;\n fontSize?: number;\n }\n const renderTasks: RenderTask[] = [];\n\n for (const node of nodes) {\n // Optimization: Cull nodes that are not in the source region\n if (node.pos && node.size) {\n const nodeRect = {\n x: node.pos[0],\n y: node.pos[1],\n width: node.size[0],\n height: node.size[1]\n };\n if (!rectsOverlap(sourceGraphRect, nodeRect)) {\n continue;\n }\n }\n\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 // Check for VHS-style widgets with videoEl property (VideoHelperSuite pattern)\n const widgetName = String(widget.name || '').toLowerCase();\n if (widgetName === 'videopreview' || widgetName === 'audiopreview') {\n const videoEl = widget.videoEl as HTMLVideoElement | undefined;\n if (videoEl && !videoEl.hidden && videoEl.videoWidth > 0) {\n isVideoElement = true;\n foundVideos = true;\n elementToProcess = videoEl;\n }\n }\n\n // Check for standard DOM element widgets (existing logic)\n if (!elementToProcess && 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 foundVideos = 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 foundVideos = 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 // READ: Force layout calculation here, batched together\n const widgetRect = elementToProcess.getBoundingClientRect();\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 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 finalWidgetCssX = (widgetCssX - pivotCssX) / currentScale + pivotCssX;\n finalWidgetCssY = (widgetCssY - pivotCssY) / currentScale + pivotCssY;\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 // Only process further if it actually overlaps\n if (rectsOverlap(magnifyRect, widgetSourceRect)) {\n const task: RenderTask = {\n element: elementToProcess,\n type: isTextElement ? 'text' : (isVideoElement ? 'video' : 'image'),\n overlayRect: widgetSourceRect, // Store calculated rect\n fontSize: undefined\n };\n\n // READ: Get computed style for text elements (only if overlapping)\n if (isTextElement) {\n task.fontSize = parseFloat(window.getComputedStyle(elementToProcess).fontSize);\n }\n\n if (isVideoElement) {\n foundVideos = true;\n }\n\n renderTasks.push(task);\n }\n }\n }\n }\n\n // WRITE Phase: Clear container and append new elements\n this.ui.htmlOverlayContainer.innerHTML = '';\n const fragment = document.createDocumentFragment();\n\n // Process collected tasks\n for (const task of renderTasks) {\n const elementToProcess = task.element;\n const widgetSourceRect = task.overlayRect; // Use stored calculated rect\n const isTextElement = task.type === 'text';\n const isVideoElement = task.type === 'video';\n const isImageElement = task.type === 'image';\n\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 // READ: Use stored font size\n const originalFontSize = task.fontSize || 12; // Fallback\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 => {\n if (e.name !== 'AbortError') {\n console.warn(\"Magnify Glass: Cloned video play failed\", e);\n }\n });\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 fragment.appendChild(clonedElement);\n }\n\n this.ui.htmlOverlayContainer.appendChild(fragment);\n\n // Manage animation loop based on video presence\n if (foundVideos && !this.hasVisibleVideos) {\n this.hasVisibleVideos = true;\n this.startAnimationLoop();\n } else if (!foundVideos && this.hasVisibleVideos) {\n this.hasVisibleVideos = false;\n // Loop will stop itself on next iteration\n }\n }\n\n /**\n * Start the animation loop for continuous video rendering.\n * Only runs when videos are visible in the magnified view.\n * This loop directly renders without going through updateMagnifiedView's\n * scheduling logic to ensure continuous frame updates for video playback.\n */\n private startAnimationLoop(): void {\n if (this.animationLoopId !== null) return; // Already running\n\n const animate = () => {\n // Stop if glass is deactivated or renderer unavailable\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n this.stopAnimationLoop();\n return;\n }\n\n // Skip if glass preview is hidden\n if (this.state.isPreviewHidden) {\n this.animationLoopId = requestAnimationFrame(animate);\n return;\n }\n\n // Update canvas transformation and source region\n this.updateCanvasTransformation();\n this.calculateSourceRegion();\n\n // Direct render (bypass isRenderScheduled to ensure continuous updates)\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 // Render HTML overlays for video 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.animationLoopId = requestAnimationFrame(animate);\n };\n\n this.animationLoopId = requestAnimationFrame(animate);\n }\n\n /**\n * Stop the animation loop.\n */\n private stopAnimationLoop(): void {\n if (this.animationLoopId !== null) {\n cancelAnimationFrame(this.animationLoopId);\n this.animationLoopId = null;\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.stopAnimationLoop();\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-glass/OffscreenRenderer.js b/web/magnify-glass/OffscreenRenderer.js index 4da2aa1..59db24c 100644 --- a/web/magnify-glass/OffscreenRenderer.js +++ b/web/magnify-glass/OffscreenRenderer.js @@ -806,4 +806,3 @@ let OffscreenRenderer = _OffscreenRenderer; export { OffscreenRenderer }; -//# sourceMappingURL=OffscreenRenderer.js.map diff --git a/web/magnify-glass/OffscreenRenderer.js.map b/web/magnify-glass/OffscreenRenderer.js.map deleted file mode 100644 index 2fe7c4a..0000000 --- a/web/magnify-glass/OffscreenRenderer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"OffscreenRenderer.js","sources":["../../src/magnify-glass/OffscreenRenderer.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Offscreen Renderer\n * \n * Hybrid Renderer:\n * 1. Direct Capture (Nuclear) for near-100% zoom (Perfect Alignment, Good Quality).\n * 2. Virtual Zoom (High Res) for zoomed-out views (High Detail, sensitive alignment).\n */\n\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\n\ndeclare const app: any;\n\nexport class OffscreenRenderer {\n private config: ConfigManager;\n private state: MagnifierState;\n private offscreenCanvas: HTMLCanvasElement | null = null;\n private offscreenCtx: CanvasRenderingContext2D | null = null;\n private lastRenderWidth: number = 0;\n private lastRenderHeight: number = 0;\n private isCapturing: boolean = false;\n\n // Virtual Zoom Throttling (60 FPS = ~16ms per frame)\n private static readonly VIRTUAL_ZOOM_MIN_INTERVAL_MS = 16;\n private lastVirtualZoomTime: number = 0;\n private cachedVirtualZoomResult: HTMLCanvasElement | null = null;\n\n constructor(config: ConfigManager, state: MagnifierState) {\n this.config = config;\n this.state = state;\n this.initOffscreenCanvas();\n }\n\n private initOffscreenCanvas(): void {\n this.offscreenCanvas = document.createElement('canvas');\n this.offscreenCtx = this.offscreenCanvas.getContext('2d');\n }\n\n renderHighResRegion(targetCanvas: HTMLCanvasElement): HTMLCanvasElement | null {\n if (!this.offscreenCanvas || !this.offscreenCtx) return null;\n if (this.isCapturing) return null;\n\n const lgCanvas = app?.canvas;\n if (!lgCanvas || !lgCanvas.ds) return null;\n\n const renderSize = this.config.glassSize;\n\n // Resize offscreen canvas\n if (this.lastRenderWidth !== renderSize || this.lastRenderHeight !== renderSize) {\n this.offscreenCanvas.width = renderSize;\n this.offscreenCanvas.height = renderSize;\n this.lastRenderWidth = renderSize;\n this.lastRenderHeight = renderSize;\n }\n\n const currentScale = lgCanvas.ds.scale;\n const rect = targetCanvas.getBoundingClientRect();\n // Calculate DPR exactly as EventHandler does (Canvas Pixels / CSS Pixels)\n const dpr = rect.width > 0 ? targetCanvas.width / rect.width : 1;\n\n // Threshold for Hybrid Mode\n // If we are zoomed in enough (>= 100%), Direct Capture quality is perfect,\n // and it guarantees perfect alignment with zero extra draws.\n // If we are zoomed out (< 100%), we use High Res Virtual Zoom for crisp details.\n // Note: Image preview nodes are handled by drawImagePreviewsNatively() during Virtual Zoom.\n // If forceDirectCapture is enabled (via hotkey), always use Direct Capture for accurate cursor alignment.\n const useDirectCapture = currentScale >= 1.0 || this.config.forceDirectCapture;\n\n if (useDirectCapture) {\n // Direct Capture: Full speed, no extra draws\n this.cachedVirtualZoomResult = null; // Invalidate cache when switching modes\n return this.renderDirectCapture(targetCanvas, renderSize, dpr);\n } else {\n // Virtual Zoom: Throttled to 60 FPS to prevent FPS counter inflation\n const now = performance.now();\n const elapsed = now - this.lastVirtualZoomTime;\n\n if (elapsed < OffscreenRenderer.VIRTUAL_ZOOM_MIN_INTERVAL_MS && this.cachedVirtualZoomResult) {\n // Return cached result if within throttle window\n return this.cachedVirtualZoomResult;\n }\n\n // Perform the expensive virtual zoom render\n const result = this.renderVirtualZoom(lgCanvas, targetCanvas, renderSize, currentScale, dpr);\n this.lastVirtualZoomTime = now;\n this.cachedVirtualZoomResult = result;\n return result;\n }\n }\n\n /**\n * Detect if there are nodes with image previews in the capture region.\n * These nodes (Save Image, Preview Image, etc.) have images that are drawn\n * via onDrawBackground/onDrawForeground and cause artifacts during Virtual Zoom.\n */\n private detectImagePreviewNodes(dpr: number, scale: number, offset: number[]): boolean {\n const graph = app?.graph;\n if (!graph || !graph._nodes) return false;\n\n const renderSize = this.config.glassSize;\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n\n // Mouse position in CSS pixels\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n // Capture region in CSS pixels\n const captureLeft = pivotCssX - (sourceSizeCss / 2);\n const captureTop = pivotCssY - (sourceSizeCss / 2);\n const captureRight = captureLeft + sourceSizeCss;\n const captureBottom = captureTop + sourceSizeCss;\n\n // Node types that have image previews\n const imagePreviewTypes = [\n 'SaveImage', 'PreviewImage', 'LoadImage', 'LoadImageMask',\n 'VHS_LoadVideo', 'VHS_VideoCombine', // Video nodes\n 'ImagePreview', 'ShowImage' // Common variants\n ];\n\n for (const node of graph._nodes) {\n if (!node.pos || !node.size) continue;\n if (node.flags?.collapsed) continue;\n\n // Check if this node type has image previews\n const nodeType = node.type || node.comfyClass || '';\n const hasImagePreview = imagePreviewTypes.some(t =>\n nodeType.toLowerCase().includes(t.toLowerCase())\n ) || node.imgs?.length > 0; // Also check for imgs array directly\n\n if (!hasImagePreview) continue;\n\n // Calculate node position in CSS pixels\n const nodeCssX = node.pos[0] * scale + offset[0];\n const nodeCssY = node.pos[1] * scale + offset[1];\n const nodeCssWidth = node.size[0] * scale;\n const nodeCssHeight = node.size[1] * scale;\n\n // Check if node overlaps with capture region\n const nodeRight = nodeCssX + nodeCssWidth;\n const nodeBottom = nodeCssY + nodeCssHeight;\n\n const overlaps = !(nodeRight < captureLeft || nodeCssX > captureRight ||\n nodeBottom < captureTop || nodeCssY > captureBottom);\n\n if (overlaps) {\n return true; // Found an image preview node in capture region\n }\n }\n\n return false;\n }\n\n /**\n * Mode 1: Direct Capture (Nuclear Fix)\n * Copies pixels directly from screen. Zero drift.\n */\n private renderDirectCapture(targetCanvas: HTMLCanvasElement, renderSize: number, dpr: number): HTMLCanvasElement | null {\n try {\n this.isCapturing = true;\n\n // state.x/y are Backing Pixels. pivotCss is Backing / DPR.\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n\n // sourceRect in Backing Pixels\n const sourceWidth = sourceSizeCss * dpr;\n const sourceHeight = sourceSizeCss * dpr;\n const sourceX = (pivotCssX - (sourceSizeCss / 2)) * dpr;\n const sourceY = (pivotCssY - (sourceSizeCss / 2)) * dpr;\n\n this.offscreenCtx!.clearRect(0, 0, renderSize, renderSize);\n\n // Apply accessibility filters\n this.offscreenCtx!.save();\n if (this.config.invertColors) {\n this.offscreenCtx!.filter = 'invert(1)';\n } else if (this.config.grayscaleMode) {\n this.offscreenCtx!.filter = 'grayscale(1)';\n }\n\n this.offscreenCtx!.drawImage(\n targetCanvas,\n sourceX, sourceY, sourceWidth, sourceHeight,\n 0, 0, renderSize, renderSize\n );\n this.offscreenCtx!.restore();\n\n // Draw native widget text on top of the captured canvas\n const lgCanvas = app?.canvas;\n const currentScale = lgCanvas?.ds?.scale ?? 1.0;\n const currentOffset: [number, number] = lgCanvas?.ds?.offset ? [lgCanvas.ds.offset[0], lgCanvas.ds.offset[1]] : [0, 0];\n this.drawWidgetTextNatively(sourceX, sourceY, sourceWidth, sourceHeight, renderSize, currentScale, currentOffset);\n\n // Draw node titles if emphasis is enabled\n if (this.config.accessibilityEnabled && this.config.nodeTitleEmphasis) {\n this.drawNodeTitlesNatively(sourceX, sourceY, sourceWidth, sourceHeight, renderSize, currentScale, currentOffset);\n }\n\n // NOTE: We do NOT call drawImagePreviewsNatively() here.\n // Direct Capture copies pixels from the screen which already has correctly-drawn images.\n // Native image drawing is only needed during Virtual Zoom where ComfyUI's rendering fails.\n\n // Draw cursor preview overlay if enabled\n if (this.config.showCursorPreview) {\n this.drawCursorPreview(renderSize);\n }\n\n this.isCapturing = false;\n return this.offscreenCanvas;\n } catch (e) {\n console.warn(\"DirectCapture failed\", e);\n this.isCapturing = false;\n return null;\n }\n }\n\n /**\n * Mode 2: Virtual Zoom (High Res)\n * Temporarily sets scale to 1.0 to render details.\n * Uses LiteGraph's setZoom() API for correct pivot handling.\n */\n private renderVirtualZoom(lgCanvas: any, targetCanvas: HTMLCanvasElement, renderSize: number, currentScale: number, dpr: number): HTMLCanvasElement | null {\n try {\n this.isCapturing = true;\n // Set global flag so node renderers can check and skip expensive operations\n (window as any).__magnifyGlassCapturing = true;\n\n // Pivot = Mouse in CSS Pixels (relative to canvas element)\n const pivotCssX = this.state.x / dpr;\n const pivotCssY = this.state.y / dpr;\n\n // Target Scale (1.0 for high-res capture)\n const targetScale = Math.max(1.0, currentScale);\n\n // Store original state for restoration\n const origScale = lgCanvas.ds.scale;\n const origOffsetX = lgCanvas.ds.offset[0];\n const origOffsetY = lgCanvas.ds.offset[1];\n\n // Use LiteGraph's setZoom API - this properly handles the pivot point\n // The center parameter tells LiteGraph to zoom around that screen position\n if (typeof lgCanvas.setZoom === 'function') {\n lgCanvas.setZoom(targetScale, [pivotCssX, pivotCssY]);\n } else {\n // Fallback: manual calculation (legacy, may drift)\n const pivotGraphX = (pivotCssX - origOffsetX) / currentScale;\n const pivotGraphY = (pivotCssY - origOffsetY) / currentScale;\n const newOffsetX = pivotCssX - (pivotGraphX * targetScale);\n const newOffsetY = pivotCssY - (pivotGraphY * targetScale);\n lgCanvas.ds.scale = targetScale;\n lgCanvas.ds.offset[0] = newOffsetX;\n lgCanvas.ds.offset[1] = newOffsetY;\n }\n\n // Hide images AND preview widgets from nodes to prevent them from being drawn on the captured canvas\n // This prevents \"ghosting\" artifacts and crashes (since some custom widgets fail in Virtual Zoom)\n const hiddenNodeImages = new Map();\n const hiddenNodeWidgets = new Map();\n\n if (app?.graph?._nodes) {\n for (const node of app.graph._nodes) {\n // 1. Hide node.imgs\n if (node.imgs) {\n hiddenNodeImages.set(node, node.imgs);\n node.imgs = null;\n }\n\n // 2. Hide preview widgets\n if (node.widgets && Array.isArray(node.widgets)) {\n const hasPreviewWidget = node.widgets.some((w: any) => {\n const wName = String(w.name || '').toLowerCase();\n const wType = String(w.type || '').toLowerCase();\n return wName.includes('preview') || wName.includes('image') || wName.includes('gallery') || wName.includes('upload') ||\n wType.includes('preview') || wType.includes('image');\n });\n\n if (hasPreviewWidget) {\n // Backup original widgets array\n hiddenNodeWidgets.set(node, node.widgets);\n // Filter out the preview widgets for this draw call\n node.widgets = node.widgets.filter((w: any) => {\n const wName = String(w.name || '').toLowerCase();\n const wType = String(w.type || '').toLowerCase();\n // Only hide explicit preview widgets. Inputs (image, upload) should remain visible.\n const isPreview = wName.includes('preview') || wName.includes('gallery') ||\n wType.includes('preview');\n return !isPreview;\n });\n }\n }\n }\n }\n\n const origUpdatePreviews = lgCanvas.updatePreviews;\n try {\n // Temporarily disable ComfyUI's image preview updates during capture\n // This prevents massive console spam if images are missing (404s)\n if (typeof origUpdatePreviews === 'function') {\n lgCanvas.updatePreviews = () => { };\n }\n\n lgCanvas.draw(true, true);\n } finally {\n // Restore immediately after draw (in finally to handle exceptions)\n if (typeof origUpdatePreviews === 'function') {\n lgCanvas.updatePreviews = origUpdatePreviews;\n }\n\n // Restore images\n for (const [node, imgs] of hiddenNodeImages.entries()) {\n node.imgs = imgs;\n }\n // Restore widgets\n for (const [node, widgets] of hiddenNodeWidgets.entries()) {\n node.widgets = widgets;\n }\n }\n\n // Capture pixels around the pivot point\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n const sourceWidth = sourceSizeCss * dpr;\n const sourceHeight = sourceSizeCss * dpr;\n const sourceX = (pivotCssX - (sourceSizeCss / 2)) * dpr;\n const sourceY = (pivotCssY - (sourceSizeCss / 2)) * dpr;\n\n this.offscreenCtx!.clearRect(0, 0, renderSize, renderSize);\n\n // Apply accessibility filters\n this.offscreenCtx!.save();\n if (this.config.invertColors) {\n this.offscreenCtx!.filter = 'invert(1)';\n } else if (this.config.grayscaleMode) {\n this.offscreenCtx!.filter = 'grayscale(1)';\n }\n\n this.offscreenCtx!.drawImage(\n targetCanvas,\n sourceX, sourceY, sourceWidth, sourceHeight,\n 0, 0, renderSize, renderSize\n );\n this.offscreenCtx!.restore();\n\n // Draw native widget text on top of the captured canvas\n // Note: For virtual zoom, we use targetScale and the CURRENT offset (after setZoom)\n // because lgCanvas.setZoom() modified the offset to zoom around the pivot\n const captureOffset: [number, number] = [lgCanvas.ds.offset[0], lgCanvas.ds.offset[1]];\n this.drawWidgetTextNatively(sourceX, sourceY, sourceWidth, sourceHeight, renderSize, targetScale, captureOffset);\n\n // Draw node titles if emphasis is enabled\n if (this.config.accessibilityEnabled && this.config.nodeTitleEmphasis) {\n this.drawNodeTitlesNatively(sourceX, sourceY, sourceWidth, sourceHeight, renderSize, targetScale, captureOffset);\n }\n\n // Draw image/video previews natively on top of the captured canvas\n this.drawImagePreviewsNatively(sourceX, sourceY, sourceWidth, sourceHeight, renderSize, targetScale, captureOffset);\n\n // Draw cursor preview overlay if enabled\n if (this.config.showCursorPreview) {\n this.drawCursorPreview(renderSize);\n }\n\n // Restore original zoom state and draw one clean frame\n lgCanvas.ds.scale = origScale;\n lgCanvas.ds.offset[0] = origOffsetX;\n lgCanvas.ds.offset[1] = origOffsetY;\n\n const finalOrigUpdatePreviews = lgCanvas.updatePreviews;\n try {\n if (typeof finalOrigUpdatePreviews === 'function') {\n lgCanvas.updatePreviews = () => { };\n }\n\n lgCanvas.draw(true, true);\n } finally {\n if (typeof finalOrigUpdatePreviews === 'function') {\n lgCanvas.updatePreviews = finalOrigUpdatePreviews;\n }\n }\n\n this.isCapturing = false;\n (window as any).__magnifyGlassCapturing = false;\n return this.offscreenCanvas;\n\n } catch (e) {\n console.warn(\"VirtualZoom failed\", e);\n // Emergency restore\n lgCanvas.ds.scale = currentScale;\n lgCanvas.draw(true, true);\n this.isCapturing = false;\n (window as any).__magnifyGlassCapturing = false;\n return null;\n }\n }\n\n\n\n /**\n * Draw node titles natively with accessibility styling.\n */\n private drawNodeTitlesNatively(\n sourceX: number,\n sourceY: number,\n sourceWidth: number,\n sourceHeight: number,\n renderSize: number,\n scale: number,\n offset: [number, number]\n ): void {\n const graph = app?.graph;\n if (!graph || !graph._nodes || !this.offscreenCtx) return;\n\n const ctx = this.offscreenCtx;\n const TITLE_HEIGHT = 30;\n\n // Coordinate transforms\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n const actualDpr = sourceWidth / sourceSizeCss;\n const captureScale = renderSize / sourceWidth;\n\n // Source region in CSS pixels\n const sourceCssX = sourceX / actualDpr;\n const sourceCssY = sourceY / actualDpr;\n const sourceCssWidth = sourceWidth / actualDpr;\n const sourceCssHeight = sourceHeight / actualDpr;\n\n for (const node of graph._nodes) {\n if (!node.pos || !node.size) continue;\n if (node.flags?.collapsed) continue;\n\n const title = node.title || node.type || \"Node\";\n\n // Node position in CSS pixels\n const nodeCssX = node.pos[0] * scale + offset[0];\n const nodeCssY = node.pos[1] * scale + offset[1];\n const nodeCssWidth = node.size[0] * scale;\n\n // Check visibility\n if (nodeCssX + nodeCssWidth < sourceCssX || nodeCssX > sourceCssX + sourceCssWidth) continue;\n if (nodeCssY + (TITLE_HEIGHT * scale) < sourceCssY || nodeCssY > sourceCssY + sourceCssHeight) continue;\n\n // Draw\n const canvasX = (nodeCssX - sourceCssX) * actualDpr * captureScale;\n const canvasY = (nodeCssY - sourceCssY) * actualDpr * captureScale;\n // Limit width for title background\n const canvasWidth = Math.min(nodeCssWidth, nodeCssWidth) * actualDpr * captureScale;\n const canvasHeight = TITLE_HEIGHT * scale * actualDpr * captureScale;\n\n if (canvasX + canvasWidth > 0 && canvasX < renderSize && canvasY + canvasHeight > 0 && canvasY < renderSize) {\n ctx.save();\n\n // Enhanced Background\n ctx.beginPath();\n ctx.roundRect(canvasX, canvasY - (5 * scale), canvasWidth, canvasHeight, 4);\n ctx.fillStyle = this.config.highContrastMode ? '#000000' : '#222222';\n ctx.fill();\n\n // Border/Highlight\n ctx.strokeStyle = this.config.highContrastMode ? '#ffffff' : '#444444';\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Enhanced Text\n const baseFontSize = 14;\n let scaleFactor = this.config.fontScaleFactor / 100;\n // Boost title size a bit more\n scaleFactor *= 1.1;\n\n const fontSize = Math.max(12, Math.min(32, baseFontSize * scale * actualDpr * captureScale * scaleFactor));\n\n ctx.font = `bold ${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif`;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n\n // Color based on settings\n let textColor = '#ffffff';\n if (this.config.highContrastMode) textColor = '#ffff00'; // Yellow on black is very high contrast\n\n ctx.fillStyle = textColor;\n\n // Accessibility features\n if (this.config.textGlowEnabled) {\n ctx.shadowBlur = this.config.textGlowIntensity;\n ctx.shadowColor = this.config.textGlowColor;\n }\n\n const padding = 10 * scale * actualDpr * captureScale;\n ctx.fillText(title, canvasX + padding, canvasY + (canvasHeight / 2) - (5 * scale));\n\n ctx.restore();\n }\n }\n }\n\n /**\n * Draw widget text natively on the offscreen canvas.\n * This renders text content that would otherwise be lost since widgets are DOM elements.\n * \n * @param sourceX - Source X position in backing pixels\n * @param sourceY - Source Y position in backing pixels\n * @param sourceWidth - Source width in backing pixels\n * @param sourceHeight - Source height in backing pixels\n * @param renderSize - Output render size in pixels\n * @param scale - Canvas scale used during capture\n * @param offset - Canvas offset [x, y] used during capture\n */\n private drawWidgetTextNatively(\n sourceX: number,\n sourceY: number,\n sourceWidth: number,\n sourceHeight: number,\n renderSize: number,\n scale: number,\n offset: [number, number]\n ): void {\n const graph = app?.graph;\n if (!graph || !graph._nodes || !this.offscreenCtx) return;\n\n const ctx = this.offscreenCtx;\n const lgCanvas = app?.canvas;\n if (!lgCanvas || !lgCanvas.ds) return;\n\n // LiteGraph layout constants\n const TITLE_HEIGHT = 30;\n const WIDGET_HEIGHT = 20;\n const WIDGET_MARGIN = 4;\n const PADDING = 15;\n\n // Use the passed offset (capture-time offset), not current lgCanvas offset\n // const ds = lgCanvas.ds;\n // const offset = ds.offset || [0, 0]; // OLD: This was wrong\n\n // The offset from LiteGraph is in CSS pixels\n // The sourceX/Y/Width/Height are in backing pixels\n // We need to convert properly\n\n // Calculate DPR from the source parameters\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n const actualDpr = sourceWidth / sourceSizeCss;\n const captureScale = renderSize / sourceWidth;\n\n // Source region in CSS pixels\n const sourceCssX = sourceX / actualDpr;\n const sourceCssY = sourceY / actualDpr;\n const sourceCssWidth = sourceWidth / actualDpr;\n const sourceCssHeight = sourceHeight / actualDpr;\n\n for (const node of graph._nodes) {\n if (!node.widgets || !node.pos || !node.size) continue;\n if (node.flags?.collapsed) continue;\n\n // Calculate node position in CSS pixels\n // node.pos is in graph coordinates, transform: screenCss = graphPos * scale + offset\n const nodeCssX = node.pos[0] * scale + offset[0];\n const nodeCssY = node.pos[1] * scale + offset[1];\n const nodeCssWidth = node.size[0] * scale;\n const nodeCssHeight = node.size[1] * scale;\n\n // Check if node is in captured region (CSS coordinates)\n if (nodeCssX + nodeCssWidth < sourceCssX || nodeCssX > sourceCssX + sourceCssWidth) continue;\n if (nodeCssY + nodeCssHeight < sourceCssY || nodeCssY > sourceCssY + sourceCssHeight) continue;\n\n // Calculate widget positions\n let widgetY = TITLE_HEIGHT;\n\n for (const widget of node.widgets) {\n // Use widget.last_y if available, otherwise use calculated position\n const widgetLocalY = (widget as any).last_y ?? widgetY;\n\n // Check if widget type contains text AND is a multi-line text area\n // We only want to draw our custom rendering for multi-line text widgets (like CLIP prompts)\n // NOT for simple single-line string inputs (like filename_prefix) which are already canvas-rendered\n const widgetType = String(widget.type || '').toLowerCase();\n\n // Check if this is a multi-line text widget that needs our native rendering\n // Multi-line text widgets typically have:\n // - type 'customtext' (CLIP text encode uses this)\n // - computedHeight >= 40 (multi-line areas are taller)\n // Single-line string inputs typically have:\n // - type 'string' with no computedHeight or small computedHeight\n // - These are already rendered on the canvas by ComfyUI\n const computedHeight = (widget as any).computedHeight || 0;\n const isMarkdownWidget = widgetType === 'markdown'; // ComfyUI standard type\n\n const isMultiLineWidget = isMarkdownWidget || widgetType === 'customtext' ||\n ((widgetType === 'text' || widgetType === 'textarea') && computedHeight >= 40);\n\n // Common calculations for positioning\n const widgetCssX = nodeCssX + (PADDING * scale);\n const widgetCssY = nodeCssY + (widgetLocalY * scale);\n const widgetCssWidth = nodeCssWidth - (PADDING * 2 * scale);\n\n // Convert from CSS to offscreen canvas coordinates\n const canvasX = (widgetCssX - sourceCssX) * actualDpr * captureScale;\n const canvasY = (widgetCssY - sourceCssY) * actualDpr * captureScale;\n const widgetWidth = widgetCssWidth * actualDpr * captureScale;\n\n // Calculate font size\n // Calculate font size with accessibility scaling\n const baseFontSize = 13;\n let scaleFactor = 1.0;\n if (this.config.accessibilityEnabled) {\n scaleFactor = this.config.fontScaleFactor / 100;\n }\n const fontSize = Math.max(10, Math.min(28 * scaleFactor, baseFontSize * scale * actualDpr * captureScale * scaleFactor));\n const lineHeight = fontSize * 1.4;\n\n // ----------------------------------------------------------------\n // RENDER TEXT / MARKDOWN\n // ----------------------------------------------------------------\n if (isMultiLineWidget && widget.value !== undefined && widget.value !== null) {\n const textValue = String(widget.value);\n if (textValue.length > 0) {\n // Calculate required height based on content\n let contentHeight = 0;\n if (isMarkdownWidget) {\n // Rough estimation of height needed\n ctx.save();\n const fontWeight = (this.config.accessibilityEnabled && this.config.boldTextEnabled) ? '700' : '500';\n ctx.font = `${fontWeight} ${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif`;\n const maxWidth = widgetWidth - 12;\n\n const lines = textValue.split('\\n');\n for (const line of lines) {\n const words = line.split(/(\\s+)/);\n let currentLine = '';\n let lineCount = 1;\n for (const word of words) {\n if (ctx.measureText(currentLine + word).width > maxWidth) {\n lineCount++;\n currentLine = word;\n } else {\n currentLine += word;\n }\n }\n contentHeight += lineCount * lineHeight;\n }\n ctx.restore();\n\n // Add padding\n contentHeight += 16;\n }\n\n // Calculate available height in canvas pixels to prevent spilling\n // Node bottom in CSS pixels\n const nodeBottomCss = nodeCssY + nodeCssHeight;\n // Max height for this widget in CSS pixels (stay inside node, minus padding)\n const maxWidgetHeightCss = Math.max(0, nodeBottomCss - widgetCssY - (PADDING * scale));\n // Convert to canvas pixels\n const maxWidgetHeight = maxWidgetHeightCss * actualDpr * captureScale;\n\n // Calculate widget height - Render full content for markdown up to node bounds\n const widgetHeight = (isMarkdownWidget)\n ? Math.min(Math.max((widget as any).computedHeight * scale * captureScale || 0, contentHeight), maxWidgetHeight)\n : ((widget as any).computedHeight\n ? (widget as any).computedHeight * scale * captureScale\n : Math.max(80, fontSize * 5));\n\n // Check if widget is at least partially visible (more lenient bounds check)\n const widgetRight = canvasX + widgetWidth;\n const widgetBottom = canvasY + widgetHeight;\n const isVisible = widgetRight > 0 && canvasX < renderSize &&\n widgetBottom > 0 && canvasY < renderSize;\n\n if (isVisible) {\n ctx.save();\n\n // Container bounds\n const containerX = canvasX - 4;\n const containerY = canvasY - 4;\n const containerWidth = widgetWidth + 8;\n const containerHeight = widgetHeight + 8;\n const borderRadius = 8;\n\n // Draw background container with rounded corners\n ctx.beginPath();\n ctx.roundRect(containerX, containerY, containerWidth, containerHeight, borderRadius);\n ctx.fillStyle = (this.config.accessibilityEnabled && this.config.highContrastMode) ? '#000000' : '#1e1e1e';\n ctx.fill();\n\n // Draw border\n ctx.strokeStyle = '#3a3a3a';\n ctx.lineWidth = 1;\n ctx.stroke();\n\n // Clip to container bounds to prevent text spill\n ctx.beginPath();\n ctx.roundRect(containerX, containerY, containerWidth, containerHeight, borderRadius);\n ctx.clip();\n\n // Use advanced markdown rendering instead of simple text\n this.drawMarkdown(ctx, textValue, canvasX + 4, canvasY + 4, widgetWidth - 12, widgetHeight - 8, fontSize);\n\n ctx.restore();\n }\n }\n }\n\n widgetY += WIDGET_HEIGHT + WIDGET_MARGIN;\n }\n }\n }\n\n /**\n * Draw image and video previews natively on the offscreen canvas.\n * This renders preview content that would otherwise cause errors during Virtual Zoom,\n * because ComfyUI's ImagePreviewWidget fails when the canvas scale is manipulated.\n * \n * Handles:\n * - node.imgs[] array (standard ComfyUI SaveImage/PreviewImage nodes)\n * - VHS-style DOM widget previews (video/image elements)\n * \n * @param sourceX - Source X position in backing pixels\n * @param sourceY - Source Y position in backing pixels\n * @param sourceWidth - Source width in backing pixels\n * @param sourceHeight - Source height in backing pixels\n * @param renderSize - Output render size in pixels\n * @param scale - Canvas scale used during capture\n * @param offset - Canvas offset [x, y] used during capture\n */\n private drawImagePreviewsNatively(\n sourceX: number,\n sourceY: number,\n sourceWidth: number,\n sourceHeight: number,\n renderSize: number,\n scale: number,\n offset: [number, number]\n ): void {\n const graph = app?.graph;\n if (!graph || !graph._nodes || !this.offscreenCtx) return;\n\n const ctx = this.offscreenCtx;\n\n // Calculate coordinate transforms (same as drawWidgetTextNatively)\n const sourceSizeCss = renderSize / this.config.zoomFactor;\n const actualDpr = sourceWidth / sourceSizeCss;\n const captureScale = renderSize / sourceWidth;\n\n // Source region in CSS pixels\n const sourceCssX = sourceX / actualDpr;\n const sourceCssY = sourceY / actualDpr;\n const sourceCssWidth = sourceWidth / actualDpr;\n const sourceCssHeight = sourceHeight / actualDpr;\n\n // LiteGraph constants\n const TITLE_HEIGHT = 30;\n const WIDGET_MARGIN = 4;\n\n for (const node of graph._nodes) {\n if (!node.pos || !node.size) continue;\n if (node.flags?.collapsed) continue;\n\n // Calculate node position in CSS pixels\n const nodeCssX = node.pos[0] * scale + offset[0];\n const nodeCssY = node.pos[1] * scale + offset[1];\n const nodeCssWidth = node.size[0] * scale;\n const nodeCssHeight = node.size[1] * scale;\n\n // Check if node is in captured region\n if (nodeCssX + nodeCssWidth < sourceCssX || nodeCssX > sourceCssX + sourceCssWidth) continue;\n if (nodeCssY + nodeCssHeight < sourceCssY || nodeCssY > sourceCssY + sourceCssHeight) continue;\n\n // Method 1: Handle node.imgs[] array (standard ComfyUI preview)\n if (node.imgs && Array.isArray(node.imgs) && node.imgs.length > 0) {\n this.drawNodeImages(\n ctx, node, nodeCssX, nodeCssY, nodeCssWidth, nodeCssHeight,\n sourceCssX, sourceCssY, actualDpr, captureScale, renderSize, scale\n );\n }\n\n // Method 2: Handle VHS-style DOM widget previews\n if (node.widgets) {\n for (const widget of node.widgets) {\n const widgetName = String(widget.name || '').toLowerCase();\n if (widgetName === 'videopreview' || widgetName === 'audiopreview') {\n this.drawDomWidgetPreview(\n ctx, widget, node, nodeCssX, nodeCssY, nodeCssWidth,\n sourceCssX, sourceCssY, actualDpr, captureScale, renderSize, scale\n );\n }\n }\n }\n }\n }\n\n /**\n * Draw images from node.imgs[] array onto the offscreen canvas.\n * Supports batch images by drawing them in a grid layout.\n */\n private drawNodeImages(\n ctx: CanvasRenderingContext2D,\n node: any,\n nodeCssX: number,\n nodeCssY: number,\n nodeCssWidth: number,\n nodeCssHeight: number,\n sourceCssX: number,\n sourceCssY: number,\n actualDpr: number,\n captureScale: number,\n renderSize: number,\n scale: number\n ): void {\n const TITLE_HEIGHT = 30;\n const PADDING = 10;\n const GAP = 4; // Gap between images in grid\n\n // Calculate image area (below title, with padding)\n const imageAreaX = nodeCssX + (PADDING * scale);\n const imageAreaY = nodeCssY + (TITLE_HEIGHT * scale);\n const imageAreaWidth = nodeCssWidth - (PADDING * 2 * scale);\n const imageAreaHeight = nodeCssHeight - (TITLE_HEIGHT * scale) - (PADDING * scale);\n\n if (imageAreaWidth <= 0 || imageAreaHeight <= 0) return;\n\n const imgs = node.imgs.filter((img: any) =>\n img && img instanceof HTMLImageElement && img.complete && img.naturalWidth > 0\n );\n const imgCount = imgs.length;\n\n if (imgCount === 0) return;\n\n // Calculate grid dimensions (match ComfyUI's layout)\n // ComfyUI typically uses a square-ish grid\n const cols = Math.ceil(Math.sqrt(imgCount));\n const rows = Math.ceil(imgCount / cols);\n\n // Calculate cell size with gap\n const cellWidth = (imageAreaWidth - (GAP * scale * (cols - 1))) / cols;\n const cellHeight = (imageAreaHeight - (GAP * scale * (rows - 1))) / rows;\n\n if (cellWidth <= 0 || cellHeight <= 0) return;\n\n // Draw each image in grid position\n for (let i = 0; i < imgCount; i++) {\n const img = imgs[i];\n const col = i % cols;\n const row = Math.floor(i / cols);\n\n // Calculate cell position\n const cellX = imageAreaX + col * (cellWidth + GAP * scale);\n const cellY = imageAreaY + row * (cellHeight + GAP * scale);\n\n try {\n // Calculate aspect-ratio-preserving dimensions within cell\n const imgAspect = img.naturalWidth / img.naturalHeight;\n const cellAspect = cellWidth / cellHeight;\n\n let drawWidth = cellWidth;\n let drawHeight = cellHeight;\n\n if (imgAspect > cellAspect) {\n // Image is wider - fit to width\n drawHeight = drawWidth / imgAspect;\n } else {\n // Image is taller - fit to height\n drawWidth = drawHeight * imgAspect;\n }\n\n // Center the image in the cell\n const drawX = cellX + (cellWidth - drawWidth) / 2;\n const drawY = cellY + (cellHeight - drawHeight) / 2;\n\n // Convert to offscreen canvas coordinates\n const canvasX = (drawX - sourceCssX) * actualDpr * captureScale;\n const canvasY = (drawY - sourceCssY) * actualDpr * captureScale;\n const canvasWidth = drawWidth * actualDpr * captureScale;\n const canvasHeight = drawHeight * actualDpr * captureScale;\n\n // Check if at least partially visible\n if (canvasX + canvasWidth > 0 && canvasX < renderSize &&\n canvasY + canvasHeight > 0 && canvasY < renderSize) {\n\n ctx.save();\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = 'high';\n ctx.drawImage(img, canvasX, canvasY, canvasWidth, canvasHeight);\n ctx.restore();\n }\n } catch (e) {\n // Silently fail for individual images\n }\n }\n }\n\n /**\n * Draw VHS-style DOM widget video/image preview onto the offscreen canvas.\n */\n private drawDomWidgetPreview(\n ctx: CanvasRenderingContext2D,\n widget: any,\n node: any,\n nodeCssX: number,\n nodeCssY: number,\n nodeCssWidth: number,\n sourceCssX: number,\n sourceCssY: number,\n actualDpr: number,\n captureScale: number,\n renderSize: number,\n scale: number\n ): void {\n // VHS widgets have videoEl and imgEl properties\n const videoEl = widget.videoEl as HTMLVideoElement | undefined;\n const imgEl = widget.imgEl as HTMLImageElement | undefined;\n\n // Determine which element to draw\n let sourceElement: HTMLVideoElement | HTMLImageElement | undefined;\n let sourceWidth = 0;\n let sourceHeight = 0;\n\n if (videoEl && !videoEl.hidden && videoEl.videoWidth > 0) {\n sourceElement = videoEl;\n sourceWidth = videoEl.videoWidth;\n sourceHeight = videoEl.videoHeight;\n } else if (imgEl && !imgEl.hidden && imgEl.naturalWidth > 0) {\n sourceElement = imgEl;\n sourceWidth = imgEl.naturalWidth;\n sourceHeight = imgEl.naturalHeight;\n }\n\n if (!sourceElement || sourceWidth === 0 || sourceHeight === 0) return;\n\n try {\n // Widget positioning - VHS uses computedHeight and last_y\n const widgetY = (widget as any).last_y ?? 30;\n const widgetHeight = widget.computedHeight ?? 100;\n\n const widgetCssY = nodeCssY + (widgetY * scale);\n const widgetCssWidth = nodeCssWidth - 20 * scale; // VHS padding\n const widgetCssHeight = widgetHeight * scale;\n\n if (widgetCssWidth <= 0 || widgetCssHeight <= 0) return;\n\n // Calculate aspect-ratio-preserving dimensions\n const srcAspect = sourceWidth / sourceHeight;\n const areaAspect = widgetCssWidth / widgetCssHeight;\n\n let drawWidth = widgetCssWidth;\n let drawHeight = widgetCssHeight;\n\n if (srcAspect > areaAspect) {\n drawHeight = drawWidth / srcAspect;\n } else {\n drawWidth = drawHeight * srcAspect;\n }\n\n // Center in widget area\n const drawX = nodeCssX + 10 * scale + (widgetCssWidth - drawWidth) / 2;\n const drawY = widgetCssY + (widgetCssHeight - drawHeight) / 2;\n\n // Convert to offscreen canvas coordinates\n const canvasX = (drawX - sourceCssX) * actualDpr * captureScale;\n const canvasY = (drawY - sourceCssY) * actualDpr * captureScale;\n const canvasWidth = drawWidth * actualDpr * captureScale;\n const canvasHeight = drawHeight * actualDpr * captureScale;\n\n // Check if at least partially visible\n if (canvasX + canvasWidth > 0 && canvasX < renderSize &&\n canvasY + canvasHeight > 0 && canvasY < renderSize) {\n\n ctx.save();\n ctx.imageSmoothingEnabled = true;\n ctx.imageSmoothingQuality = 'high';\n ctx.drawImage(sourceElement, canvasX, canvasY, canvasWidth, canvasHeight);\n ctx.restore();\n }\n } catch (e) {\n // Silently fail for individual previews\n }\n }\n\n /**\n * Render markdown text onto the context.\n * Supports basic headers (#) and lists (-).\n */\n private drawMarkdown(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n maxWidth: number,\n maxHeight: number,\n fontSize: number\n ): void {\n const lines = text.split('\\n');\n const lineHeight = fontSize * 1.4;\n let currentY = y;\n const maxY = y + maxHeight;\n\n ctx.textBaseline = 'top';\n\n // High Contrast Mode Color Selection\n let textColor = '#e0e0e0';\n if (this.config.accessibilityEnabled && this.config.highContrastMode) {\n textColor = '#ffffff'; // Pure white for high contrast\n }\n ctx.fillStyle = textColor;\n\n // Word wrap helper\n const wrapText = (text: string, maxWidth: number, font: string): string[] => {\n ctx.font = font;\n const words = text.split(/(\\s+)/);\n const wrappedLines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n const testLine = currentLine + word;\n const testWidth = ctx.measureText(testLine).width;\n if (testWidth > maxWidth && currentLine.length > 0) {\n wrappedLines.push(currentLine.trimEnd());\n currentLine = word.trimStart();\n } else {\n currentLine = testLine;\n }\n }\n if (currentLine.length > 0) wrappedLines.push(currentLine.trimEnd());\n return wrappedLines;\n };\n\n for (const line of lines) {\n if (currentY >= maxY) break;\n\n let renderText = line;\n const fontWeight = (this.config.accessibilityEnabled && this.config.boldTextEnabled) ? '700' : '500';\n const headerWeight = (this.config.accessibilityEnabled && this.config.boldTextEnabled) ? '900' : '700';\n\n let currentFont = `${fontWeight} ${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif`;\n let indent = 0;\n let color = textColor;\n\n // 1. Headers\n if (line.startsWith('#')) {\n const level = line.match(/^#+/)?.[0].length || 0;\n renderText = line.substring(level).trim();\n const headerScale = Math.max(1.1, 1.8 - (level * 0.15)); // H1=1.8x, H2=1.65x\n const headerSize = fontSize * headerScale;\n currentFont = `${headerWeight} ${headerSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif`;\n // Add a little margin before headers\n currentY += fontSize * 0.5;\n }\n // 2. Lists\n else if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) {\n renderText = '• ' + line.trim().substring(2);\n indent = fontSize; // Indent list items\n }\n\n const wrapped = wrapText(renderText, maxWidth - indent, currentFont);\n\n ctx.font = currentFont;\n ctx.fillStyle = color;\n\n for (const wrap of wrapped) {\n if (currentY >= maxY) break;\n\n // improved accessibility text drawing\n this.drawAccessibleText(ctx, wrap, x + indent, currentY);\n\n // Headers get taller line height\n const currentLineHeight = line.startsWith('#') ? (parseFloat(currentFont.split(' ')[1]) * 1.4) : lineHeight;\n currentY += currentLineHeight;\n }\n }\n }\n\n\n\n /**\n * Render a button widget.\n * Draws a rounded rectangle with the label centered.\n */\n private drawButton(\n ctx: CanvasRenderingContext2D,\n label: string,\n x: number,\n y: number,\n width: number,\n height: number,\n fontSize: number\n ): void {\n const radius = 6;\n\n ctx.save();\n\n // Button background\n ctx.beginPath();\n ctx.roundRect(x, y, width, height, radius);\n ctx.fillStyle = '#222'; // Dark button background\n ctx.fill();\n\n // Button border\n ctx.strokeStyle = '#555';\n ctx.lineWidth = 1;\n ctx.stroke();\n\n // Button label\n ctx.font = `600 ${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n let textColor = '#ccc'; // Light grey text\n if (this.config.accessibilityEnabled && this.config.highContrastMode) {\n textColor = '#ffffff';\n }\n ctx.fillStyle = textColor;\n\n // Center text in button\n const fileUploadLabel = \"choose file to upload\";\n // If it's the long upload label, we might need smaller font or wrapping, \n // but typically buttons are single line.\n // We'll just truncate if too long or let it clip via context clipping if we added any (we didn't yet).\n\n this.drawAccessibleText(ctx, label, x + width / 2, y + height / 2);\n\n ctx.restore();\n }\n\n /**\n * Draw a mini cursor preview at the center of the glass.\n * The cursor is drawn as a classic arrow pointer with a contrasting outline.\n * @param renderSize - The render size of the glass in pixels\n */\n private drawCursorPreview(renderSize: number): void {\n if (!this.offscreenCtx) return;\n const ctx = this.offscreenCtx;\n\n // Cursor size scales with glass size (roughly 8-12% of glass size)\n const cursorSize = Math.max(16, Math.min(32, renderSize * 0.1));\n\n // Center position of the glass\n const centerX = renderSize / 2;\n const centerY = renderSize / 2;\n\n ctx.save();\n\n // Translate to center (cursor tip will be at center)\n ctx.translate(centerX, centerY);\n\n // Scale the cursor shape\n const scale = cursorSize / 24; // Base cursor is 24px\n ctx.scale(scale, scale);\n\n // Classic arrow cursor shape (pointing up-left)\n ctx.beginPath();\n ctx.moveTo(0, 0); // Tip\n ctx.lineTo(0, 17); // Down\n ctx.lineTo(4, 13); // Notch right\n ctx.lineTo(7, 20); // Handle bottom-right\n ctx.lineTo(10, 19); // Handle outer\n ctx.lineTo(7, 12); // Handle top\n ctx.lineTo(12, 12); // Arrow right point\n ctx.closePath();\n\n // Draw white fill\n ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';\n ctx.fill();\n\n // Draw black outline for contrast\n ctx.strokeStyle = 'rgba(0, 0, 0, 0.9)';\n ctx.lineWidth = 1.5;\n ctx.stroke();\n\n ctx.restore();\n }\n\n isAvailable(): boolean {\n return this.offscreenCanvas !== null && this.offscreenCtx !== null;\n }\n\n getCanvas(): HTMLCanvasElement | null {\n return this.offscreenCanvas;\n }\n\n /**\n * Draw text with accessibility enhancements (glow, outline).\n */\n private drawAccessibleText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number): void {\n if (!this.config.accessibilityEnabled) {\n ctx.fillText(text, x, y);\n return;\n }\n\n ctx.save();\n\n // 1. Text Glow\n if (this.config.textGlowEnabled) {\n ctx.shadowBlur = this.config.textGlowIntensity;\n ctx.shadowColor = this.config.textGlowColor;\n // Draw regular text with shadow\n ctx.fillText(text, x, y);\n // Draw again to strengthen if needed, or just let shadow do work\n }\n\n // 2. Text Outline\n if (this.config.textOutlineEnabled) {\n ctx.lineWidth = 3; // Thicker stroke for visibility\n ctx.strokeStyle = this.config.textOutlineColor;\n ctx.lineJoin = 'round';\n ctx.miterLimit = 2;\n ctx.strokeText(text, x, y);\n }\n\n // 3. Main Text Fill (ensure high contrast if enabled)\n if (this.config.highContrastMode) {\n ctx.fillStyle = '#ffffff';\n }\n\n // Remove shadow for main fill to prevent muddying\n ctx.shadowBlur = 0;\n ctx.fillText(text, x, y);\n\n ctx.restore();\n }\n}\n"],"names":["text","maxWidth"],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/PopOutManager.js b/web/magnify-glass/PopOutManager.js index ca4473a..de747f4 100644 --- a/web/magnify-glass/PopOutManager.js +++ b/web/magnify-glass/PopOutManager.js @@ -493,4 +493,3 @@ class PopOutManager { export { PopOutManager }; -//# sourceMappingURL=PopOutManager.js.map diff --git a/web/magnify-glass/PopOutManager.js.map b/web/magnify-glass/PopOutManager.js.map deleted file mode 100644 index 18c35fe..0000000 --- a/web/magnify-glass/PopOutManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"PopOutManager.js","sources":["../../src/magnify-glass/PopOutManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - PopOutManager (TypeScript)\n * \n * Manages pop-out tab functionality for multi-monitor viewing.\n * Uses BroadcastChannel API for efficient cross-tab communication.\n */\n\nimport { BROADCAST_CHANNEL_NAME } from '../shared/constants';\nimport { Logger } from '../shared/logger';\nimport { WidgetSyncManager } from '../info-panel/widget-editors/WidgetSyncManager';\n\n/**\n * Message types for BroadcastChannel communication\n */\ninterface PopOutMessage {\n type: 'frame' | 'config' | 'info' | 'close' | 'ping' | 'pong' | 'node-select' | 'nodes-list' | 'request-nodes' | 'zoom-node' | 'widget-edit' | 'button-click';\n data?: string | Partial | PopOutInfo | NodeListData | number | WidgetEditData;\n timestamp?: number;\n}\n\n/**\n * Widget edit command data from pop-out viewer\n */\ninterface WidgetEditData {\n nodeId: number;\n widgetName: string;\n value: unknown;\n}\n\n/**\n * Node list data sent to popout for dropdown\n */\ninterface NodeListData {\n nodes: { id: number; title: string; type: string; order?: number }[];\n sortBy: 'title' | 'execOrder' | 'id';\n}\n\ninterface PopOutConfig {\n glassSize: number;\n borderColor: string;\n borderWidth: number;\n glassShape: string;\n theme?: string;\n}\n\n/**\n * Inspector info data structure for pop-out viewer\n */\ninterface PopOutInfo {\n hoveredNode?: {\n title: string;\n type: string;\n id?: number;\n mode?: string | number;\n pos?: { x: number; y: number } | string;\n size?: { w: number; h: number } | string;\n executionOrder?: number;\n category?: string;\n pythonModule?: string;\n widgets?: any[];\n inputs?: any[];\n outputs?: any[];\n properties?: Record;\n };\n cursor?: {\n canvas?: { x: number; y: number };\n };\n canvas?: {\n scale: number;\n };\n magnifier?: {\n zoomFactor: number;\n };\n media?: {\n tagName: string;\n naturalSize?: string;\n };\n}\n\n/**\n * PopOutManager class.\n * Handles opening, closing, and syncing with an external browser tab.\n */\nexport class PopOutManager {\n private channel: BroadcastChannel | null = null;\n private isOpen: boolean = false;\n private popOutWindow: Window | null = null;\n public onStateChange: ((isOpen: boolean) => void) | null = null;\n public onNodeSelect: ((nodeId: number) => void) | null = null;\n public onWidgetEdit: (() => void) | null = null;\n private lastPongTime: number = 0;\n private pingInterval: number | null = null;\n private viewerUrl: string;\n private currentTheme: string = 'dark'; // Default theme\n\n /** Throttle frame sending to ~30fps */\n private lastFrameTime: number = 0;\n private readonly FRAME_INTERVAL = 33; // ~30fps\n\n /** Connection timeout (ms) */\n private readonly CONNECTION_TIMEOUT = 5000;\n\n constructor() {\n // Get the viewer URL relative to the extension\n // The viewer HTML will be in the web directory\n this.viewerUrl = this.getViewerUrl();\n this.initChannel();\n }\n\n /**\n * Get the URL for the pop-out viewer page.\n */\n private getViewerUrl(): string {\n // Cache-busting version - increment to force refresh\n const version = 'v25';\n\n // Find the extension's base URL from the loaded scripts\n const scripts = document.querySelectorAll('script[src*=\"magnify\"]');\n Logger.debug(`[PopOut] Found ${scripts.length} magnify scripts`);\n\n if (scripts.length > 0) {\n const src = (scripts[0] as HTMLScriptElement).src;\n Logger.debug(`[PopOut] First script src: ${src}`);\n\n // src might be like: /extensions/comfyui-magnifyglass/magnify-glass/MagnifyGlass.js\n // We need to get to the web root: /extensions/comfyui-magnifyglass/\n const urlParts = src.split('/');\n // Find the extension folder (contains 'magnify' in name)\n let extensionIndex = -1;\n for (let i = 0; i < urlParts.length; i++) {\n if (urlParts[i].toLowerCase().includes('magnify')) {\n extensionIndex = i;\n break;\n }\n }\n if (extensionIndex >= 0) {\n const baseUrl = urlParts.slice(0, extensionIndex + 1).join('/');\n const viewerUrl = `${baseUrl}/popout-viewer.html?${version}`;\n Logger.debug(`[PopOut] Using viewer URL: ${viewerUrl}`);\n return viewerUrl;\n }\n }\n // Fallback: assume standard ComfyUI extension path\n const fallbackUrl = `/extensions/comfyui-magnifyglass/popout-viewer.html?${version}`;\n Logger.debug(`[PopOut] Using fallback URL: ${fallbackUrl}`);\n return fallbackUrl;\n }\n\n /**\n * Initialize the BroadcastChannel.\n */\n private initChannel(): void {\n try {\n this.channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);\n this.channel.onmessage = (event: MessageEvent) => {\n this.handleMessage(event.data);\n };\n Logger.debug('[PopOut] BroadcastChannel initialized');\n // Start pinging immediately to detect if viewer is already open (e.g. after reload)\n this.startPing();\n } catch (e) {\n Logger.error('[PopOut] Failed to create BroadcastChannel:', e);\n }\n }\n\n /**\n * Handle incoming messages from the viewer tab.\n */\n private handleMessage(message: PopOutMessage): void {\n switch (message.type) {\n case 'pong':\n this.lastPongTime = Date.now();\n if (!this.isOpen) {\n this.isOpen = true;\n Logger.debug('[PopOut] Viewer tab connected');\n if (this.onStateChange) this.onStateChange(true);\n // Send current theme immediately upon connection\n this.sendConfig({ theme: this.currentTheme });\n }\n break;\n case 'close':\n this.isOpen = false;\n this.popOutWindow = null;\n // Don't stop pinging, we might want to reconnect if user re-opens externally\n // or if we just want to keep checking. \n // However, original logic stopped ping. Let's keep pinging to auto-detect return.\n // this.stopPing(); \n Logger.debug('[PopOut] Viewer tab closed');\n if (this.onStateChange) this.onStateChange(false);\n break;\n case 'request-nodes':\n // Popout is requesting the node list for dropdown\n Logger.debug('[PopOut] Received request-nodes:', message.data);\n this.handleNodeListRequest(message.data as 'title' | 'execOrder' | 'id');\n break;\n case 'node-select':\n // Popout selected a node, notify main window\n if (this.onNodeSelect && typeof message.data === 'number') {\n this.onNodeSelect(message.data);\n }\n break;\n case 'zoom-node':\n // Popout requested to zoom to a node\n if (typeof message.data === 'number') {\n const app = (window as any).app;\n const node = app.graph.getNodeById(message.data);\n if (node && app.canvas) {\n app.canvas.centerOnNode(node);\n }\n }\n break;\n case 'widget-edit':\n // Popout is sending a widget value edit\n this.handleWidgetEdit(message.data as WidgetEditData);\n break;\n case 'button-click':\n // Popout is requesting a button widget callback invocation\n this.handleButtonClick(message.data as { nodeId: number; widgetName: string });\n break;\n }\n }\n\n /**\n * Handle widget edit command from pop-out viewer.\n * Syncs the edit to the actual node widget on the canvas.\n */\n private handleWidgetEdit(editData: WidgetEditData): void {\n if (!editData || typeof editData.nodeId !== 'number' || !editData.widgetName) {\n Logger.warn('[PopOut] Invalid widget edit data:', editData);\n return;\n }\n\n const result = WidgetSyncManager.syncWidgetValue(\n editData.nodeId,\n editData.widgetName,\n editData.value\n );\n\n if (result.success) {\n Logger.debug(`[PopOut] Widget edit synced: ${editData.widgetName} = ${editData.value}`);\n // Notify listeners (InfoPanel) that a widget was edited, to force an immediate refresh\n if (this.onWidgetEdit) {\n this.onWidgetEdit();\n }\n } else {\n Logger.warn(`[PopOut] Widget edit failed: ${result.error}`);\n }\n }\n\n /**\n * Handle button click command from pop-out viewer.\n * Invokes the widget's callback function.\n */\n private handleButtonClick(buttonData: { nodeId: number; widgetName: string }): void {\n if (!buttonData || typeof buttonData.nodeId !== 'number' || !buttonData.widgetName) {\n Logger.warn('[PopOut] Invalid button click data:', buttonData);\n return;\n }\n\n const app = (window as any).app;\n if (!app?.graph) {\n Logger.warn('[PopOut] No app.graph available for button click');\n return;\n }\n\n const node = app.graph.getNodeById(buttonData.nodeId);\n if (!node?.widgets) {\n Logger.warn(`[PopOut] Node ${buttonData.nodeId} not found or has no widgets`);\n return;\n }\n\n const widget = node.widgets.find((w: any) => w.name === buttonData.widgetName);\n if (widget && typeof widget.callback === 'function') {\n try {\n widget.callback(widget.value, app.canvas, node, [0, 0], null);\n Logger.debug(`[PopOut] Button ${buttonData.widgetName} clicked successfully`);\n } catch (error) {\n Logger.warn(`[PopOut] Button callback failed:`, error);\n }\n } else {\n Logger.warn(`[PopOut] Widget ${buttonData.widgetName} not found or has no callback`);\n }\n }\n\n /**\n * Open the pop-out viewer in a new tab.\n */\n open(): void {\n // If window exists and is open, focus it\n if (this.popOutWindow && !this.popOutWindow.closed) {\n this.popOutWindow.focus();\n return;\n }\n\n // Open new tab (or refocus existing if name matches)\n // Use a constant name to ensure singleton behavior across reloads\n this.popOutWindow = window.open(this.viewerUrl, 'MagnifyGlassPopout');\n\n if (!this.popOutWindow) {\n Logger.error('[PopOut] Failed to open new tab - popup may be blocked');\n return;\n }\n\n // Start pinging to detect when viewer is ready\n this.startPing();\n Logger.debug('[PopOut] Opening viewer tab...');\n }\n\n /**\n * Close the pop-out viewer tab.\n */\n close(): void {\n if (!this.isOpen && !this.popOutWindow) return;\n\n // update state immediately for instant UI feedback\n this.isOpen = false;\n if (this.onStateChange) this.onStateChange(false);\n this.stopPing();\n\n this.sendMessage({ type: 'close' });\n\n if (this.popOutWindow) {\n this.popOutWindow.close();\n this.popOutWindow = null;\n }\n\n Logger.debug('[PopOut] Sent close message to viewer');\n }\n\n /**\n * Toggle the pop-out state.\n */\n toggle(): void {\n if (this.isOpen) {\n this.close();\n } else {\n this.open();\n }\n }\n\n /**\n * Check if the pop-out tab is currently open.\n */\n isPopOutOpen(): boolean {\n return this.isOpen;\n }\n\n /**\n * Send a frame to the pop-out viewer.\n * Throttled to ~30fps for performance.\n * @param canvas - The source canvas to send\n */\n sendFrame(canvas: HTMLCanvasElement): void {\n if (!this.isOpen || !this.channel) return;\n\n // Throttle frame rate\n const now = Date.now();\n if (now - this.lastFrameTime < this.FRAME_INTERVAL) {\n return;\n }\n this.lastFrameTime = now;\n\n try {\n // Convert canvas to data URL (efficient for small canvases)\n const dataUrl = canvas.toDataURL('image/png');\n this.sendMessage({\n type: 'frame',\n data: dataUrl,\n timestamp: now\n });\n } catch (e) {\n Logger.error('[PopOut] Failed to send frame:', e);\n }\n }\n\n /**\n * Send configuration to the pop-out viewer.\n */\n /**\n * Send configuration to the pop-out viewer.\n */\n sendConfig(config: Partial): void {\n if (!this.channel) return;\n\n // Merge with current theme if not provided\n const finalConfig = {\n theme: this.currentTheme,\n ...config\n };\n\n // Update local theme cache if provided in config\n if (config.theme) {\n this.currentTheme = config.theme;\n }\n\n this.sendMessage({\n type: 'config',\n data: finalConfig\n });\n }\n\n /**\n * Update the viewer theme.\n * @param theme - Theme name (e.g. 'dark', 'light')\n */\n updateTheme(theme: string): void {\n if (this.currentTheme === theme) return;\n\n this.currentTheme = theme;\n this.sendConfig({ theme });\n Logger.debug(`[PopOut] Theme updated to: ${theme}`);\n }\n\n /**\n * Send inspector info to the pop-out viewer.\n * @param info - Inspector panel information\n */\n sendInfo(info: PopOutInfo | null): void {\n if (!this.isOpen || !this.channel) return;\n\n // Sanitize the info object to remove non-serializable data\n const sanitizedInfo = this.sanitizeInfo(info);\n\n this.sendMessage({\n type: 'info',\n data: sanitizedInfo || undefined\n });\n }\n\n /**\n * Handle node list request from popout viewer.\n * Fetches nodes from canvas and sends them to the popout.\n */\n private handleNodeListRequest(sortBy: 'title' | 'execOrder' | 'id'): void {\n if (!this.channel) return;\n\n try {\n // Access nodes from ComfyUI's app object\n const app = (window as any).app;\n const nodes = app?.graph?._nodes || [];\n\n let nodeList: { id: number; title: string; type: string; order?: number }[];\n\n if (sortBy === 'execOrder') {\n nodeList = nodes\n .map((n: any) => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown',\n order: n.order ?? -1\n }))\n .filter((n: any) => n.order >= 0)\n .sort((a: any, b: any) => a.order - b.order);\n } else if (sortBy === 'id') {\n nodeList = nodes\n .map((n: any) => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a: any, b: any) => a.id - b.id);\n } else {\n nodeList = nodes\n .map((n: any) => ({\n id: n.id,\n title: n.title || 'Untitled',\n type: n.type || 'Unknown'\n }))\n .sort((a: any, b: any) => a.title.localeCompare(b.title));\n }\n\n this.sendMessage({\n type: 'nodes-list',\n data: { nodes: nodeList, sortBy }\n });\n\n Logger.debug(`[PopOut] Sent ${nodeList.length} nodes to viewer (sorted by ${sortBy})`);\n } catch (e) {\n Logger.error('[PopOut] Failed to get node list:', e);\n }\n }\n\n /**\n * Sanitize info object for BroadcastChannel transfer.\n * Removes functions, circular references, and non-serializable data.\n * Handles both GatheredInfo and PopOutInfo formats.\n */\n private sanitizeInfo(info: any): PopOutInfo | null {\n if (!info) return null;\n\n try {\n // Extract only the serializable parts we need\n const sanitized: PopOutInfo = {};\n\n // Handle hoveredNode\n if (info.hoveredNode) {\n sanitized.hoveredNode = {\n title: String(info.hoveredNode.title || ''),\n type: String(info.hoveredNode.type || ''),\n id: info.hoveredNode.id,\n mode: info.hoveredNode.mode,\n executionOrder: info.hoveredNode.executionOrder,\n category: info.hoveredNode.category ? String(info.hoveredNode.category) : undefined,\n pythonModule: info.hoveredNode.pythonModule ? String(info.hoveredNode.pythonModule) : undefined,\n // Map formatting or raw values\n pos: info.hoveredNode.position && typeof info.hoveredNode.position === 'object'\n ? { x: info.hoveredNode.position.x, y: info.hoveredNode.position.y }\n : String(info.hoveredNode.position || ''),\n size: info.hoveredNode.size && typeof info.hoveredNode.size === 'object'\n ? { w: info.hoveredNode.size.width, h: info.hoveredNode.size.height }\n : String(info.hoveredNode.size || ''),\n widgets: this.extractSafeData(info.hoveredNode.widgets),\n inputs: this.extractSafeData(info.hoveredNode.inputs),\n outputs: this.extractSafeData(info.hoveredNode.outputs),\n properties: this.sanitizeOptions(info.hoveredNode.properties) || {}\n };\n }\n\n // Handle cursor - map from GatheredInfo format (canvasX/canvasY)\n if (info.cursor) {\n sanitized.cursor = {\n canvas: {\n x: Number(info.cursor.canvasX || info.cursor.canvas?.x || 0),\n y: Number(info.cursor.canvasY || info.cursor.canvas?.y || 0)\n }\n };\n }\n\n // Handle canvas/zoom - map from GatheredInfo (zoom) to canvas.scale\n if (info.zoom !== undefined || info.canvas) {\n sanitized.canvas = {\n scale: Number(info.zoom || info.canvas?.scale || 1)\n };\n }\n\n // Handle magnifier\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass?.config) {\n sanitized.magnifier = {\n zoomFactor: Number(magnifyGlass.config.zoomFactor || 1)\n };\n }\n\n // Handle media - map from GatheredInfo mediaElement\n if (info.mediaElement || info.media) {\n const media = info.mediaElement || info.media;\n sanitized.media = {\n tagName: String(media.tagName || media.type || ''),\n naturalSize: media.naturalWidth && media.naturalHeight\n ? `${media.naturalWidth}×${media.naturalHeight}`\n : media.naturalSize\n };\n }\n\n return sanitized;\n } catch (e) {\n Logger.error('[PopOut] Failed to sanitize info:', e);\n return null;\n }\n }\n\n /**\n * Extract specific safe fields from node lists (widgets, inputs, outputs).\n * Removes functions like onFloatValueChange which cause cloning errors.\n */\n private extractSafeData(list: any[] | undefined): any[] {\n if (!list || !Array.isArray(list)) return [];\n return list.map(item => {\n if (!item || typeof item !== 'object') return {};\n\n // Only extract known safe identifiers and values\n const safeItem: any = {\n name: item.name,\n type: item.type,\n label: item.label\n };\n\n // Safely copy value if it's not a function\n if (item.value !== undefined && typeof item.value !== 'function') {\n safeItem.value = item.value;\n }\n\n // Handle widget options - preserve arrays for combo dropdowns\n if (item.options !== undefined) {\n if (Array.isArray(item.options)) {\n // For combo widgets, options is an array of values - keep it as is\n safeItem.options = item.options.filter(\n (opt: any) => typeof opt !== 'function'\n );\n } else if (typeof item.options === 'object' && item.options !== null) {\n // Check for options.values (ComfyUI nested format for combo widgets)\n if (Array.isArray(item.options.values)) {\n safeItem.options = item.options.values.filter(\n (opt: any) => typeof opt !== 'function'\n );\n } else {\n // For other widgets, options is an object with config (min, max, step, etc)\n safeItem.options = this.sanitizeOptions(item.options);\n }\n }\n }\n\n return safeItem;\n });\n }\n\n /**\n * Shallow copy object removing functions.\n */\n private sanitizeOptions(options: any): any {\n if (!options || typeof options !== 'object') return {};\n const clean: any = {};\n for (const key in options) {\n const val = options[key];\n if (typeof val !== 'function' && !key.startsWith('_')) {\n // Determine if value is simple enough to copy\n if (typeof val === 'object' && val !== null) {\n try {\n // Simple check if it's a plain object/array without cycles\n JSON.stringify(val);\n clean[key] = val;\n } catch (e) {\n // Skip complex objects\n }\n } else {\n clean[key] = val;\n }\n }\n }\n return clean;\n }\n\n /**\n * Send a message through the BroadcastChannel.\n */\n private sendMessage(message: PopOutMessage): void {\n if (!this.channel) return;\n\n try {\n this.channel.postMessage(message);\n } catch (e) {\n Logger.error('[PopOut] Failed to send message:', e);\n }\n }\n\n /**\n * Start pinging to check connection.\n */\n private startPing(): void {\n // Define ping function\n const ping = () => {\n if (this.channel) {\n this.channel.postMessage({ type: 'ping', timestamp: Date.now() });\n }\n\n // Check for connection timeout\n if (this.isOpen && Date.now() - this.lastPongTime > this.CONNECTION_TIMEOUT) {\n Logger.debug('[PopOut] Connection timeout, marking as closed');\n this.isOpen = false;\n this.stopPing(); // Stop pinging if timeout occurs (assume truly gone)\n // Actually, if we want auto-reconnect, we should NOT stop pinging on timeout?\n // But timeout implies \"It was open, but stopped responding\".\n // If user closed it, we got 'close' message.\n // If it crashed, we get timeout.\n // If we stop pinging, we won't detect if it comes back?\n // But usually we want to stop to save resources.\n // Let's stick to original behavior for timeout: stop pinging.\n // But for 'close' message: keep pinging (handled in handleMessage).\n if (this.onStateChange) this.onStateChange(false);\n }\n };\n\n // Always send an immediate ping when startPing is called, \n // to ensure responsiveness (e.g. when clicking Open button).\n ping();\n\n // If interval is already running, we don't need to start another one\n if (this.pingInterval) return;\n\n // Then ping every second\n this.pingInterval = window.setInterval(ping, 1000);\n }\n\n /**\n * Stop pinging.\n */\n private stopPing(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n /**\n * Cleanup resources.\n */\n cleanup(): void {\n this.close();\n this.stopPing();\n if (this.channel) {\n this.channel.close();\n this.channel = null;\n }\n Logger.debug('[PopOut] Cleaned up');\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/UiManager.js b/web/magnify-glass/UiManager.js index 52b857e..815d851 100644 --- a/web/magnify-glass/UiManager.js +++ b/web/magnify-glass/UiManager.js @@ -375,4 +375,3 @@ class UiManager { export { UiManager }; -//# sourceMappingURL=UiManager.js.map diff --git a/web/magnify-glass/UiManager.js.map b/web/magnify-glass/UiManager.js.map deleted file mode 100644 index 8309401..0000000 --- a/web/magnify-glass/UiManager.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"UiManager.js","sources":["../../src/magnify-glass/UiManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - UiManager (TypeScript)\n * \n * Handles creation and management of DOM elements for the magnifying glass.\n */\n\nimport { Z_INDEX, DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';\nimport { Icons } from '../shared/icons';\nimport { Logger } from '../shared/logger';\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\n\n/**\n * UI Manager class.\n * Manages all DOM elements for the magnifying glass.\n */\nexport class UiManager {\n config: ConfigManager;\n state: MagnifierState;\n glassDiv: HTMLDivElement | null;\n glassCanvas: HTMLCanvasElement | null;\n htmlOverlayContainer: HTMLDivElement | null;\n onToggle: (() => void) | undefined;\n\n constructor(config: ConfigManager, state: MagnifierState, onToggle?: () => void) {\n this.config = config;\n this.state = state;\n this.glassDiv = null;\n this.glassCanvas = null;\n this.htmlOverlayContainer = null;\n this.onToggle = onToggle;\n }\n\n /**\n * Create all DOM elements for the magnifying glass.\n */\n createElements(): void {\n // Create magnifying glass container\n this.glassDiv = document.createElement(\"div\");\n this.glassDiv.id = \"comfyui-magnify-glass\";\n this.glassDiv.style.cssText = `\n position: absolute;\n box-sizing: border-box;\n width: ${this.config.glassSize}px;\n height: ${this.config.glassSize}px;\n border-radius: ${this.config.glassShape === \"Circle\" ? \"50%\" : \"0px\"};\n border: ${this.config.borderEnabled ? `${this.config.borderWidth}px solid ${this.config.borderColor}` : 'none'};\n overflow: hidden;\n pointer-events: none;\n z-index: ${Z_INDEX.GLASS};\n display: none;\n box-shadow: 0 5px 15px rgba(0,0,0,0.3);\n background-color: rgba(255,255,255,0.1);\n `;\n\n // Create WebGL canvas for the magnifying glass\n this.glassCanvas = document.createElement(\"canvas\");\n this.glassCanvas.width = this.config.glassSize;\n this.glassCanvas.height = this.config.glassSize;\n this.glassCanvas.id = \"comfyui-magnify-canvas\";\n this.glassDiv.appendChild(this.glassCanvas);\n\n // Create HTML overlay container\n this.htmlOverlayContainer = document.createElement(\"div\");\n this.htmlOverlayContainer.id = \"comfyui-magnify-html-overlay\";\n this.htmlOverlayContainer.style.cssText = `\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n pointer-events: none;\n overflow: hidden; \n `;\n this.glassDiv.appendChild(this.htmlOverlayContainer);\n\n document.body.appendChild(this.glassDiv);\n\n // Inject menu button\n this.injectMenuButton();\n }\n\n /**\n * Show the magnifying glass.\n */\n show(): void {\n if (this.glassDiv) {\n this.glassDiv.style.display = 'block';\n }\n }\n\n /**\n * Set the visual visibility of the glass preview (opacity).\n * This allows the tool to remain \"Active\" (tracking mouse) but invisible,\n * so that the Inspector Panel can be used in \"Inspector Only\" mode.\n */\n setPreviewVisibility(visible: boolean): void {\n if (this.glassDiv) {\n this.glassDiv.style.opacity = visible ? '1' : '0';\n }\n }\n\n /**\n * Enable or disable glass drag mode.\n * When enabled, the glass can be dragged to a new position.\n */\n setDragMode(enabled: boolean): void {\n if (!this.glassDiv) return;\n\n if (enabled) {\n // Use 'all-scroll' which shows a 4-way arrow consistently across browsers\n // Firefox shows 'move' as a grab/fist cursor which is confusing\n this.glassDiv.style.cursor = 'all-scroll';\n this.glassDiv.style.pointerEvents = 'auto';\n this.glassDiv.classList.add('drag-mode');\n\n // Add drag handlers\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n\n // Calculate the fixed offset between mouse and glass top-left\n // This maintains the \"grab point\" relative to the glass\n const rect = this.glassDiv!.getBoundingClientRect();\n const grabOffsetX = rect.left - e.clientX;\n const grabOffsetY = rect.top - e.clientY;\n\n // First, set left/top to current position to prevent jump\n this.glassDiv!.style.left = `${rect.left}px`;\n this.glassDiv!.style.top = `${rect.top}px`;\n\n // Then clear opposite positioning props (now safe since left/top are set)\n this.glassDiv!.style.right = 'auto';\n this.glassDiv!.style.bottom = 'auto';\n this.glassDiv!.style.transform = 'none'; // Clear any centering transforms if they exist\n\n const onMouseMove = (moveEvent: MouseEvent) => {\n moveEvent.preventDefault();\n moveEvent.stopPropagation();\n\n // 1:1 Movement: Glass moves exactly with mouse\n // New Pos = Mouse Pos + Initial Grab Offset\n const newLeft = moveEvent.clientX + grabOffsetX;\n const newTop = moveEvent.clientY + grabOffsetY;\n\n if (this.glassDiv) {\n this.glassDiv.style.left = `${newLeft}px`;\n this.glassDiv.style.top = `${newTop}px`;\n }\n };\n\n const onMouseUp = (upEvent: MouseEvent) => {\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n\n // Disable drag mode after drop\n this.state.isDragModeEnabled = false;\n this.setDragMode(false);\n\n // Update control states via info panel\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.uiManager) {\n infoPanel.uiManager.updateControlStates();\n }\n };\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n this.glassDiv.addEventListener('mousedown', onMouseDown);\n (this.glassDiv as any)._dragHandler = onMouseDown;\n } else {\n this.glassDiv.style.cursor = '';\n this.glassDiv.style.pointerEvents = 'none';\n this.glassDiv.classList.remove('drag-mode');\n\n // Remove drag handler\n if ((this.glassDiv as any)._dragHandler) {\n this.glassDiv.removeEventListener('mousedown', (this.glassDiv as any)._dragHandler);\n delete (this.glassDiv as any)._dragHandler;\n }\n }\n }\n\n /**\n * Hide the magnifying glass.\n */\n hide(): void {\n if (this.glassDiv) {\n this.glassDiv.style.display = 'none';\n }\n // Clear HTML overlays when hiding\n if (this.htmlOverlayContainer) {\n this.htmlOverlayContainer.innerHTML = '';\n }\n }\n\n /**\n * Position the glass relative to cursor.\n * Uses dynamic anchoring (Left/Right, Top/Bottom) based on screen quadrant.\n * @param clientX - Client X coordinate\n * @param clientY - Client Y coordinate\n */\n positionGlass(clientX: number, clientY: number): void {\n // Skip positioning if drag mode is active (handled manually in setDragMode)\n if (!this.config.followCursor || !this.glassDiv || this.state.isDragModeEnabled) return;\n\n const glassSize = this.config.glassSize;\n const offsetAmount = DEFAULT_PADDING;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // Determine base coordinates (top-left based)\n // Add manual offsets (from drag operations)\n let targetX: number = this.config.offsetX || 0;\n let targetY: number = this.config.offsetY || 0;\n\n // Calculate standard Top-Left based positions first\n switch (this.config.glassPosition) {\n case \"Top\":\n targetX += clientX - (glassSize / 2);\n targetY += clientY - glassSize - offsetAmount;\n break;\n case \"Bottom\":\n targetX += clientX - (glassSize / 2);\n targetY += clientY + offsetAmount;\n break;\n case \"Left\":\n targetX += clientX - glassSize - offsetAmount;\n targetY += clientY - (glassSize / 2);\n break;\n case \"Right\":\n targetX += clientX + offsetAmount;\n targetY += clientY - (glassSize / 2);\n break;\n case \"Top-Left\":\n targetX += clientX - glassSize - offsetAmount;\n targetY += clientY - glassSize - offsetAmount;\n break;\n case \"Top-Right\":\n targetX += clientX + offsetAmount;\n targetY += clientY - glassSize - offsetAmount;\n break;\n case \"Bottom-Left\":\n targetX += clientX - glassSize - offsetAmount;\n targetY += clientY + offsetAmount;\n break;\n case \"Bottom-Right\":\n targetX += clientX + offsetAmount;\n targetY += clientY + offsetAmount;\n break;\n default:\n targetX += clientX - (glassSize / 2);\n targetY += clientY + offsetAmount;\n break;\n }\n\n // Apply Dynamic Anchoring based on resulting position's center relative to screen\n const glassCenterX = targetX + (glassSize / 2);\n const glassCenterY = targetY + (glassSize / 2);\n\n // Horizontal Anchor\n if (glassCenterX > vw / 2) {\n // Anchor Right\n const rightPos = vw - (targetX + glassSize);\n this.glassDiv.style.right = `${rightPos}px`;\n this.glassDiv.style.left = 'auto';\n } else {\n // Anchor Left\n this.glassDiv.style.left = `${targetX}px`;\n this.glassDiv.style.right = 'auto';\n }\n\n // Vertical Anchor\n if (glassCenterY > vh / 2) {\n // Anchor Bottom\n const bottomPos = vh - (targetY + glassSize);\n this.glassDiv.style.bottom = `${bottomPos}px`;\n this.glassDiv.style.top = 'auto';\n } else {\n // Anchor Top\n this.glassDiv.style.top = `${targetY}px`;\n this.glassDiv.style.bottom = 'auto';\n }\n\n // We run a lightweight clamp to ensure it doesn't bleed offscreen during movement\n // (Though the anchor logic implicitly handles resize, dragging near edges might need clamping)\n this.adjustForBoundaries();\n }\n\n /**\n * Adjust glass position to stay within viewport boundaries.\n * Respects the current anchor (Left/Right, Top/Bottom).\n */\n adjustForBoundaries(): void {\n if (!this.glassDiv) return;\n\n const glassSize = this.config.glassSize;\n const padding = DEFAULT_PADDING;\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n // Check if anchored Right or Left\n const isAnchoredRight = this.glassDiv.style.right !== 'auto' && this.glassDiv.style.right !== '';\n\n if (isAnchoredRight) {\n let currentRight = parseFloat(this.glassDiv.style.right) || padding;\n // Clamp Right: max(padding, min(current, vw - size - padding))\n // Ensure it doesn't go off left side (vw - size - padding)\n currentRight = Math.max(padding, Math.min(currentRight, vw - glassSize - padding));\n this.glassDiv.style.right = `${currentRight}px`;\n } else {\n // Anchor Left\n let currentLeft = parseFloat(this.glassDiv.style.left) || padding;\n currentLeft = Math.max(padding, Math.min(currentLeft, vw - glassSize - padding));\n this.glassDiv.style.left = `${currentLeft}px`;\n }\n\n // Check if anchored Bottom or Top\n const isAnchoredBottom = this.glassDiv.style.bottom !== 'auto' && this.glassDiv.style.bottom !== '';\n\n if (isAnchoredBottom) {\n let currentBottom = parseFloat(this.glassDiv.style.bottom) || padding;\n currentBottom = Math.max(padding, Math.min(currentBottom, vh - glassSize - padding));\n this.glassDiv.style.bottom = `${currentBottom}px`;\n } else {\n // Anchor Top\n let currentTop = parseFloat(this.glassDiv.style.top) || padding;\n currentTop = Math.max(padding, Math.min(currentTop, vh - glassSize - padding));\n this.glassDiv.style.top = `${currentTop}px`;\n }\n }\n\n /**\n * Update position safely on resize.\n * Since we use dynamic anchoring in positionGlass, CSS handles most resize cases.\n * This method ensures we verify boundaries (clamping) and fix anchors if we cross thresholds drastically.\n */\n updateResponsivePosition(): void {\n if (!this.glassDiv) return;\n // Re-run boundary adjustments to clamp if resize pushed us offscreen\n this.adjustForBoundaries();\n }\n\n /**\n * Apply current config to UI elements.\n */\n applyStyles(): void {\n if (this.glassDiv) {\n this.glassDiv.style.width = `${this.config.glassSize}px`;\n this.glassDiv.style.height = `${this.config.glassSize}px`;\n this.glassDiv.style.border = this.config.borderEnabled\n ? `${this.config.borderWidth}px solid ${this.config.borderColor}`\n : 'none';\n\n // Reset clip-path and set default border-radius\n this.glassDiv.style.clipPath = 'none';\n this.glassDiv.style.borderRadius = '0px';\n\n switch (this.config.glassShape) {\n case \"Circle\":\n this.glassDiv.style.borderRadius = \"50%\";\n break;\n case \"Square\":\n // borderRadius is already '0px'\n break;\n case \"Rounded Square\":\n this.glassDiv.style.borderRadius = \"20%\";\n break;\n default:\n this.glassDiv.style.borderRadius = \"50%\";\n break;\n }\n\n // Reduce Motion\n if (this.config.reduceMotion) {\n this.glassDiv.style.transition = 'none !important';\n this.glassDiv.style.animation = 'none !important';\n // Also set property directly just in case !important doesn't stick in inline style without special handling\n this.glassDiv.style.setProperty('transition', 'none', 'important');\n this.glassDiv.style.setProperty('animation', 'none', 'important');\n } else {\n this.glassDiv.style.transition = '';\n this.glassDiv.style.animation = '';\n this.glassDiv.style.removeProperty('transition');\n this.glassDiv.style.removeProperty('animation');\n }\n }\n\n if (this.glassCanvas) {\n this.glassCanvas.width = this.config.glassSize;\n this.glassCanvas.height = this.config.glassSize;\n }\n }\n\n /**\n * Cleanup DOM elements.\n */\n cleanup(): void {\n if (this.glassDiv) this.glassDiv.remove();\n // Remove menu button if needed (though hard to track references here without saving it)\n const btn = document.querySelector('.magnify-toggle-btn');\n if (btn) btn.remove();\n }\n\n /**\n * Inject a quick toggle button into the ComfyUI menu.\n */\n injectMenuButton(): void {\n // Stop after 30 seconds\n const stopTime = Date.now() + 30000;\n\n // Define check function\n const attemptInjection = () => {\n // Use data-testid selectors to find the specific buttons in the bottom toolbar\n // These are the official ComfyUI frontend identifiers\n const minimapBtn = document.querySelector('button[data-testid=\"toggle-minimap-button\"]') as HTMLButtonElement;\n const linkVisibilityBtn = document.querySelector('button[data-testid=\"toggle-link-visibility-button\"]') as HTMLButtonElement;\n\n // We need at least the minimap button to inject after it\n if (!minimapBtn || !minimapBtn.parentElement) {\n return false;\n }\n\n // Check if already injected\n if (minimapBtn.parentElement.querySelector('.magnify-toggle-btn')) {\n return true;\n }\n\n Logger.debug('Found minimap button in bottom toolbar, injecting magnify glass toggle');\n\n const btn = document.createElement('button');\n // Copy all classes from minimap button to match theme/library\n btn.className = minimapBtn.className + ' magnify-toggle-btn';\n\n // Copy key styles that might define shape/size\n const computed = window.getComputedStyle(minimapBtn);\n btn.style.height = computed.height;\n btn.style.minHeight = computed.minHeight;\n btn.style.width = computed.width; // Match width for consistency\n\n btn.title = \"Toggle Magnify Glass (X)\";\n btn.setAttribute('aria-label', \"Toggle Magnify Glass\");\n btn.setAttribute('aria-pressed', 'false');\n btn.setAttribute('data-testid', 'toggle-magnify-glass-button');\n btn.innerHTML = Icons.magnifyGlass;\n\n // Ensure specific display props\n btn.style.display = \"inline-flex\";\n btn.style.alignItems = \"center\";\n btn.style.justifyContent = \"center\";\n btn.style.padding = \"0\";\n btn.style.cursor = \"pointer\";\n\n // Copy border radius if available\n if (minimapBtn.style.borderRadius) {\n btn.style.borderRadius = minimapBtn.style.borderRadius;\n } else {\n btn.style.borderRadius = computed.borderRadius;\n }\n\n // Add active state tracking\n btn.addEventListener('click', () => {\n if (this.onToggle) {\n this.onToggle();\n // Toggle active states to match ComfyUI's button styling\n const isActive = btn.classList.toggle('active');\n btn.classList.toggle('p-highlight'); // PrimeVue\n btn.classList.toggle('selected');\n btn.setAttribute('aria-pressed', String(isActive));\n }\n });\n\n // Insert AFTER the minimap button (between minimap and link visibility)\n if (linkVisibilityBtn && minimapBtn.parentElement === linkVisibilityBtn.parentElement) {\n // Insert before link visibility button (which puts it after minimap)\n minimapBtn.parentElement.insertBefore(btn, linkVisibilityBtn);\n } else if (minimapBtn.nextSibling) {\n // Fallback: insert after minimap button\n minimapBtn.parentElement.insertBefore(btn, minimapBtn.nextSibling);\n } else {\n minimapBtn.parentElement.appendChild(btn);\n }\n\n Logger.debug('Menu toggle button injected successfully between minimap and link visibility');\n return true;\n };\n\n // Run immediately first\n if (attemptInjection()) return;\n\n // Then poll rapidly\n const checkForMenu = setInterval(() => {\n if (Date.now() > stopTime) {\n console.warn(\"[MagnifyGlass] Menu injection timed out. Could not find toggle-minimap-button\");\n clearInterval(checkForMenu);\n return;\n }\n\n if (attemptInjection()) {\n clearInterval(checkForMenu);\n }\n }, 100);\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/WebGLRenderer.js b/web/magnify-glass/WebGLRenderer.js index bb66e65..8702e7a 100644 --- a/web/magnify-glass/WebGLRenderer.js +++ b/web/magnify-glass/WebGLRenderer.js @@ -337,4 +337,3 @@ class WebGLRenderer { export { WebGLRenderer }; -//# sourceMappingURL=WebGLRenderer.js.map diff --git a/web/magnify-glass/WebGLRenderer.js.map b/web/magnify-glass/WebGLRenderer.js.map deleted file mode 100644 index 104767f..0000000 --- a/web/magnify-glass/WebGLRenderer.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"WebGLRenderer.js","sources":["../../src/magnify-glass/WebGLRenderer.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - WebGLRenderer (TypeScript)\n * \n * Handles WebGL-based rendering of the magnified view.\n */\n\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\nimport type { UiManager } from './UiManager';\nimport { Logger } from '../shared/logger';\n\ninterface UniformLocations {\n sourceTexture: WebGLUniformLocation | null;\n textureOffset: WebGLUniformLocation | null;\n textureRepeat: WebGLUniformLocation | null;\n glassSize: WebGLUniformLocation | null;\n}\n\ninterface AttributeLocations {\n position: number;\n texCoord: number;\n}\n\n/**\n * WebGL Renderer class.\n * Provides efficient WebGL-based magnification rendering.\n */\nexport class WebGLRenderer {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n\n gl: WebGLRenderingContext | null;\n program: WebGLProgram | null;\n texture: WebGLTexture | null;\n positionBuffer: WebGLBuffer | null;\n texCoordBuffer: WebGLBuffer | null;\n uniformLocations: UniformLocations | null;\n attributeLocations: AttributeLocations | null;\n currentFilteringMode: number | null;\n\n // Dirty flag optimization\n lastCanvasVersion: number;\n lastSourceX: number;\n lastSourceY: number;\n lastSourceWidth: number;\n lastSourceHeight: number;\n textureUploadSkipCount: number;\n\n vertexShaderSource: string;\n fragmentShaderSource: string;\n\n constructor(config: ConfigManager, state: MagnifierState, ui: UiManager) {\n this.config = config;\n this.state = state;\n this.ui = ui;\n\n this.gl = null;\n this.program = null;\n this.texture = null;\n this.positionBuffer = null;\n this.texCoordBuffer = null;\n this.uniformLocations = null;\n this.attributeLocations = null;\n this.currentFilteringMode = null;\n\n // Initialize dirty flag tracking\n this.lastCanvasVersion = -1;\n this.lastSourceX = -1;\n this.lastSourceY = -1;\n this.lastSourceWidth = -1;\n this.lastSourceHeight = -1;\n this.textureUploadSkipCount = 0;\n\n this.vertexShaderSource = `\n attribute vec2 a_position;\n attribute vec2 a_texCoord;\n varying vec2 v_texCoord;\n void main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texCoord = a_texCoord;\n }\n `;\n\n this.fragmentShaderSource = `\n precision mediump float;\n varying vec2 v_texCoord;\n uniform sampler2D u_sourceTexture;\n uniform vec2 u_textureOffset;\n uniform vec2 u_textureRepeat;\n uniform float u_glassSize;\n\n void main() {\n vec2 sampleCoord = u_textureOffset + v_texCoord * u_textureRepeat;\n vec4 color = texture2D(u_sourceTexture, sampleCoord);\n gl_FragColor = color;\n }\n `;\n\n this.initialize();\n }\n\n /**\n * Initialize WebGL context and resources.\n */\n initialize(): void {\n // Get WebGL context\n if (!this.ui.glassCanvas) return;\n\n this.gl = this.ui.glassCanvas.getContext(\"webgl\", { preserveDrawingBuffer: true });\n if (!this.gl) {\n // Try webgl2 as fallback\n this.gl = this.ui.glassCanvas.getContext(\"webgl2\", { preserveDrawingBuffer: true }) as WebGLRenderingContext | null;\n }\n\n if (!this.gl) {\n const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');\n const browserInstructions = isFirefox\n ? \"Firefox: Go to about:config, search for 'webgl.disabled' and set it to 'false', then restart Firefox.\"\n : \"Check your browser settings to ensure WebGL is enabled.\";\n\n console.error(\"ComfyUI Magnifying Glass ERROR: WebGL not supported or context creation failed.\");\n console.error(`To fix: ${browserInstructions}`);\n\n // Show user notification\n this.showWebGLError(browserInstructions);\n return;\n }\n\n // Create shader program\n this.program = this.createShaderProgram(this.gl, this.vertexShaderSource, this.fragmentShaderSource);\n if (!this.program) return;\n\n // Create position buffer\n this.positionBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n const positions = [-1, -1, 1, -1, -1, 1, 1, 1];\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);\n\n // Create texture coordinate buffer\n this.texCoordBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n const texCoords = [0, 1, 1, 1, 0, 0, 1, 0]; // Flipped Y for texCoords\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(texCoords), this.gl.STATIC_DRAW);\n\n // Create texture\n this.texture = this.gl.createTexture();\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);\n this.updateTextureFiltering(this.config.textureFiltering);\n\n // Get uniform and attribute locations\n this.uniformLocations = {\n sourceTexture: this.gl.getUniformLocation(this.program, \"u_sourceTexture\"),\n textureOffset: this.gl.getUniformLocation(this.program, \"u_textureOffset\"),\n textureRepeat: this.gl.getUniformLocation(this.program, \"u_textureRepeat\"),\n glassSize: this.gl.getUniformLocation(this.program, \"u_glassSize\"),\n };\n\n this.attributeLocations = {\n position: this.gl.getAttribLocation(this.program, \"a_position\"),\n texCoord: this.gl.getAttribLocation(this.program, \"a_texCoord\"),\n };\n }\n\n /**\n * Update texture filtering mode.\n * @param filteringModeString - \"Linear\" or \"Nearest\"\n */\n updateTextureFiltering(filteringModeString: string): void {\n if (!this.gl || !this.texture) return;\n\n let glFilterMode: number;\n if (filteringModeString === \"Nearest\") {\n glFilterMode = this.gl.NEAREST;\n } else {\n glFilterMode = this.gl.LINEAR;\n }\n\n if (this.currentFilteringMode === glFilterMode) return;\n\n this.currentFilteringMode = glFilterMode;\n\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, glFilterMode);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, glFilterMode);\n this.gl.bindTexture(this.gl.TEXTURE_2D, null);\n }\n\n /**\n * Check if renderer is valid and ready.\n * @returns Boolean indicating if WebGL context and program are ready\n */\n isValid(): boolean {\n return this.gl !== null && this.program !== null;\n }\n\n /**\n * Create a WebGL shader program.\n * @param gl - WebGL context\n * @param vsSource - Vertex shader source\n * @param fsSource - Fragment shader source\n * @returns WebGL program or null on failure\n */\n createShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string): WebGLProgram | null {\n const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n if (!vertexShader) return null;\n\n gl.shaderSource(vertexShader, vsSource);\n gl.compileShader(vertexShader);\n if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Vertex shader compilation error:', gl.getShaderInfoLog(vertexShader));\n gl.deleteShader(vertexShader);\n return null;\n }\n\n const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n if (!fragmentShader) {\n gl.deleteShader(vertexShader);\n return null;\n }\n\n gl.shaderSource(fragmentShader, fsSource);\n gl.compileShader(fragmentShader);\n if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Fragment shader compilation error:', gl.getShaderInfoLog(fragmentShader));\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n const program = gl.createProgram();\n if (!program) {\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Shader program linking error:', gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n return program;\n }\n\n /**\n * Render the magnified view.\n * @param sourceCanvas - The source canvas to sample from\n */\n render(sourceCanvas: HTMLCanvasElement): void {\n if (!this.gl || !this.program || !this.texture || !this.uniformLocations || !this.attributeLocations) return;\n\n // Check if we need to update the texture\n // We track: canvas version (via LiteGraph's change counter), source coordinates, and source dimensions\n const app = (window as any).app;\n const currentCanvasVersion = app?.graph?.change_counter ?? -1;\n\n const sourceChanged =\n this.lastSourceX !== this.state.sourceX ||\n this.lastSourceY !== this.state.sourceY ||\n this.lastSourceWidth !== this.state.sourceWidth ||\n this.lastSourceHeight !== this.state.sourceHeight;\n\n const canvasChanged = currentCanvasVersion !== this.lastCanvasVersion;\n\n // Only upload texture if something changed\n const needsTextureUpdate = canvasChanged || sourceChanged || this.lastCanvasVersion === -1;\n\n // Bind texture\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n\n if (needsTextureUpdate) {\n try {\n this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceCanvas);\n\n // Update tracking variables\n this.lastCanvasVersion = currentCanvasVersion;\n this.lastSourceX = this.state.sourceX;\n this.lastSourceY = this.state.sourceY;\n this.lastSourceWidth = this.state.sourceWidth;\n this.lastSourceHeight = this.state.sourceHeight;\n\n // Reset skip counter\n this.textureUploadSkipCount = 0;\n } catch (e) {\n console.error(\"ComfyUI Magnifying Glass ERROR: Error in texImage2D:\", e);\n return;\n }\n } else {\n // Skip texture upload - just increment counter\n this.textureUploadSkipCount++;\n }\n\n // Calculate normalized texture coordinates (UV space: 0-1)\n // Check if this is the offscreen canvas (its size matches glassSize)\n const isOffscreenCanvas = sourceCanvas.width === this.config.glassSize &&\n sourceCanvas.height === this.config.glassSize;\n\n let uvX: number, uvY: number, uvWidth: number, uvHeight: number;\n\n if (isOffscreenCanvas) {\n // Offscreen canvas already contains the correctly rendered region\n // Use full texture\n uvX = 0;\n uvY = 0;\n uvWidth = 1;\n uvHeight = 1;\n } else {\n // Main LiteGraph canvas - calculate subrect\n uvX = this.state.sourceX / sourceCanvas.width;\n uvY = this.state.sourceY / sourceCanvas.height;\n uvWidth = this.state.sourceWidth / sourceCanvas.width;\n uvHeight = this.state.sourceHeight / sourceCanvas.height;\n }\n\n // Set up rendering\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n this.gl.clearColor(0.0, 0.0, 0.0, 0.0);\n this.gl.clear(this.gl.COLOR_BUFFER_BIT);\n\n this.gl.useProgram(this.program);\n\n // Set uniforms\n this.gl.activeTexture(this.gl.TEXTURE0);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.uniform1i(this.uniformLocations.sourceTexture, 0);\n this.gl.uniform2f(this.uniformLocations.textureOffset, uvX, uvY);\n this.gl.uniform2f(this.uniformLocations.textureRepeat, uvWidth, uvHeight);\n this.gl.uniform1f(this.uniformLocations.glassSize, this.config.glassSize);\n\n // Set attributes\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.position, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.position);\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.texCoord, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.texCoord);\n\n // Draw\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);\n }\n\n /**\n * Check for WebGL errors.\n * @param label - Debug label\n * @returns True if error occurred\n */\n checkWebGLError(label: string): boolean {\n if (!this.gl) return false;\n\n const err = this.gl.getError();\n if (err !== this.gl.NO_ERROR) {\n console.error(`ComfyUI Magnifying Glass ERROR: WebGL Error (${label}):`, err);\n return true;\n }\n return false;\n }\n\n /**\n * Update viewport when glass size changes.\n */\n updateViewport(): void {\n if (this.gl) {\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n }\n }\n\n /**\n * Show WebGL error notification to user.\n * @param instructions - Browser-specific instructions to fix\n */\n showWebGLError(instructions: string): void {\n // Create a toast notification\n const toast = document.createElement('div');\n toast.id = 'magnifyglass-webgl-error';\n toast.style.cssText = `\n position: fixed;\n bottom: 20px;\n right: 20px;\n max-width: 400px;\n padding: 16px 20px;\n background: linear-gradient(135deg, #ff4444 0%, #cc3333 100%);\n color: white;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n z-index: 100000;\n animation: slideIn 0.3s ease-out;\n `;\n\n const container = document.createElement('div');\n container.style.cssText = \"display: flex; align-items: flex-start; gap: 12px;\";\n\n const icon = document.createElement('span');\n icon.style.fontSize = \"24px\";\n icon.textContent = \"⚠️\";\n container.appendChild(icon);\n\n const content = document.createElement('div');\n\n const title = document.createElement('strong');\n title.style.cssText = \"display: block; margin-bottom: 6px;\";\n title.textContent = \"MagnifyGlass: WebGL Disabled\";\n content.appendChild(title);\n\n const message = document.createElement('span');\n message.style.opacity = \"0.9\";\n message.textContent = instructions;\n content.appendChild(message);\n\n const btn = document.createElement('button');\n btn.textContent = \"Dismiss\";\n btn.style.cssText = `display: block; margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2);\n border: 1px solid rgba(255,255,255,0.3); border-radius: 4px; color: white; \n cursor: pointer; font-size: 12px;`;\n btn.addEventListener('click', () => toast.remove());\n content.appendChild(btn);\n\n container.appendChild(content);\n toast.appendChild(container);\n\n // Add animation keyframes\n if (!document.getElementById('magnifyglass-toast-style')) {\n const style = document.createElement('style');\n style.id = 'magnifyglass-toast-style';\n style.textContent = `\n @keyframes slideIn {\n from { transform: translateX(100%); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n }\n `;\n document.head.appendChild(style);\n }\n\n document.body.appendChild(toast);\n\n // Auto-dismiss after 15 seconds\n setTimeout(() => {\n if (toast.parentElement) {\n toast.style.animation = 'slideIn 0.3s ease-out reverse';\n setTimeout(() => toast.remove(), 300);\n }\n }, 15000);\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify_glass.js b/web/magnify_glass.js index a6099fb..7d140c4 100644 --- a/web/magnify_glass.js +++ b/web/magnify_glass.js @@ -12,4 +12,3 @@ app.registerExtension({ Logger.debug("Magnify Glass extension initialized"); } }); -//# sourceMappingURL=magnify_glass.js.map diff --git a/web/magnify_glass.js.map b/web/magnify_glass.js.map deleted file mode 100644 index 28e6f12..0000000 --- a/web/magnify_glass.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"magnify_glass.js","sources":["../src/magnify_glass.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Glass - Main Entry Point\n * \n * Initializes the Magnify Glass extension.\n */\n\nimport { MagnifyGlass } from './magnify-glass';\nimport { Logger } from './shared/logger';\nimport { initSidebar } from './sidebar';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// Global declaration handled in comfyui.d.ts\n\n// We rely on the global 'app' object provided by ComfyUI\n// Register extension\napp.registerExtension({\n name: \"comfyui.magnify.glass\",\n init() {\n // Initialize the magnify glass\n const magnifyGlass = new MagnifyGlass();\n\n // Expose to window for other extensions (like info panel) to access\n // Must be done BEFORE init() because init() creates UI which accesses this global\n window.comfyUIMagnifyGlass = magnifyGlass;\n\n magnifyGlass.init();\n\n // Initialize sidebar\n initSidebar();\n\n Logger.debug('Magnify Glass extension initialized');\n }\n});\n"],"names":[],"mappings":";;;;AAgBA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,eAAe,IAAI,aAAA;AAIzB,WAAO,sBAAsB;AAE7B,iBAAa,KAAA;AAGb,gBAAA;AAEA,WAAO,MAAM,qCAAqC;AAAA,EACtD;AACJ,CAAC;"} \ No newline at end of file diff --git a/web/magnify_info_panel.js b/web/magnify_info_panel.js index cdccd5d..9970b72 100644 --- a/web/magnify_info_panel.js +++ b/web/magnify_info_panel.js @@ -32,4 +32,3 @@ function initializeInfoPanel() { Logger.error("Error during initialization:", e); } } -//# sourceMappingURL=magnify_info_panel.js.map diff --git a/web/magnify_info_panel.js.map b/web/magnify_info_panel.js.map deleted file mode 100644 index 66bf3d6..0000000 --- a/web/magnify_info_panel.js.map +++ /dev/null @@ -1 +0,0 @@ -{"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.debug('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,MAAM,kCAAkC;AAAA,EACnD,SAAS,GAAG;AACR,WAAO,MAAM,gCAAgC,CAAC;AAAA,EAClD;AACJ;"} \ No newline at end of file diff --git a/web/shared/CustomDropdown.js b/web/shared/CustomDropdown.js index 527a06a..82afddf 100644 --- a/web/shared/CustomDropdown.js +++ b/web/shared/CustomDropdown.js @@ -262,4 +262,3 @@ export { createDropdownTrigger, updateDropdownTriggerValue }; -//# sourceMappingURL=CustomDropdown.js.map diff --git a/web/shared/CustomDropdown.js.map b/web/shared/CustomDropdown.js.map deleted file mode 100644 index 4602356..0000000 --- a/web/shared/CustomDropdown.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"CustomDropdown.js","sources":["../../src/shared/CustomDropdown.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Custom Dropdown\n *\n * A custom dropdown component that stays within viewport bounds.\n * Replaces native select elements for better positioning control.\n */\n\nexport interface CustomDropdownConfig {\n options: string[];\n currentValue: string;\n anchor: HTMLElement;\n onChange: (value: string) => void;\n onClose?: () => void;\n theme?: string;\n}\n\n/**\n * Custom dropdown that positions itself within viewport bounds.\n * Use this instead of native