diff --git a/src/magnify-glass/UiManager.ts b/src/magnify-glass/UiManager.ts index 25db13c..bc9c515 100644 --- a/src/magnify-glass/UiManager.ts +++ b/src/magnify-glass/UiManager.ts @@ -520,11 +520,16 @@ export class UiManager { btn.style.justifyContent = "center"; btn.style.padding = "0 8px"; btn.style.cursor = "pointer"; - btn.style.border = anchorBtn.style.border || computed.border; - btn.style.borderRadius = anchorBtn.style.borderRadius || computed.borderRadius; - // Use cssText if available for background/color specific overrides - if (!btn.style.background) btn.style.background = computed.background; - if (!btn.style.color) btn.style.color = computed.color; + // Copy border radius if available, but avoid copying colors/backgrounds to allow theme switching + if (anchorBtn.style.borderRadius) { + btn.style.borderRadius = anchorBtn.style.borderRadius; + } else { + // Fallback to computed only for structural properties, not colors + btn.style.borderRadius = computed.borderRadius; + } + + // Do NOT copy background/color/border from computed styles as they freeze the theme + // Rely on className to handle theme styling // Add active state tracking btn.addEventListener('click', () => { diff --git a/web/magnify-glass/UiManager.js b/web/magnify-glass/UiManager.js index 0cf0570..73fce9e 100644 --- a/web/magnify-glass/UiManager.js +++ b/web/magnify-glass/UiManager.js @@ -383,10 +383,11 @@ class UiManager { btn.style.justifyContent = "center"; btn.style.padding = "0 8px"; btn.style.cursor = "pointer"; - btn.style.border = anchorBtn.style.border || computed.border; - btn.style.borderRadius = anchorBtn.style.borderRadius || computed.borderRadius; - if (!btn.style.background) btn.style.background = computed.background; - if (!btn.style.color) btn.style.color = computed.color; + if (anchorBtn.style.borderRadius) { + btn.style.borderRadius = anchorBtn.style.borderRadius; + } else { + btn.style.borderRadius = computed.borderRadius; + } btn.addEventListener("click", () => { if (this.onToggle) { this.onToggle(); diff --git a/web/magnify-glass/UiManager.js.map b/web/magnify-glass/UiManager.js.map index f832505..de2b5b4 100644 --- a/web/magnify-glass/UiManager.js.map +++ b/web/magnify-glass/UiManager.js.map @@ -1 +1 @@ -{"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 debugCanvas: HTMLCanvasElement | null;\n debugCtx: CanvasRenderingContext2D | 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.debugCanvas = null;\n this.debugCtx = 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 // Create debug canvas if debug mode is enabled\n if (this.config.debugMode) {\n this.createDebugCanvas();\n }\n\n // Inject menu button\n this.injectMenuButton();\n }\n\n /**\n * Create the debug canvas overlay.\n */\n createDebugCanvas(): void {\n this.debugCanvas = document.createElement(\"canvas\");\n this.debugCanvas.id = \"comfyui-magnify-debug\";\n this.debugCanvas.width = 400;\n this.debugCanvas.height = 350;\n this.debugCanvas.style.cssText = `\n position: fixed;\n top: 10px;\n right: 10px;\n background: rgba(0,0,0,0.7);\n border: 1px solid #fff;\n z-index: ${Z_INDEX.DEBUG};\n pointer-events: none;\n color: white;\n font-family: monospace;\n display: none;\n `;\n document.body.appendChild(this.debugCanvas);\n this.debugCtx = this.debugCanvas.getContext('2d');\n }\n\n /**\n * Show the magnifying glass.\n */\n show(): void {\n if (this.glassDiv) {\n this.glassDiv.style.display = 'block';\n // We do NOT modify opacity here, allowing it to be controlled independently\n }\n if (this.config.debugMode && this.debugCanvas) {\n this.debugCanvas.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 // Disable pointer events when hidden to be safe, though usually they are none anyway\n // this.glassDiv.style.pointerEvents = visible ? 'none' : 'none'; \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 this.glassDiv.style.cursor = 'move';\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 // Clear opposite positioning props to ensure left/top works\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 // We do NOT update config.offsetX/Y here to prevent double-movement wrapping.\n // The offset relative to the cursor remains constant during a drag.\n };\n\n const onMouseUp = (upEvent: MouseEvent) => {\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n\n // On release, we update the config offset so positionGlass() picks up where we left off\n if (this.config.followCursor) {\n // positionGlass logic: Target = Mouse (approx) + Offset.\n // CurrentGlass = Mouse + GrabOffset.\n // So implicitly, config.offsetX should arguably effectively capture this GrabOffset relative to the 'Standard' position?\n\n // Actually, sticking with the current offset works best for 1:1 dragging.\n // If we want to support \"Dragging to change Offset\", we would need to calculate:\n // config.offsetX = CurrentGlassLeft - (StandardTargetLeft based on glassPosition)\n\n // But simply maintaining the position is safer for now.\n }\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 if (this.config.debugMode && this.debugCanvas) {\n this.debugCanvas.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 * @param clientX - Client X coordinate\n * @param clientY - Client Y coordinate\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 * @param clientX - Client X coordinate\n * @param clientY - Client Y coordinate\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\n if (this.glassCanvas) {\n this.glassCanvas.width = this.config.glassSize;\n this.glassCanvas.height = this.config.glassSize;\n }\n\n // Handle debug canvas show/hide\n if (this.config.debugMode) {\n if (!this.debugCanvas) this.createDebugCanvas();\n if (this.state.active && this.debugCanvas) this.debugCanvas.style.display = \"block\";\n } else {\n if (this.debugCanvas) this.debugCanvas.style.display = \"none\";\n }\n }\n\n /**\n * Cleanup DOM elements.\n */\n cleanup(): void {\n if (this.glassDiv) this.glassDiv.remove();\n if (this.debugCanvas) this.debugCanvas.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 /**\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 const buttons = Array.from(document.querySelectorAll('button'));\n\n // Try to find an anchor button (Minimap or Links)\n // Priority 1: Minimap\n let anchorBtn = buttons.find(b => {\n const title = (b.title || '').toLowerCase();\n const aria = (b.getAttribute('aria-label') || '').toLowerCase();\n const text = (b.textContent || '').toLowerCase();\n return (title.includes('map') && !title.includes('open')) ||\n (aria.includes('map') && !aria.includes('open')) ||\n (text.includes('map'));\n });\n\n // Priority 2: Link Visibility\n if (!anchorBtn) {\n anchorBtn = buttons.find(b => {\n const title = (b.title || '').toLowerCase();\n const aria = (b.getAttribute('aria-label') || '').toLowerCase();\n return title.includes('link') || aria.includes('link');\n });\n }\n\n if (anchorBtn && anchorBtn.parentElement) {\n // Determine success BEFORE creating elements to allow early return if duplicate\n if (anchorBtn.parentElement.querySelector('.magnify-toggle-btn')) return true;\n\n Logger.debug('Found menu anchor:', anchorBtn.title || anchorBtn.getAttribute('aria-label') || 'Unknown Button');\n\n const btn = document.createElement('button');\n // Copy all classes to match theme/library (e.g. p-button, comfy-btn, etc)\n btn.className = anchorBtn.className + ' magnify-toggle-btn';\n\n // Copy key styles that might define shape/size\n const computed = window.getComputedStyle(anchorBtn);\n btn.style.height = computed.height;\n btn.style.minHeight = computed.minHeight;\n // btn.style.width = computed.width; // Don't copy width, let it adapt\n\n btn.title = \"Toggle Magnify Glass\";\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 8px\";\n btn.style.cursor = \"pointer\";\n btn.style.border = anchorBtn.style.border || computed.border;\n btn.style.borderRadius = anchorBtn.style.borderRadius || computed.borderRadius;\n // Use cssText if available for background/color specific overrides\n if (!btn.style.background) btn.style.background = computed.background;\n if (!btn.style.color) btn.style.color = computed.color;\n\n // Add active state tracking\n btn.addEventListener('click', () => {\n if (this.onToggle) {\n this.onToggle();\n // Try to apply common active classes found in modern UI frameworks\n btn.classList.toggle('active');\n btn.classList.toggle('p-highlight'); // PrimeVue\n btn.classList.toggle('selected');\n }\n });\n\n // Insert logic:\n const isMinimap = (anchorBtn.title || '').toLowerCase().includes('map') ||\n (anchorBtn.getAttribute('aria-label') || '').toLowerCase().includes('map');\n\n if (isMinimap) {\n // Insert AFTER Minimap\n if (anchorBtn.nextSibling) {\n anchorBtn.parentElement.insertBefore(btn, anchorBtn.nextSibling);\n } else {\n anchorBtn.parentElement.appendChild(btn);\n }\n } else {\n // It's likely links, Insert BEFORE\n anchorBtn.parentElement.insertBefore(btn, anchorBtn);\n }\n\n Logger.debug('Menu toggle button injected successfully');\n return true;\n }\n return false;\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. Found buttons:\",\n Array.from(document.querySelectorAll('button')).map(b => b.title || b.getAttribute('aria-label') || b.textContent || b.className).slice(0, 5));\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 +{"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 debugCanvas: HTMLCanvasElement | null;\n debugCtx: CanvasRenderingContext2D | 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.debugCanvas = null;\n this.debugCtx = 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 // Create debug canvas if debug mode is enabled\n if (this.config.debugMode) {\n this.createDebugCanvas();\n }\n\n // Inject menu button\n this.injectMenuButton();\n }\n\n /**\n * Create the debug canvas overlay.\n */\n createDebugCanvas(): void {\n this.debugCanvas = document.createElement(\"canvas\");\n this.debugCanvas.id = \"comfyui-magnify-debug\";\n this.debugCanvas.width = 400;\n this.debugCanvas.height = 350;\n this.debugCanvas.style.cssText = `\n position: fixed;\n top: 10px;\n right: 10px;\n background: rgba(0,0,0,0.7);\n border: 1px solid #fff;\n z-index: ${Z_INDEX.DEBUG};\n pointer-events: none;\n color: white;\n font-family: monospace;\n display: none;\n `;\n document.body.appendChild(this.debugCanvas);\n this.debugCtx = this.debugCanvas.getContext('2d');\n }\n\n /**\n * Show the magnifying glass.\n */\n show(): void {\n if (this.glassDiv) {\n this.glassDiv.style.display = 'block';\n // We do NOT modify opacity here, allowing it to be controlled independently\n }\n if (this.config.debugMode && this.debugCanvas) {\n this.debugCanvas.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 // Disable pointer events when hidden to be safe, though usually they are none anyway\n // this.glassDiv.style.pointerEvents = visible ? 'none' : 'none'; \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 this.glassDiv.style.cursor = 'move';\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 // Clear opposite positioning props to ensure left/top works\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 // We do NOT update config.offsetX/Y here to prevent double-movement wrapping.\n // The offset relative to the cursor remains constant during a drag.\n };\n\n const onMouseUp = (upEvent: MouseEvent) => {\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n\n // On release, we update the config offset so positionGlass() picks up where we left off\n if (this.config.followCursor) {\n // positionGlass logic: Target = Mouse (approx) + Offset.\n // CurrentGlass = Mouse + GrabOffset.\n // So implicitly, config.offsetX should arguably effectively capture this GrabOffset relative to the 'Standard' position?\n\n // Actually, sticking with the current offset works best for 1:1 dragging.\n // If we want to support \"Dragging to change Offset\", we would need to calculate:\n // config.offsetX = CurrentGlassLeft - (StandardTargetLeft based on glassPosition)\n\n // But simply maintaining the position is safer for now.\n }\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 if (this.config.debugMode && this.debugCanvas) {\n this.debugCanvas.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 * @param clientX - Client X coordinate\n * @param clientY - Client Y coordinate\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 * @param clientX - Client X coordinate\n * @param clientY - Client Y coordinate\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\n if (this.glassCanvas) {\n this.glassCanvas.width = this.config.glassSize;\n this.glassCanvas.height = this.config.glassSize;\n }\n\n // Handle debug canvas show/hide\n if (this.config.debugMode) {\n if (!this.debugCanvas) this.createDebugCanvas();\n if (this.state.active && this.debugCanvas) this.debugCanvas.style.display = \"block\";\n } else {\n if (this.debugCanvas) this.debugCanvas.style.display = \"none\";\n }\n }\n\n /**\n * Cleanup DOM elements.\n */\n cleanup(): void {\n if (this.glassDiv) this.glassDiv.remove();\n if (this.debugCanvas) this.debugCanvas.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 /**\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 const buttons = Array.from(document.querySelectorAll('button'));\n\n // Try to find an anchor button (Minimap or Links)\n // Priority 1: Minimap\n let anchorBtn = buttons.find(b => {\n const title = (b.title || '').toLowerCase();\n const aria = (b.getAttribute('aria-label') || '').toLowerCase();\n const text = (b.textContent || '').toLowerCase();\n return (title.includes('map') && !title.includes('open')) ||\n (aria.includes('map') && !aria.includes('open')) ||\n (text.includes('map'));\n });\n\n // Priority 2: Link Visibility\n if (!anchorBtn) {\n anchorBtn = buttons.find(b => {\n const title = (b.title || '').toLowerCase();\n const aria = (b.getAttribute('aria-label') || '').toLowerCase();\n return title.includes('link') || aria.includes('link');\n });\n }\n\n if (anchorBtn && anchorBtn.parentElement) {\n // Determine success BEFORE creating elements to allow early return if duplicate\n if (anchorBtn.parentElement.querySelector('.magnify-toggle-btn')) return true;\n\n Logger.debug('Found menu anchor:', anchorBtn.title || anchorBtn.getAttribute('aria-label') || 'Unknown Button');\n\n const btn = document.createElement('button');\n // Copy all classes to match theme/library (e.g. p-button, comfy-btn, etc)\n btn.className = anchorBtn.className + ' magnify-toggle-btn';\n\n // Copy key styles that might define shape/size\n const computed = window.getComputedStyle(anchorBtn);\n btn.style.height = computed.height;\n btn.style.minHeight = computed.minHeight;\n // btn.style.width = computed.width; // Don't copy width, let it adapt\n\n btn.title = \"Toggle Magnify Glass\";\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 8px\";\n btn.style.cursor = \"pointer\";\n // Copy border radius if available, but avoid copying colors/backgrounds to allow theme switching\n if (anchorBtn.style.borderRadius) {\n btn.style.borderRadius = anchorBtn.style.borderRadius;\n } else {\n // Fallback to computed only for structural properties, not colors\n btn.style.borderRadius = computed.borderRadius;\n }\n\n // Do NOT copy background/color/border from computed styles as they freeze the theme\n // Rely on className to handle theme styling\n\n // Add active state tracking\n btn.addEventListener('click', () => {\n if (this.onToggle) {\n this.onToggle();\n // Try to apply common active classes found in modern UI frameworks\n btn.classList.toggle('active');\n btn.classList.toggle('p-highlight'); // PrimeVue\n btn.classList.toggle('selected');\n }\n });\n\n // Insert logic:\n const isMinimap = (anchorBtn.title || '').toLowerCase().includes('map') ||\n (anchorBtn.getAttribute('aria-label') || '').toLowerCase().includes('map');\n\n if (isMinimap) {\n // Insert AFTER Minimap\n if (anchorBtn.nextSibling) {\n anchorBtn.parentElement.insertBefore(btn, anchorBtn.nextSibling);\n } else {\n anchorBtn.parentElement.appendChild(btn);\n }\n } else {\n // It's likely links, Insert BEFORE\n anchorBtn.parentElement.insertBefore(btn, anchorBtn);\n }\n\n Logger.debug('Menu toggle button injected successfully');\n return true;\n }\n return false;\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. Found buttons:\",\n Array.from(document.querySelectorAll('button')).map(b => b.title || b.getAttribute('aria-label') || b.textContent || b.className).slice(0, 5));\n clearInterval(checkForMenu);\n return;\n }\n\n if (attemptInjection()) {\n clearInterval(checkForMenu);\n }\n }, 100);\n }\n}\n"],"names":[],"mappings":";;;;;;AAgBO,MAAM,UAAU;AAAA,EAUnB,YAAY,QAAuB,OAAuB,UAAuB;AATjF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGI,SAAK,SAAS;AACd,SAAK,QAAQ;AACb,SAAK,WAAW;AAChB,SAAK,cAAc;AACnB,SAAK,cAAc;AACnB,SAAK,WAAW;AAChB,SAAK,uBAAuB;AAC5B,SAAK,WAAW;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;AAEnB,SAAK,WAAW,SAAS,cAAc,KAAK;AAC5C,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,MAAM,UAAU;AAAA;AAAA;AAAA,qBAGjB,KAAK,OAAO,SAAS;AAAA,sBACpB,KAAK,OAAO,SAAS;AAAA,6BACd,KAAK,OAAO,eAAe,WAAW,QAAQ,KAAK;AAAA,sBAC1D,KAAK,OAAO,gBAAgB,GAAG,KAAK,OAAO,WAAW,YAAY,KAAK,OAAO,WAAW,KAAK,MAAM;AAAA;AAAA;AAAA,uBAGnG,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAO5B,SAAK,cAAc,SAAS,cAAc,QAAQ;AAClD,SAAK,YAAY,QAAQ,KAAK,OAAO;AACrC,SAAK,YAAY,SAAS,KAAK,OAAO;AACtC,SAAK,YAAY,KAAK;AACtB,SAAK,SAAS,YAAY,KAAK,WAAW;AAG1C,SAAK,uBAAuB,SAAS,cAAc,KAAK;AACxD,SAAK,qBAAqB,KAAK;AAC/B,SAAK,qBAAqB,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS1C,SAAK,SAAS,YAAY,KAAK,oBAAoB;AAEnD,aAAS,KAAK,YAAY,KAAK,QAAQ;AAGvC,QAAI,KAAK,OAAO,WAAW;AACvB,WAAK,kBAAA;AAAA,IACT;AAGA,SAAK,iBAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA0B;AACtB,SAAK,cAAc,SAAS,cAAc,QAAQ;AAClD,SAAK,YAAY,KAAK;AACtB,SAAK,YAAY,QAAQ;AACzB,SAAK,YAAY,SAAS;AAC1B,SAAK,YAAY,MAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,uBAMlB,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAM5B,aAAS,KAAK,YAAY,KAAK,WAAW;AAC1C,SAAK,WAAW,KAAK,YAAY,WAAW,IAAI;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACT,QAAI,KAAK,UAAU;AACf,WAAK,SAAS,MAAM,UAAU;AAAA,IAElC;AACA,QAAI,KAAK,OAAO,aAAa,KAAK,aAAa;AAC3C,WAAK,YAAY,MAAM,UAAU;AAAA,IACrC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,qBAAqB,SAAwB;AACzC,QAAI,KAAK,UAAU;AACf,WAAK,SAAS,MAAM,UAAU,UAAU,MAAM;AAAA,IAGlD;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,SAAwB;AAChC,QAAI,CAAC,KAAK,SAAU;AAEpB,QAAI,SAAS;AACT,WAAK,SAAS,MAAM,SAAS;AAC7B,WAAK,SAAS,MAAM,gBAAgB;AACpC,WAAK,SAAS,UAAU,IAAI,WAAW;AAGvC,YAAM,cAAc,CAAC,MAAkB;AACnC,UAAE,eAAA;AACF,UAAE,gBAAA;AAIF,cAAM,OAAO,KAAK,SAAU,sBAAA;AAC5B,cAAM,cAAc,KAAK,OAAO,EAAE;AAClC,cAAM,cAAc,KAAK,MAAM,EAAE;AAGjC,aAAK,SAAU,MAAM,QAAQ;AAC7B,aAAK,SAAU,MAAM,SAAS;AAC9B,aAAK,SAAU,MAAM,YAAY;AAEjC,cAAM,cAAc,CAAC,cAA0B;AAC3C,oBAAU,eAAA;AACV,oBAAU,gBAAA;AAIV,gBAAM,UAAU,UAAU,UAAU;AACpC,gBAAM,SAAS,UAAU,UAAU;AAEnC,cAAI,KAAK,UAAU;AACf,iBAAK,SAAS,MAAM,OAAO,GAAG,OAAO;AACrC,iBAAK,SAAS,MAAM,MAAM,GAAG,MAAM;AAAA,UACvC;AAAA,QAIJ;AAEA,cAAM,YAAY,CAAC,YAAwB;AACvC,mBAAS,oBAAoB,aAAa,WAAW;AACrD,mBAAS,oBAAoB,WAAW,SAAS;AAGjD,cAAI,KAAK,OAAO,aAAc;AAa9B,eAAK,MAAM,oBAAoB;AAC/B,eAAK,YAAY,KAAK;AAGtB,gBAAM,YAAa,OAAe;AAClC,cAAI,uCAAW,WAAW;AACtB,sBAAU,UAAU,oBAAA;AAAA,UACxB;AAAA,QACJ;AAEA,iBAAS,iBAAiB,aAAa,WAAW;AAClD,iBAAS,iBAAiB,WAAW,SAAS;AAAA,MAClD;AAEA,WAAK,SAAS,iBAAiB,aAAa,WAAW;AACtD,WAAK,SAAiB,eAAe;AAAA,IAC1C,OAAO;AACH,WAAK,SAAS,MAAM,SAAS;AAC7B,WAAK,SAAS,MAAM,gBAAgB;AACpC,WAAK,SAAS,UAAU,OAAO,WAAW;AAG1C,UAAK,KAAK,SAAiB,cAAc;AACrC,aAAK,SAAS,oBAAoB,aAAc,KAAK,SAAiB,YAAY;AAClF,eAAQ,KAAK,SAAiB;AAAA,MAClC;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACT,QAAI,KAAK,UAAU;AACf,WAAK,SAAS,MAAM,UAAU;AAAA,IAClC;AACA,QAAI,KAAK,OAAO,aAAa,KAAK,aAAa;AAC3C,WAAK,YAAY,MAAM,UAAU;AAAA,IACrC;AAEA,QAAI,KAAK,sBAAsB;AAC3B,WAAK,qBAAqB,YAAY;AAAA,IAC1C;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,cAAc,SAAiB,SAAuB;AAElD,QAAI,CAAC,KAAK,OAAO,gBAAgB,CAAC,KAAK,YAAY,KAAK,MAAM,kBAAmB;AAEjF,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,eAAe;AACrB,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAIlB,QAAI,UAAkB,KAAK,OAAO,WAAW;AAC7C,QAAI,UAAkB,KAAK,OAAO,WAAW;AAG7C,YAAQ,KAAK,OAAO,eAAA;AAAA,MAChB,KAAK;AACD,mBAAW,UAAW,YAAY;AAClC,mBAAW,UAAU,YAAY;AACjC;AAAA,MACJ,KAAK;AACD,mBAAW,UAAW,YAAY;AAClC,mBAAW,UAAU;AACrB;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU,YAAY;AACjC,mBAAW,UAAW,YAAY;AAClC;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU;AACrB,mBAAW,UAAW,YAAY;AAClC;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU,YAAY;AACjC,mBAAW,UAAU,YAAY;AACjC;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU;AACrB,mBAAW,UAAU,YAAY;AACjC;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU,YAAY;AACjC,mBAAW,UAAU;AACrB;AAAA,MACJ,KAAK;AACD,mBAAW,UAAU;AACrB,mBAAW,UAAU;AACrB;AAAA,MACJ;AACI,mBAAW,UAAW,YAAY;AAClC,mBAAW,UAAU;AACrB;AAAA,IAAA;AAIR,UAAM,eAAe,UAAW,YAAY;AAC5C,UAAM,eAAe,UAAW,YAAY;AAG5C,QAAI,eAAe,KAAK,GAAG;AAEvB,YAAM,WAAW,MAAM,UAAU;AACjC,WAAK,SAAS,MAAM,QAAQ,GAAG,QAAQ;AACvC,WAAK,SAAS,MAAM,OAAO;AAAA,IAC/B,OAAO;AAEH,WAAK,SAAS,MAAM,OAAO,GAAG,OAAO;AACrC,WAAK,SAAS,MAAM,QAAQ;AAAA,IAChC;AAGA,QAAI,eAAe,KAAK,GAAG;AAEvB,YAAM,YAAY,MAAM,UAAU;AAClC,WAAK,SAAS,MAAM,SAAS,GAAG,SAAS;AACzC,WAAK,SAAS,MAAM,MAAM;AAAA,IAC9B,OAAO;AAEH,WAAK,SAAS,MAAM,MAAM,GAAG,OAAO;AACpC,WAAK,SAAS,MAAM,SAAS;AAAA,IACjC;AAIA,SAAK,oBAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,sBAA4B;AACxB,QAAI,CAAC,KAAK,SAAU;AAEpB,UAAM,YAAY,KAAK,OAAO;AAC9B,UAAM,UAAU;AAChB,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAGlB,UAAM,kBAAkB,KAAK,SAAS,MAAM,UAAU,UAAU,KAAK,SAAS,MAAM,UAAU;AAE9F,QAAI,iBAAiB;AACjB,UAAI,eAAe,WAAW,KAAK,SAAS,MAAM,KAAK,KAAK;AAG5D,qBAAe,KAAK,IAAI,SAAS,KAAK,IAAI,cAAc,KAAK,YAAY,OAAO,CAAC;AACjF,WAAK,SAAS,MAAM,QAAQ,GAAG,YAAY;AAAA,IAC/C,OAAO;AAEH,UAAI,cAAc,WAAW,KAAK,SAAS,MAAM,IAAI,KAAK;AAC1D,oBAAc,KAAK,IAAI,SAAS,KAAK,IAAI,aAAa,KAAK,YAAY,OAAO,CAAC;AAC/E,WAAK,SAAS,MAAM,OAAO,GAAG,WAAW;AAAA,IAC7C;AAGA,UAAM,mBAAmB,KAAK,SAAS,MAAM,WAAW,UAAU,KAAK,SAAS,MAAM,WAAW;AAEjG,QAAI,kBAAkB;AAClB,UAAI,gBAAgB,WAAW,KAAK,SAAS,MAAM,MAAM,KAAK;AAC9D,sBAAgB,KAAK,IAAI,SAAS,KAAK,IAAI,eAAe,KAAK,YAAY,OAAO,CAAC;AACnF,WAAK,SAAS,MAAM,SAAS,GAAG,aAAa;AAAA,IACjD,OAAO;AAEH,UAAI,aAAa,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK;AACxD,mBAAa,KAAK,IAAI,SAAS,KAAK,IAAI,YAAY,KAAK,YAAY,OAAO,CAAC;AAC7E,WAAK,SAAS,MAAM,MAAM,GAAG,UAAU;AAAA,IAC3C;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,2BAAiC;AAC7B,QAAI,CAAC,KAAK,SAAU;AAEpB,SAAK,oBAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,cAAoB;AAChB,QAAI,KAAK,UAAU;AACf,WAAK,SAAS,MAAM,QAAQ,GAAG,KAAK,OAAO,SAAS;AACpD,WAAK,SAAS,MAAM,SAAS,GAAG,KAAK,OAAO,SAAS;AACrD,WAAK,SAAS,MAAM,SAAS,KAAK,OAAO,gBACnC,GAAG,KAAK,OAAO,WAAW,YAAY,KAAK,OAAO,WAAW,KAC7D;AAGN,WAAK,SAAS,MAAM,WAAW;AAC/B,WAAK,SAAS,MAAM,eAAe;AAEnC,cAAQ,KAAK,OAAO,YAAA;AAAA,QAChB,KAAK;AACD,eAAK,SAAS,MAAM,eAAe;AACnC;AAAA,QACJ,KAAK;AAED;AAAA,QACJ,KAAK;AACD,eAAK,SAAS,MAAM,eAAe;AACnC;AAAA,QACJ;AACI,eAAK,SAAS,MAAM,eAAe;AACnC;AAAA,MAAA;AAAA,IAEZ;AAEA,QAAI,KAAK,aAAa;AAClB,WAAK,YAAY,QAAQ,KAAK,OAAO;AACrC,WAAK,YAAY,SAAS,KAAK,OAAO;AAAA,IAC1C;AAGA,QAAI,KAAK,OAAO,WAAW;AACvB,UAAI,CAAC,KAAK,YAAa,MAAK,kBAAA;AAC5B,UAAI,KAAK,MAAM,UAAU,KAAK,YAAa,MAAK,YAAY,MAAM,UAAU;AAAA,IAChF,OAAO;AACH,UAAI,KAAK,YAAa,MAAK,YAAY,MAAM,UAAU;AAAA,IAC3D;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,QAAI,KAAK,SAAU,MAAK,SAAS,OAAA;AACjC,QAAI,KAAK,YAAa,MAAK,YAAY,OAAA;AAEvC,UAAM,MAAM,SAAS,cAAc,qBAAqB;AACxD,QAAI,SAAS,OAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAyB;AAErB,UAAM,WAAW,KAAK,IAAA,IAAQ;AAG9B,UAAM,mBAAmB,MAAM;AAC3B,YAAM,UAAU,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC;AAI9D,UAAI,YAAY,QAAQ,KAAK,CAAA,MAAK;AAC9B,cAAM,SAAS,EAAE,SAAS,IAAI,YAAA;AAC9B,cAAM,QAAQ,EAAE,aAAa,YAAY,KAAK,IAAI,YAAA;AAClD,cAAM,QAAQ,EAAE,eAAe,IAAI,YAAA;AACnC,eAAQ,MAAM,SAAS,KAAK,KAAK,CAAC,MAAM,SAAS,MAAM,KAClD,KAAK,SAAS,KAAK,KAAK,CAAC,KAAK,SAAS,MAAM,KAC7C,KAAK,SAAS,KAAK;AAAA,MAC5B,CAAC;AAGD,UAAI,CAAC,WAAW;AACZ,oBAAY,QAAQ,KAAK,CAAA,MAAK;AAC1B,gBAAM,SAAS,EAAE,SAAS,IAAI,YAAA;AAC9B,gBAAM,QAAQ,EAAE,aAAa,YAAY,KAAK,IAAI,YAAA;AAClD,iBAAO,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS,MAAM;AAAA,QACzD,CAAC;AAAA,MACL;AAEA,UAAI,aAAa,UAAU,eAAe;AAEtC,YAAI,UAAU,cAAc,cAAc,qBAAqB,EAAG,QAAO;AAEzE,eAAO,MAAM,sBAAsB,UAAU,SAAS,UAAU,aAAa,YAAY,KAAK,gBAAgB;AAE9G,cAAM,MAAM,SAAS,cAAc,QAAQ;AAE3C,YAAI,YAAY,UAAU,YAAY;AAGtC,cAAM,WAAW,OAAO,iBAAiB,SAAS;AAClD,YAAI,MAAM,SAAS,SAAS;AAC5B,YAAI,MAAM,YAAY,SAAS;AAG/B,YAAI,QAAQ;AACZ,YAAI,YAAY,MAAM;AAGtB,YAAI,MAAM,UAAU;AACpB,YAAI,MAAM,aAAa;AACvB,YAAI,MAAM,iBAAiB;AAC3B,YAAI,MAAM,UAAU;AACpB,YAAI,MAAM,SAAS;AAEnB,YAAI,UAAU,MAAM,cAAc;AAC9B,cAAI,MAAM,eAAe,UAAU,MAAM;AAAA,QAC7C,OAAO;AAEH,cAAI,MAAM,eAAe,SAAS;AAAA,QACtC;AAMA,YAAI,iBAAiB,SAAS,MAAM;AAChC,cAAI,KAAK,UAAU;AACf,iBAAK,SAAA;AAEL,gBAAI,UAAU,OAAO,QAAQ;AAC7B,gBAAI,UAAU,OAAO,aAAa;AAClC,gBAAI,UAAU,OAAO,UAAU;AAAA,UACnC;AAAA,QACJ,CAAC;AAGD,cAAM,aAAa,UAAU,SAAS,IAAI,YAAA,EAAc,SAAS,KAAK,MACjE,UAAU,aAAa,YAAY,KAAK,IAAI,YAAA,EAAc,SAAS,KAAK;AAE7E,YAAI,WAAW;AAEX,cAAI,UAAU,aAAa;AACvB,sBAAU,cAAc,aAAa,KAAK,UAAU,WAAW;AAAA,UACnE,OAAO;AACH,sBAAU,cAAc,YAAY,GAAG;AAAA,UAC3C;AAAA,QACJ,OAAO;AAEH,oBAAU,cAAc,aAAa,KAAK,SAAS;AAAA,QACvD;AAEA,eAAO,MAAM,0CAA0C;AACvD,eAAO;AAAA,MACX;AACA,aAAO;AAAA,IACX;AAGA,QAAI,mBAAoB;AAGxB,UAAM,eAAe,YAAY,MAAM;AACnC,UAAI,KAAK,IAAA,IAAQ,UAAU;AACvB,gBAAQ;AAAA,UAAK;AAAA,UACT,MAAM,KAAK,SAAS,iBAAiB,QAAQ,CAAC,EAAE,IAAI,CAAA,MAAK,EAAE,SAAS,EAAE,aAAa,YAAY,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC;AAAA,QAAA;AAChJ,sBAAc,YAAY;AAC1B;AAAA,MACJ;AAEA,UAAI,oBAAoB;AACpB,sBAAc,YAAY;AAAA,MAC9B;AAAA,IACJ,GAAG,GAAG;AAAA,EACV;AACJ;"} \ No newline at end of file