From 6b42e7dd0269ffa8db99ac7f1d6488b651e34df1 Mon Sep 17 00:00:00 2001 From: AEmotionStudio Date: Wed, 31 Dec 2025 18:56:56 -0800 Subject: [PATCH] refactor: Reimplement info panel UI, removing Vue components, externalizing styles, and enhancing theme detection. --- package.json | 7 +- src/components/ComfyPanel.vue | 47 -- src/info-panel/StateManager.ts | 106 ++-- src/info-panel/UIManager.ts | 13 +- src/info-panel/components/InfoPanel.vue | 174 ------- src/info-panel/index.ts | 1 - src/info-panel/styles.ts | 574 ---------------------- vite.config.ts | 7 +- web/info-panel.css | 615 ++++++++++++++++++++++++ web/info-panel/StateManager.js | 84 ++-- web/info-panel/StateManager.js.map | 2 +- web/info-panel/UIManager.js | 13 +- web/info-panel/UIManager.js.map | 2 +- web/info-panel/styles.js | 568 ---------------------- web/info-panel/styles.js.map | 1 - 15 files changed, 737 insertions(+), 1477 deletions(-) delete mode 100644 src/components/ComfyPanel.vue delete mode 100644 src/info-panel/components/InfoPanel.vue delete mode 100644 src/info-panel/styles.ts create mode 100644 web/info-panel.css delete mode 100644 web/info-panel/styles.js delete mode 100644 web/info-panel/styles.js.map diff --git a/package.json b/package.json index b35e49f..a0f30de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "comfyui-magnifyglass", - "version": "1.0.0", + "version": "1.5.0", "type": "module", "description": "A professional magnifying glass and info panel for ComfyUI", "scripts": { @@ -12,14 +12,9 @@ }, "devDependencies": { "@playwright/test": "^1.57.0", - "@vitejs/plugin-vue": "^6.0.3", - "@vue/test-utils": "^2.4.6", "happy-dom": "^20.0.11", "typescript": "^5.3.0", "vite": "^5.0.0", "vitest": "^4.0.16" - }, - "dependencies": { - "vue": "^3.5.26" } } \ No newline at end of file diff --git a/src/components/ComfyPanel.vue b/src/components/ComfyPanel.vue deleted file mode 100644 index 25e0bbb..0000000 --- a/src/components/ComfyPanel.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/src/info-panel/StateManager.ts b/src/info-panel/StateManager.ts index 7d34969..bdc2c2c 100644 --- a/src/info-panel/StateManager.ts +++ b/src/info-panel/StateManager.ts @@ -126,61 +126,71 @@ export class StateManager { detectCurrentTheme(): boolean { let detectedTheme = 'dark'; // Default fallback - const body = document.body; - const html = document.documentElement; - const vueApp = document.querySelector('#vue-app'); - const sidebar = document.querySelector('.comfy-menu, .sidebar, .menu, [class*="sidebar"], [class*="menu"]'); + // Method 1: Try to read from ComfyUI's color palette setting + try { + // ComfyUI stores theme in 'Comfy.ColorPalette' setting + const colorPalette = getSettingValue('Comfy.ColorPalette', ''); - // Get all possible theme-related elements - const allElements = [body, html, vueApp, sidebar].filter(Boolean) as Element[]; + if (colorPalette) { + // Extract theme name from various possible formats + const paletteStr = String(colorPalette).toLowerCase(); - // Check CSS variables - const rootStyles = window.getComputedStyle(html); - const bodyStyles = window.getComputedStyle(body); - - // Method 2: Analyze background colors of multiple elements - const backgrounds = allElements.map(el => { - const styles = window.getComputedStyle(el); - return { - element: el.tagName || el.className, - backgroundColor: styles.backgroundColor, - color: styles.color - }; - }); - - // Method 3: Check for light backgrounds - for (const bg of backgrounds) { - if (bg.backgroundColor && bg.backgroundColor !== 'rgba(0, 0, 0, 0)' && bg.backgroundColor !== 'transparent') { - const rgbMatch = bg.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); - if (rgbMatch) { - const [, r, g, b] = rgbMatch.map(Number); - const brightness = (r * 299 + g * 587 + b * 114) / 1000; - if (brightness > 180) { // Higher threshold for light detection - detectedTheme = 'light'; - break; - } + if (paletteStr.includes('solarized')) { + detectedTheme = 'solarized'; + } else if (paletteStr.includes('arc')) { + detectedTheme = 'arc'; + } else if (paletteStr.includes('nord')) { + detectedTheme = 'nord'; + } else if (paletteStr.includes('github')) { + detectedTheme = 'github'; + } else if (paletteStr.includes('light')) { + detectedTheme = 'light'; + } else if (paletteStr.includes('dark')) { + detectedTheme = 'dark'; } } + } catch (e) { + // Fallback to brightness-based detection + console.warn('ComfyUI Info Panel: Could not read theme from settings, using fallback detection'); + } + + // Method 2: Fallback - check body/html for theme classes or data attributes + if (detectedTheme === 'dark') { + const body = document.body; + const html = document.documentElement; + + // Check for data-theme attribute + const dataTheme = body.getAttribute('data-theme') || html.getAttribute('data-theme'); + if (dataTheme) { + detectedTheme = dataTheme.toLowerCase(); + } + + // Check for theme classes + const classes = (body.className + ' ' + html.className).toLowerCase(); + if (classes.includes('theme-solarized') || classes.includes('solarized')) { + detectedTheme = 'solarized'; + } else if (classes.includes('theme-arc') || classes.includes('arc-theme')) { + detectedTheme = 'arc'; + } else if (classes.includes('theme-nord') || classes.includes('nord-theme')) { + detectedTheme = 'nord'; + } else if (classes.includes('theme-github') || classes.includes('github-theme')) { + detectedTheme = 'github'; + } else if (classes.includes('theme-light') || classes.includes('light-theme')) { + detectedTheme = 'light'; + } } - // Method 5: Force light theme detection if we see light backgrounds + // Method 3: Last resort - brightness detection for light vs dark if (detectedTheme === 'dark') { - // Look for any element with very light background - const lightElements = document.querySelectorAll('*'); - for (let i = 0; i < Math.min(lightElements.length, 50); i++) { // Check first 50 elements - const el = lightElements[i]; - const styles = window.getComputedStyle(el); - const bgColor = styles.backgroundColor; - - if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') { - const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); - if (rgbMatch) { - const [, r, g, b] = rgbMatch.map(Number); - const brightness = (r * 299 + g * 587 + b * 114) / 1000; - if (brightness > 200) { // Very light elements - detectedTheme = 'light'; - break; - } + const bodyStyles = window.getComputedStyle(document.body); + const bgColor = bodyStyles.backgroundColor; + if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') { + const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + if (rgbMatch) { + const [, r, g, b] = rgbMatch.map(Number); + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + if (brightness > 180) { + detectedTheme = 'light'; } } } diff --git a/src/info-panel/UIManager.ts b/src/info-panel/UIManager.ts index a5f53d5..2c156c2 100644 --- a/src/info-panel/UIManager.ts +++ b/src/info-panel/UIManager.ts @@ -6,7 +6,6 @@ */ // import { Z_INDEX } from '../shared/constants'; // Not used in original code? Kept for reference if needed -import { getInfoPanelCSS } from './styles'; import { StateManager } from './StateManager'; interface InfoPanelElements { @@ -817,14 +816,16 @@ export class UIManager { } /** - * Inject CSS styles. + * Inject CSS styles by loading external stylesheet. */ injectStyles(): void { if (!document.getElementById('magnify-info-panel-styles-v2')) { - const style = document.createElement('style'); - style.id = 'magnify-info-panel-styles-v2'; - style.textContent = getInfoPanelCSS(); - document.head.appendChild(style); + const link = document.createElement('link'); + link.id = 'magnify-info-panel-styles-v2'; + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = 'extensions/comfyui-magnifyglass/info-panel.css'; + document.head.appendChild(link); } } diff --git a/src/info-panel/components/InfoPanel.vue b/src/info-panel/components/InfoPanel.vue deleted file mode 100644 index aec8e0d..0000000 --- a/src/info-panel/components/InfoPanel.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - - - diff --git a/src/info-panel/index.ts b/src/info-panel/index.ts index e4698ff..b48465c 100644 --- a/src/info-panel/index.ts +++ b/src/info-panel/index.ts @@ -4,4 +4,3 @@ export * from './PositionManager'; export * from './InformationGatherer'; export * from './EventManager'; export * from './InfoPanel'; -export * from './styles'; diff --git a/src/info-panel/styles.ts b/src/info-panel/styles.ts deleted file mode 100644 index cd70e90..0000000 --- a/src/info-panel/styles.ts +++ /dev/null @@ -1,574 +0,0 @@ -/** - * ComfyUI MagnifyGlass - Info Panel Styles (TypeScript) - * - * Complete CSS styles for the info panel component. - */ - -/** - * Get the complete CSS for the info panel. - * @returns CSS string - */ -export function getInfoPanelCSS(): string { - return ` - .magnify-info-panel { - border-radius: 12px; - backdrop-filter: blur(20px); - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1); - overflow: hidden; - font-size: 16px; - line-height: 1.6; - pointer-events: auto; - } - - .magnify-info-panel * { - pointer-events: auto; - } - - .magnify-info-panel.theme-dark { - background: linear-gradient(135deg, rgba(0, 0, 0, 0.96), rgba(10, 10, 15, 0.96)); - color: var(--info-panel-text-color, #e0e0e0); - border: 1px solid rgba(100, 100, 120, 0.4); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 16px rgba(0, 0, 0, 0.2); - backdrop-filter: blur(12px); - } - - .magnify-info-panel.theme-light { - background: linear-gradient(135deg, rgba(250, 250, 255, 0.96), rgba(240, 240, 250, 0.96)); - color: var(--info-panel-text-color, #2a2a2a); - border: 1px solid rgba(200, 200, 220, 0.5); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 16px rgba(0, 0, 0, 0.05); - backdrop-filter: blur(12px); - } - - .magnify-info-panel.visible { - transform: translateY(0) !important; - } - - .magnify-info-panel.panel-pinned { - cursor: grab; - box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); - } - - .magnify-info-panel.panel-locked { - cursor: default !important; - box-shadow: 0 0 0 2px rgba(255, 69, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); - } - - .magnify-info-panel.panel-locked::after { - content: '📌'; - position: absolute; - top: 8px; - right: 8px; - font-size: 14px; - opacity: 0.7; - pointer-events: none; - z-index: 1; - } - - .magnify-info-panel.panel-pinned.panel-dragging { - cursor: grabbing; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7); - transform: scale(1.02) !important; - z-index: 100001 !important; - transition: none !important; - } - - .magnify-info-panel.panel-pinned::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: linear-gradient(45deg, rgba(255, 215, 0, 0.05) 0%, transparent 100%); - pointer-events: none; - border-radius: inherit; - } - - .panel-dragging * { - pointer-events: none !important; - user-select: none !important; - transition: none !important; - } - - .magnify-info-panel.panel-minimized .panel-content { - max-height: 0 !important; - overflow: hidden; - transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); - } - - .floating-controls { - position: fixed; - display: flex; - padding: 4px; - background: rgba(40, 40, 50, 0.95); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 4px; - opacity: 1; - z-index: 100000; - backdrop-filter: blur(8px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); - } - - .floating-controls.vertical-layout { - flex-direction: column; - gap: 4px; - } - - .floating-controls.horizontal-layout { - flex-direction: row; - gap: 4px; - } - - .control-btn { - background: rgba(60, 60, 70, 0.8); - border: 1px solid rgba(255, 255, 255, 0.2); - width: 28px; - height: 28px; - font-size: 14px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - color: #ddd; - border-radius: 3px; - transition: background-color 0.15s ease; - } - - .control-btn:hover { - background: rgba(80, 80, 90, 0.9); - color: white; - } - - .control-btn:active { - background: rgba(100, 100, 110, 0.9); - } - - .control-btn:disabled { - background: rgba(40, 40, 50, 0.5); - color: #666; - cursor: not-allowed; - opacity: 0.5; - } - - .control-btn.active { - background: rgba(255, 200, 50, 0.3); - border-color: rgba(255, 200, 50, 0.6); - color: #ffcc33; - } - - .control-btn.lock-btn.active { - background: rgba(255, 69, 0, 0.3); - border-color: rgba(255, 69, 0, 0.6); - color: #ff4500; - } - - .panel-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 18px; - background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05)); - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - user-select: none; - } - - .header-content { - display: flex; - align-items: center; - gap: 10px; - } - - .header-icon { - font-size: 18px; - opacity: 0.9; - } - - .header-title { - font-weight: 600; - font-size: 18px; - } - - .header-subtitle { - font-size: 14px; - opacity: 0.7; - font-style: italic; - } - - .header-controls { - display: flex; - gap: 6px; - } - - .minimize-btn { - background: rgba(255, 255, 255, 0.1); - border: none; - border-radius: 6px; - color: inherit; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s; - font-weight: bold; - font-size: 16px; - } - - .minimize-btn:hover { - background: rgba(255, 255, 255, 0.2); - transform: scale(1.05); - } - - .minimize-btn:active { - transform: scale(0.95); - } - - .panel-content { - padding: 0; - max-height: calc(100% - 60px); - overflow-y: auto; - scrollbar-width: thin; - } - - .panel-content::-webkit-scrollbar { - width: 8px; - } - - .panel-content::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - } - - .panel-content::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 3px; - } - - .info-section { - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 8px; - border-radius: 10px; - overflow: visible; - background: rgba(255, 255, 255, 0.02); - transition: all 0.3s ease; - } - - .info-section:hover { - background: rgba(255, 255, 255, 0.04); - transform: translateY(-1px); - } - - .section-header { - display: flex; - align-items: center; - gap: 12px; - padding: 16px 20px; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - font-weight: 500; - border-radius: 8px; - margin: 3px; - position: relative; - overflow: hidden; - } - - .section-header::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.05) 50%, transparent 100%); - transform: translateX(-100%); - transition: transform 0.6s ease; - } - - .section-header:hover::before { - transform: translateX(100%); - } - - .section-header:hover { - background: rgba(255, 255, 255, 0.08); - transform: translateX(2px); - box-shadow: inset 2px 0 0 rgba(116, 185, 255, 0.5); - } - - .section-header:active { - background: rgba(255, 255, 255, 0.12); - transform: translateX(1px); - } - - .section-header.expanded { - background: rgba(116, 185, 255, 0.1); - border-left: 2px solid var(--info-panel-accent-color, #74b9ff); - } - - .info-section[data-section="node"] { - background: linear-gradient(135deg, rgba(116, 185, 255, 0.05), rgba(160, 212, 104, 0.05)); - border: 1px solid rgba(116, 185, 255, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="node"] .section-header { - background: linear-gradient(135deg, rgba(116, 185, 255, 0.15), rgba(160, 212, 104, 0.15)); - border-radius: 8px 8px 0 0; - cursor: default; - } - - .info-section[data-section="node"] .section-header:hover { - transform: none; - box-shadow: none; - } - - .info-section[data-section="node"] .section-title { - color: var(--info-panel-accent-color, #74b9ff); - font-weight: 700; - } - - .info-section[data-section="inspector"] { - background: linear-gradient(135deg, rgba(100, 150, 255, 0.05), rgba(74, 144, 226, 0.05)); - border: 1px solid rgba(100, 150, 255, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="inspector"] .section-header { - background: linear-gradient(135deg, rgba(100, 150, 255, 0.15), rgba(74, 144, 226, 0.15)); - border-radius: 8px 8px 0 0; - } - - .info-section[data-section="inspector"] .section-title { - color: #6496ff; - font-weight: 700; - } - - .info-section[data-section="media"] { - background: linear-gradient(135deg, rgba(253, 203, 110, 0.05), rgba(255, 215, 0, 0.05)); - border: 1px solid rgba(253, 203, 110, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="media"] .section-header { - background: linear-gradient(135deg, rgba(253, 203, 110, 0.15), rgba(255, 215, 0, 0.15)); - border-radius: 8px 8px 0 0; - } - - .info-section[data-section="media"] .section-title { - color: var(--info-panel-accent-color, #fdcb6e); - font-weight: 700; - } - - .section-icon { - font-size: 18px; - width: 22px; - text-align: center; - } - - .section-title { - flex: 1; - font-size: 16px; - font-weight: 600; - letter-spacing: 0.3px; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); - } - - .section-badge { - background: rgba(100, 150, 255, 0.2); - color: #8bb3ff; - padding: 3px 10px; - border-radius: 10px; - font-size: 13px; - font-weight: 500; - } - - .expand-icon { - font-size: 14px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: rgba(255, 255, 255, 0.6); - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - background: rgba(255, 255, 255, 0.05); - margin-left: auto; - } - - .section-header:hover .expand-icon { - color: var(--info-panel-accent-color, #74b9ff); - background: rgba(116, 185, 255, 0.15); - transform: scale(1.1); - } - - .section-header.expanded .expand-icon { - transform: rotate(90deg) scale(1.1); - color: var(--info-panel-accent-color, #74b9ff); - background: rgba(116, 185, 255, 0.2); - } - - .section-content { - max-height: 0; - overflow: visible; - transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; - opacity: 0; - } - - .section-content.expanded { - max-height: 2000px; - opacity: 1; - overflow: visible; - } - - .section-body { - padding: 0 18px 16px 24px; - } - - .info-row { - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 10px 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - transition: all 0.2s ease; - border-radius: 4px; - margin: 0 -8px; - padding-left: 8px; - padding-right: 8px; - } - - .info-row:last-child { - border-bottom: none; - } - - .info-row:hover { - background: rgba(255, 255, 255, 0.03); - border-bottom-color: rgba(255, 255, 255, 0.08); - } - - .info-label { - font-weight: 500; - opacity: 0.85; - font-size: 15px; - min-width: 80px; - padding-right: 12px; - line-height: 1.5; - } - - .info-value { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 14px; - color: var(--info-panel-accent-color, #a0d468); - background: rgba(160, 212, 104, 0.12); - padding: 4px 10px; - border-radius: 6px; - max-width: 100%; - word-wrap: break-word; - word-break: break-word; - hyphens: auto; - line-height: 1.4; - text-align: left; - white-space: pre-wrap; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - transition: all 0.2s ease; - } - - .info-value:hover { - background: rgba(160, 212, 104, 0.18); - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - } - - .info-value.long-text { - white-space: pre-wrap; - word-wrap: break-word; - max-width: 100%; - line-height: 1.4; - } - - .empty-state { - text-align: center; - padding: 50px 25px; - opacity: 0.6; - } - - .empty-state-icon { - font-size: 36px; - margin-bottom: 15px; - opacity: 0.5; - } - - /* Copy to clipboard functionality */ - .info-value[title]:hover::after { - content: '📋'; - position: absolute; - right: -20px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - opacity: 0.7; - } - - /* Smooth loading states */ - .section-body.loading { - opacity: 0.5; - pointer-events: none; - } - - .section-body.loading::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - border: 2px solid transparent; - border-top: 2px solid var(--info-panel-accent-color, #74b9ff); - border-radius: 50%; - animation: spin 1s linear infinite; - transform: translate(-50%, -50%); - } - - @keyframes spin { - to { transform: translate(-50%, -50%) rotate(360deg); } - } - - /* Enhanced scrollbar for better UX */ - .section-body::-webkit-scrollbar { - width: 4px; - } - - .section-body::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); - border-radius: 2px; - } - - .section-body::-webkit-scrollbar-thumb { - background: var(--info-panel-accent-color, #74b9ff); - border-radius: 2px; - opacity: 0.6; - } - - .section-body::-webkit-scrollbar-thumb:hover { - opacity: 1; - } - - .empty-state-text { - font-size: 16px; - font-style: italic; - } - - @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } - } - - .pulse { - animation: pulse 2s infinite; - } - `; -} diff --git a/vite.config.ts b/vite.config.ts index 0e1bbc1..4a8b539 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,13 +1,8 @@ /// import { defineConfig } from 'vite'; import { resolve } from 'path'; -import vue from '@vitejs/plugin-vue'; export default defineConfig({ - define: { - 'process.env': {} - }, - plugins: [vue()], build: { lib: { entry: { @@ -41,7 +36,7 @@ export default defineConfig({ } }, test: { - environment: 'happy-dom', // or jsdom, useful for component testing + environment: 'happy-dom', globals: true } }); diff --git a/web/info-panel.css b/web/info-panel.css new file mode 100644 index 0000000..ae0709f --- /dev/null +++ b/web/info-panel.css @@ -0,0 +1,615 @@ +/** + * ComfyUI MagnifyGlass - Info Panel Styles + * + * Complete CSS styles for the info panel component. + */ + +.magnify-info-panel { + border-radius: 12px; + backdrop-filter: blur(20px); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1); + overflow: hidden; + font-size: 16px; + line-height: 1.6; + pointer-events: auto; +} + +.magnify-info-panel * { + pointer-events: auto; +} + +.magnify-info-panel.theme-dark { + background: linear-gradient(135deg, rgba(0, 0, 0, 0.96), rgba(10, 10, 15, 0.96)); + color: var(--info-panel-text-color, #e0e0e0); + border: 1px solid rgba(100, 100, 120, 0.4); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 16px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(12px); +} + +.magnify-info-panel.theme-light { + background: linear-gradient(135deg, rgba(250, 250, 255, 0.96), rgba(240, 240, 250, 0.96)); + color: var(--info-panel-text-color, #2a2a2a); + border: 1px solid rgba(200, 200, 220, 0.5); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 16px rgba(0, 0, 0, 0.05); + backdrop-filter: blur(12px); +} + +/* Solarized Theme - warm, muted tones */ +.magnify-info-panel.theme-solarized { + background: linear-gradient(135deg, rgba(0, 43, 54, 0.96), rgba(7, 54, 66, 0.96)); + color: var(--info-panel-text-color, #839496); + border: 1px solid rgba(88, 110, 117, 0.5); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 16px rgba(0, 0, 0, 0.2); + backdrop-filter: blur(12px); + --info-panel-accent-color: #268bd2; +} + +/* Arc Theme - modern blue-gray */ +.magnify-info-panel.theme-arc { + background: linear-gradient(135deg, rgba(56, 60, 74, 0.96), rgba(64, 69, 82, 0.96)); + color: var(--info-panel-text-color, #d3dae3); + border: 1px solid rgba(90, 100, 120, 0.5); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25), 0 2px 16px rgba(0, 0, 0, 0.15); + backdrop-filter: blur(12px); + --info-panel-accent-color: #5294e2; +} + +/* Nord Theme - cool arctic colors */ +.magnify-info-panel.theme-nord { + background: linear-gradient(135deg, rgba(46, 52, 64, 0.96), rgba(59, 66, 82, 0.96)); + color: var(--info-panel-text-color, #d8dee9); + border: 1px solid rgba(76, 86, 106, 0.5); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25), 0 2px 16px rgba(0, 0, 0, 0.15); + backdrop-filter: blur(12px); + --info-panel-accent-color: #88c0d0; +} + +/* GitHub Theme - clean, professional */ +.magnify-info-panel.theme-github { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(246, 248, 250, 0.96)); + color: var(--info-panel-text-color, #24292e); + border: 1px solid rgba(225, 228, 232, 0.8); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 16px rgba(0, 0, 0, 0.05); + backdrop-filter: blur(12px); + --info-panel-accent-color: #0366d6; +} + +.magnify-info-panel.visible { + transform: translateY(0) !important; +} + +.magnify-info-panel.panel-pinned { + cursor: grab; + box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); +} + +.magnify-info-panel.panel-locked { + cursor: default !important; + box-shadow: 0 0 0 2px rgba(255, 69, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); +} + +.magnify-info-panel.panel-locked::after { + content: '📌'; + position: absolute; + top: 8px; + right: 8px; + font-size: 14px; + opacity: 0.7; + pointer-events: none; + z-index: 1; +} + +.magnify-info-panel.panel-pinned.panel-dragging { + cursor: grabbing; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7); + transform: scale(1.02) !important; + z-index: 100001 !important; + transition: none !important; +} + +.magnify-info-panel.panel-pinned::before { + content: ''; + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: linear-gradient(45deg, rgba(255, 215, 0, 0.05) 0%, transparent 100%); + pointer-events: none; + border-radius: inherit; +} + +.panel-dragging * { + pointer-events: none !important; + user-select: none !important; + transition: none !important; +} + +.magnify-info-panel.panel-minimized .panel-content { + max-height: 0 !important; + overflow: hidden; + transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.floating-controls { + position: fixed; + display: flex; + padding: 4px; + background: rgba(40, 40, 50, 0.95); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 4px; + opacity: 1; + z-index: 100000; + backdrop-filter: blur(8px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); +} + +.floating-controls.vertical-layout { + flex-direction: column; + gap: 4px; +} + +.floating-controls.horizontal-layout { + flex-direction: row; + gap: 4px; +} + +.control-btn { + background: rgba(60, 60, 70, 0.8); + border: 1px solid rgba(255, 255, 255, 0.2); + width: 28px; + height: 28px; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: #ddd; + border-radius: 3px; + transition: background-color 0.15s ease; +} + +.control-btn:hover { + background: rgba(80, 80, 90, 0.9); + color: white; +} + +.control-btn:active { + background: rgba(100, 100, 110, 0.9); +} + +.control-btn:disabled { + background: rgba(40, 40, 50, 0.5); + color: #666; + cursor: not-allowed; + opacity: 0.5; +} + +.control-btn.active { + background: rgba(255, 200, 50, 0.3); + border-color: rgba(255, 200, 50, 0.6); + color: #ffcc33; +} + +.control-btn.lock-btn.active { + background: rgba(255, 69, 0, 0.3); + border-color: rgba(255, 69, 0, 0.6); + color: #ff4500; +} + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 18px; + background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05)); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + user-select: none; +} + +.header-content { + display: flex; + align-items: center; + gap: 10px; +} + +.header-icon { + font-size: 18px; + opacity: 0.9; +} + +.header-title { + font-weight: 600; + font-size: 18px; +} + +.header-subtitle { + font-size: 14px; + opacity: 0.7; + font-style: italic; +} + +.header-controls { + display: flex; + gap: 6px; +} + +.minimize-btn { + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 6px; + color: inherit; + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; + font-weight: bold; + font-size: 16px; +} + +.minimize-btn:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.05); +} + +.minimize-btn:active { + transform: scale(0.95); +} + +.panel-content { + padding: 0; + max-height: calc(100% - 60px); + overflow-y: auto; + scrollbar-width: thin; +} + +.panel-content::-webkit-scrollbar { + width: 8px; +} + +.panel-content::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); +} + +.panel-content::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 3px; +} + +.info-section { + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 8px; + border-radius: 10px; + overflow: visible; + background: rgba(255, 255, 255, 0.02); + transition: all 0.3s ease; +} + +.info-section:hover { + background: rgba(255, 255, 255, 0.04); + transform: translateY(-1px); +} + +.section-header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + font-weight: 500; + border-radius: 8px; + margin: 3px; + position: relative; + overflow: hidden; +} + +.section-header::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.05) 50%, transparent 100%); + transform: translateX(-100%); + transition: transform 0.6s ease; +} + +.section-header:hover::before { + transform: translateX(100%); +} + +.section-header:hover { + background: rgba(255, 255, 255, 0.08); + transform: translateX(2px); + box-shadow: inset 2px 0 0 rgba(116, 185, 255, 0.5); +} + +.section-header:active { + background: rgba(255, 255, 255, 0.12); + transform: translateX(1px); +} + +.section-header.expanded { + background: rgba(116, 185, 255, 0.1); + border-left: 2px solid var(--info-panel-accent-color, #74b9ff); +} + +.info-section[data-section="node"] { + background: linear-gradient(135deg, rgba(116, 185, 255, 0.05), rgba(160, 212, 104, 0.05)); + border: 1px solid rgba(116, 185, 255, 0.2); + border-radius: 8px; + margin: 6px; +} + +.info-section[data-section="node"] .section-header { + background: linear-gradient(135deg, rgba(116, 185, 255, 0.15), rgba(160, 212, 104, 0.15)); + border-radius: 8px 8px 0 0; + cursor: default; +} + +.info-section[data-section="node"] .section-header:hover { + transform: none; + box-shadow: none; +} + +.info-section[data-section="node"] .section-title { + color: var(--info-panel-accent-color, #74b9ff); + font-weight: 700; +} + +.info-section[data-section="inspector"] { + background: linear-gradient(135deg, rgba(100, 150, 255, 0.05), rgba(74, 144, 226, 0.05)); + border: 1px solid rgba(100, 150, 255, 0.2); + border-radius: 8px; + margin: 6px; +} + +.info-section[data-section="inspector"] .section-header { + background: linear-gradient(135deg, rgba(100, 150, 255, 0.15), rgba(74, 144, 226, 0.15)); + border-radius: 8px 8px 0 0; +} + +.info-section[data-section="inspector"] .section-title { + color: #6496ff; + font-weight: 700; +} + +.info-section[data-section="media"] { + background: linear-gradient(135deg, rgba(253, 203, 110, 0.05), rgba(255, 215, 0, 0.05)); + border: 1px solid rgba(253, 203, 110, 0.2); + border-radius: 8px; + margin: 6px; +} + +.info-section[data-section="media"] .section-header { + background: linear-gradient(135deg, rgba(253, 203, 110, 0.15), rgba(255, 215, 0, 0.15)); + border-radius: 8px 8px 0 0; +} + +.info-section[data-section="media"] .section-title { + color: var(--info-panel-accent-color, #fdcb6e); + font-weight: 700; +} + +.section-icon { + font-size: 18px; + width: 22px; + text-align: center; +} + +.section-title { + flex: 1; + font-size: 16px; + font-weight: 600; + letter-spacing: 0.3px; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} + +.section-badge { + background: rgba(100, 150, 255, 0.2); + color: #8bb3ff; + padding: 3px 10px; + border-radius: 10px; + font-size: 13px; + font-weight: 500; +} + +.expand-icon { + font-size: 14px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + color: rgba(255, 255, 255, 0.6); + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background: rgba(255, 255, 255, 0.05); + margin-left: auto; +} + +.section-header:hover .expand-icon { + color: var(--info-panel-accent-color, #74b9ff); + background: rgba(116, 185, 255, 0.15); + transform: scale(1.1); +} + +.section-header.expanded .expand-icon { + transform: rotate(90deg) scale(1.1); + color: var(--info-panel-accent-color, #74b9ff); + background: rgba(116, 185, 255, 0.2); +} + +.section-content { + max-height: 0; + overflow: visible; + transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; + opacity: 0; +} + +.section-content.expanded { + max-height: 2000px; + opacity: 1; + overflow: visible; +} + +.section-body { + padding: 0 18px 16px 24px; +} + +.info-row { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + transition: all 0.2s ease; + border-radius: 4px; + margin: 0 -8px; + padding-left: 8px; + padding-right: 8px; +} + +.info-row:last-child { + border-bottom: none; +} + +.info-row:hover { + background: rgba(255, 255, 255, 0.03); + border-bottom-color: rgba(255, 255, 255, 0.08); +} + +.info-label { + font-weight: 500; + opacity: 0.85; + font-size: 15px; + min-width: 80px; + padding-right: 12px; + line-height: 1.5; +} + +.info-value { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 14px; + color: var(--info-panel-accent-color, #a0d468); + background: rgba(160, 212, 104, 0.12); + padding: 4px 10px; + border-radius: 6px; + max-width: 100%; + word-wrap: break-word; + word-break: break-word; + hyphens: auto; + line-height: 1.4; + text-align: left; + white-space: pre-wrap; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.info-value:hover { + background: rgba(160, 212, 104, 0.18); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.info-value.long-text { + white-space: pre-wrap; + word-wrap: break-word; + max-width: 100%; + line-height: 1.4; +} + +.empty-state { + text-align: center; + padding: 50px 25px; + opacity: 0.6; +} + +.empty-state-icon { + font-size: 36px; + margin-bottom: 15px; + opacity: 0.5; +} + +.empty-state-text { + font-size: 16px; + font-style: italic; +} + +/* Copy to clipboard functionality */ +.info-value[title]:hover::after { + content: '📋'; + position: absolute; + right: -20px; + top: 50%; + transform: translateY(-50%); + font-size: 12px; + opacity: 0.7; +} + +/* Smooth loading states */ +.section-body.loading { + opacity: 0.5; + pointer-events: none; +} + +.section-body.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 20px; + height: 20px; + border: 2px solid transparent; + border-top: 2px solid var(--info-panel-accent-color, #74b9ff); + border-radius: 50%; + animation: spin 1s linear infinite; + transform: translate(-50%, -50%); +} + +@keyframes spin { + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} + +/* Enhanced scrollbar for better UX */ +.section-body::-webkit-scrollbar { + width: 4px; +} + +.section-body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 2px; +} + +.section-body::-webkit-scrollbar-thumb { + background: var(--info-panel-accent-color, #74b9ff); + border-radius: 2px; + opacity: 0.6; +} + +.section-body::-webkit-scrollbar-thumb:hover { + opacity: 1; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.7; + } +} + +.pulse { + animation: pulse 2s infinite; +} \ No newline at end of file diff --git a/web/info-panel/StateManager.js b/web/info-panel/StateManager.js index 85e4c6d..0b4608e 100644 --- a/web/info-panel/StateManager.js +++ b/web/info-panel/StateManager.js @@ -62,49 +62,57 @@ class StateManager { } detectCurrentTheme() { let detectedTheme = "dark"; - const body = document.body; - const html = document.documentElement; - const vueApp = document.querySelector("#vue-app"); - const sidebar = document.querySelector('.comfy-menu, .sidebar, .menu, [class*="sidebar"], [class*="menu"]'); - const allElements = [body, html, vueApp, sidebar].filter(Boolean); - window.getComputedStyle(html); - window.getComputedStyle(body); - const backgrounds = allElements.map((el) => { - const styles = window.getComputedStyle(el); - return { - element: el.tagName || el.className, - backgroundColor: styles.backgroundColor, - color: styles.color - }; - }); - for (const bg of backgrounds) { - if (bg.backgroundColor && bg.backgroundColor !== "rgba(0, 0, 0, 0)" && bg.backgroundColor !== "transparent") { - const rgbMatch = bg.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); + try { + const colorPalette = getSettingValue("Comfy.ColorPalette", ""); + if (colorPalette) { + const paletteStr = String(colorPalette).toLowerCase(); + if (paletteStr.includes("solarized")) { + detectedTheme = "solarized"; + } else if (paletteStr.includes("arc")) { + detectedTheme = "arc"; + } else if (paletteStr.includes("nord")) { + detectedTheme = "nord"; + } else if (paletteStr.includes("github")) { + detectedTheme = "github"; + } else if (paletteStr.includes("light")) { + detectedTheme = "light"; + } else if (paletteStr.includes("dark")) { + detectedTheme = "dark"; + } + } + } catch (e) { + console.warn("ComfyUI Info Panel: Could not read theme from settings, using fallback detection"); + } + if (detectedTheme === "dark") { + const body = document.body; + const html = document.documentElement; + const dataTheme = body.getAttribute("data-theme") || html.getAttribute("data-theme"); + if (dataTheme) { + detectedTheme = dataTheme.toLowerCase(); + } + const classes = (body.className + " " + html.className).toLowerCase(); + if (classes.includes("theme-solarized") || classes.includes("solarized")) { + detectedTheme = "solarized"; + } else if (classes.includes("theme-arc") || classes.includes("arc-theme")) { + detectedTheme = "arc"; + } else if (classes.includes("theme-nord") || classes.includes("nord-theme")) { + detectedTheme = "nord"; + } else if (classes.includes("theme-github") || classes.includes("github-theme")) { + detectedTheme = "github"; + } else if (classes.includes("theme-light") || classes.includes("light-theme")) { + detectedTheme = "light"; + } + } + if (detectedTheme === "dark") { + const bodyStyles = window.getComputedStyle(document.body); + const bgColor = bodyStyles.backgroundColor; + if (bgColor && bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent") { + const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); if (rgbMatch) { const [, r, g, b] = rgbMatch.map(Number); const brightness = (r * 299 + g * 587 + b * 114) / 1e3; if (brightness > 180) { detectedTheme = "light"; - break; - } - } - } - } - if (detectedTheme === "dark") { - const lightElements = document.querySelectorAll("*"); - for (let i = 0; i < Math.min(lightElements.length, 50); i++) { - const el = lightElements[i]; - const styles = window.getComputedStyle(el); - const bgColor = styles.backgroundColor; - if (bgColor && bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent") { - const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); - if (rgbMatch) { - const [, r, g, b] = rgbMatch.map(Number); - const brightness = (r * 299 + g * 587 + b * 114) / 1e3; - if (brightness > 200) { - detectedTheme = "light"; - break; - } } } } diff --git a/web/info-panel/StateManager.js.map b/web/info-panel/StateManager.js.map index 748ef74..27f398e 100644 --- a/web/info-panel/StateManager.js.map +++ b/web/info-panel/StateManager.js.map @@ -1 +1 @@ -{"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 { getSettingValue } from '../shared/utils';\nimport { DEFAULT_PANEL_SETTINGS } from '../shared/settings';\n\nexport interface InfoPanelStateData {\n // Panel visibility and positioning\n isPanelVisible: boolean;\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 // 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: any;\n\n // Settings cache\n settings: { [key: string]: any };\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 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\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: {},\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 const body = document.body;\n const html = document.documentElement;\n const vueApp = document.querySelector('#vue-app');\n const sidebar = document.querySelector('.comfy-menu, .sidebar, .menu, [class*=\"sidebar\"], [class*=\"menu\"]');\n\n // Get all possible theme-related elements\n const allElements = [body, html, vueApp, sidebar].filter(Boolean) as Element[];\n\n // Check CSS variables\n const rootStyles = window.getComputedStyle(html);\n const bodyStyles = window.getComputedStyle(body);\n\n // Method 2: Analyze background colors of multiple elements\n const backgrounds = allElements.map(el => {\n const styles = window.getComputedStyle(el);\n return {\n element: el.tagName || el.className,\n backgroundColor: styles.backgroundColor,\n color: styles.color\n };\n });\n\n // Method 3: Check for light backgrounds\n for (const bg of backgrounds) {\n if (bg.backgroundColor && bg.backgroundColor !== 'rgba(0, 0, 0, 0)' && bg.backgroundColor !== 'transparent') {\n const rgbMatch = bg.backgroundColor.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) { // Higher threshold for light detection\n detectedTheme = 'light';\n break;\n }\n }\n }\n }\n\n // Method 5: Force light theme detection if we see light backgrounds\n if (detectedTheme === 'dark') {\n // Look for any element with very light background\n const lightElements = document.querySelectorAll('*');\n for (let i = 0; i < Math.min(lightElements.length, 50); i++) { // Check first 50 elements\n const el = lightElements[i];\n const styles = window.getComputedStyle(el);\n const bgColor = styles.backgroundColor;\n\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 > 200) { // Very light elements\n detectedTheme = 'light';\n break;\n }\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 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\n loadSettings(): void {\n Object.keys(DEFAULT_PANEL_SETTINGS).forEach(key => {\n // Skip the theme setting since we auto-detect it\n if (key !== \"🔍MagnifyGlass.InfoPanelTheme\") {\n this.state.settings[key] = getSettingValue(key, (DEFAULT_PANEL_SETTINGS as any)[key]);\n }\n });\n\n // Set theme to auto-detected value\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTheme\"] = this.state.currentTheme;\n }\n\n updateSettings(): { [key: string]: { old: any, new: any } } {\n const oldSettings = { ...this.state.settings };\n this.loadSettings();\n\n // Return what changed for reactive updates\n const changes: { [key: string]: { old: any, new: any } } = {};\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 }\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 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: any): void {\n this.state.currentInfo = info;\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 +{"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 { getSettingValue } from '../shared/utils';\nimport { DEFAULT_PANEL_SETTINGS } from '../shared/settings';\n\nexport interface InfoPanelStateData {\n // Panel visibility and positioning\n isPanelVisible: boolean;\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 // 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: any;\n\n // Settings cache\n settings: { [key: string]: any };\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 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\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: {},\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('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-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 }\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 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\n loadSettings(): void {\n Object.keys(DEFAULT_PANEL_SETTINGS).forEach(key => {\n // Skip the theme setting since we auto-detect it\n if (key !== \"🔍MagnifyGlass.InfoPanelTheme\") {\n this.state.settings[key] = getSettingValue(key, (DEFAULT_PANEL_SETTINGS as any)[key]);\n }\n });\n\n // Set theme to auto-detected value\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTheme\"] = this.state.currentTheme;\n }\n\n updateSettings(): { [key: string]: { old: any, new: any } } {\n const oldSettings = { ...this.state.settings };\n this.loadSettings();\n\n // Return what changed for reactive updates\n const changes: { [key: string]: { old: any, new: any } } = {};\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 }\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 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: any): void {\n this.state.currentInfo = info;\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 08b01bf..5cdb8dd 100644 --- a/web/info-panel/UIManager.js +++ b/web/info-panel/UIManager.js @@ -1,7 +1,6 @@ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); -import { getInfoPanelCSS } from "./styles.js"; class UIManager { constructor(stateManager) { __publicField(this, "stateManager"); @@ -669,14 +668,16 @@ class UIManager { return String(value); } /** - * Inject CSS styles. + * Inject CSS styles by loading external stylesheet. */ injectStyles() { if (!document.getElementById("magnify-info-panel-styles-v2")) { - const style = document.createElement("style"); - style.id = "magnify-info-panel-styles-v2"; - style.textContent = getInfoPanelCSS(); - document.head.appendChild(style); + const link = document.createElement("link"); + link.id = "magnify-info-panel-styles-v2"; + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "extensions/comfyui-magnifyglass/info-panel.css"; + document.head.appendChild(link); } } cleanup() { diff --git a/web/info-panel/UIManager.js.map b/web/info-panel/UIManager.js.map index 5b697af..05debc4 100644 --- a/web/info-panel/UIManager.js.map +++ b/web/info-panel/UIManager.js.map @@ -1 +1 @@ -{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\n// import { Z_INDEX } from '../shared/constants'; // Not used in original code? Kept for reference if needed\nimport { getInfoPanelCSS } from './styles';\nimport { StateManager } from './StateManager';\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\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\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = \"comfyui-magnify-info-panel-pro-v2\";\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n
\n
🔍
\n
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 document.body.appendChild(this.elements.panel);\n\n // Create floating controls after panel is in DOM\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.ShowHoveringControls\"]) {\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\"; // Default to vertical\n this.elements.controls.innerHTML = `\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 // Set initial layout based on settings\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\";\n this.updateControlsLayout(controlsPosition);\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\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.textContent = this.stateManager.state.isPanelPinned ? \"🔒\" : \"🔓\";\n }\n\n if (lockBtn) {\n // Only show/enable lock button when pinned\n lockBtn.style.display = this.stateManager.state.isPanelPinned ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Upin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n visibilityBtn.classList.toggle('active', this.stateManager.state.isPanelVisible);\n visibilityBtn.title = this.stateManager.state.isPanelVisible ? \"Show Panel\" : \"Hide Panel\";\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', this.stateManager.state.isGlassPreviewVisible);\n glassBtn.title = this.stateManager.state.isGlassPreviewVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\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 * 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 custom text color via CSS variable if provided\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"];\n if (textColor) {\n // Ensure color has # symbol\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n // Apply custom accent color via CSS variable if provided\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (accentColor) {\n // Ensure color has # symbol\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // Apply opacity setting if panel is visible (convert percentage to decimal)\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n }\n\n this.elements.panel.style.cssText = `\n position: absolute;\n width: ${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px;\n max-height: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}px;\n z-index: 99999;\n display: none;\n opacity: 0;\n transform: translateY(-10px);\n transition: ${settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none'};\n pointer-events: auto;\n user-select: none;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}\n ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''}\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 if (this.elements.controls) {\n this.elements.controls.style.display = \"flex\";\n }\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\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 }\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 setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n if (this.elements.controls) {\n this.elements.controls.style.display = \"none\";\n }\n }\n }, this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 300 : 0);\n this.stateManager.state.isPanelVisible = false;\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"]) {\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: '🔍',\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: '📷',\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 = [\n { label: 'Title', value: info.hoveredNode.title }\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 checkpointInfo = this.getCheckpointInfo(info.hoveredNode);\n if (checkpointInfo) {\n nodeContent.push({ label: 'Model', value: checkpointInfo });\n }\n\n const imageInfo = this.getImageInfo(info.hoveredNode);\n if (imageInfo) {\n if (typeof imageInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imageInfo });\n } else if (typeof imageInfo === 'object' && imageInfo !== null) {\n const imgInfoAny = imageInfo as any;\n nodeContent.push({ label: 'Image Size', value: `${imgInfoAny.width}×${imgInfoAny.height}` });\n if (imgInfoAny.src) {\n nodeContent.push({ label: 'Image Source', value: imgInfoAny.src });\n }\n }\n }\n\n const textBoxContent = this.getTextBoxContent(info.hoveredNode);\n if (textBoxContent) {\n nodeContent.push({ label: 'Text', value: textBoxContent });\n }\n }\n\n const importantParameters = this.getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: '🎯',\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n
\n
📍
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n
\n
\n ${section.icon}\n ${section.title}\n ${section.badge ? `${section.badge}` : ''}\n ${section.id !== 'node' ? '▶' : ''}\n
\n
\n
\n ${section.content.map((item: any) => {\n const value = this.formatValue(item.value, item.label);\n const valueClass = this.getValueClass(item.value);\n const valueAttributes = this.getValueAttributes(item.value);\n return `\n
\n ${item.label}\n ${value}\n
`;\n }).join('')}\n
\n
\n
\n `).join('');\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 * Format a value for display.\n * @param value \n * @param label\n * @returns \n */\n formatValue(value: any, 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 str;\n }\n\n // Show full text for very long values (no truncation)\n return str;\n }\n\n /**\n * Get value class for styling.\n * @param value \n * @returns \n */\n getValueClass(value: any): string {\n if (!value) return '';\n\n const str = String(value);\n let 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 value attributes.\n * @param value \n * @returns \n */\n getValueAttributes(value: any): 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=\"${str.replace(/\"/g, '"')}\"`;\n }\n\n return '';\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 = 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 // ============== Helper methods for node information extraction ==============\n\n /**\n * Get checkpoint/model info from a node.\n * @param nodeInfo \n * @returns \n */\n getCheckpointInfo(nodeInfo: any): 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 * Get image info from a node.\n * @param nodeInfo \n * @returns \n */\n getImageInfo(nodeInfo: any): string | object | 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 widget.value;\n }\n }\n }\n\n if (nodeInfo.properties && nodeInfo.properties.img) {\n const img = nodeInfo.properties.img;\n return {\n width: img.width || \"unknown\",\n height: img.height || \"unknown\",\n src: img.src ? img.src.split(/[\\/\\\\]/).pop() : \"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 \n * @returns \n */\n getTextBoxContent(nodeInfo: any): 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\n // Show full text without truncation\n return widget.value;\n }\n }\n }\n return null;\n }\n\n /**\n * Get important node parameters based on node type.\n * @param nodeInfo \n * @returns \n */\n getImportantNodeParameters(nodeInfo: any): any[] {\n const parameters: any[] = [];\n\n // For complex nodes that have many parameters, show ALL widgets\n const nodeType = nodeInfo.type ? nodeInfo.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = nodeInfo.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 if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n // Show all widgets for complex nodes, but filter out some duplicates\n if (widget.name && widget.name !== '') {\n const widgetName = widget.name.toLowerCase();\n\n // Skip some generic widget names that might be duplicates\n if (widgetName.includes('title') ||\n widgetName === 'node' ||\n widgetName === 'id' ||\n widgetName === 'type' ||\n widgetName === 'mode') {\n continue;\n }\n\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n return parameters;\n }\n\n // Special handling for Save nodes - only show essential parameters\n let importantParams: string[];\n if (isSaveNode) {\n importantParams = [\n 'filename_prefix', 'filename', 'directory', 'path',\n 'format', 'quality', 'extension'\n ];\n } else {\n // For other nodes, use the filtered list but avoid duplicates with specific extractions\n importantParams = [\n 'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',\n 'positive', 'negative', 'width', 'height', 'denoise', 'strength',\n 'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',\n // KSampler specific parameters (for other samplers)\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 // Additional common parameters (but avoid duplicates with specific extractors)\n 'model', 'vae', 'clip', 'lora', 'checkpoint',\n 'latent', 'image', 'mask', 'filename', 'directory',\n 'prompt', 'conditioning', 'filename_prefix',\n // New detection vocabulary\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',\n // Additional detection vocabulary\n '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',\n // Camera and 3D parameters\n 'camera_pose', 'fx', 'cx', 'fy', 'cy'\n // Note: 'text' and 'string' removed to avoid duplication with getTextBoxContent\n ];\n }\n\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n const paramName = widget.name.toLowerCase();\n if (importantParams.some(param => paramName.includes(param))) {\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n\n return parameters;\n }\n\n /**\n * Format widget value for display.\n * @param value \n * @returns \n */\n formatWidgetValue(value: any): 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 /**\n * Inject CSS styles.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const style = document.createElement('style');\n style.id = 'magnify-info-panel-styles-v2';\n style.textContent = getInfoPanelCSS();\n document.head.appendChild(style);\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\n// import { Z_INDEX } from '../shared/constants'; // Not used in original code? Kept for reference if needed\nimport { StateManager } from './StateManager';\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\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\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = \"comfyui-magnify-info-panel-pro-v2\";\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n
\n
🔍
\n
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 document.body.appendChild(this.elements.panel);\n\n // Create floating controls after panel is in DOM\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.ShowHoveringControls\"]) {\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\"; // Default to vertical\n this.elements.controls.innerHTML = `\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 // Set initial layout based on settings\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\";\n this.updateControlsLayout(controlsPosition);\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\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.textContent = this.stateManager.state.isPanelPinned ? \"🔒\" : \"🔓\";\n }\n\n if (lockBtn) {\n // Only show/enable lock button when pinned\n lockBtn.style.display = this.stateManager.state.isPanelPinned ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Upin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n visibilityBtn.classList.toggle('active', this.stateManager.state.isPanelVisible);\n visibilityBtn.title = this.stateManager.state.isPanelVisible ? \"Show Panel\" : \"Hide Panel\";\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', this.stateManager.state.isGlassPreviewVisible);\n glassBtn.title = this.stateManager.state.isGlassPreviewVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\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 * 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 custom text color via CSS variable if provided\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"];\n if (textColor) {\n // Ensure color has # symbol\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n // Apply custom accent color via CSS variable if provided\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (accentColor) {\n // Ensure color has # symbol\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // Apply opacity setting if panel is visible (convert percentage to decimal)\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n }\n\n this.elements.panel.style.cssText = `\n position: absolute;\n width: ${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px;\n max-height: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}px;\n z-index: 99999;\n display: none;\n opacity: 0;\n transform: translateY(-10px);\n transition: ${settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none'};\n pointer-events: auto;\n user-select: none;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}\n ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''}\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 if (this.elements.controls) {\n this.elements.controls.style.display = \"flex\";\n }\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\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 }\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 setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n if (this.elements.controls) {\n this.elements.controls.style.display = \"none\";\n }\n }\n }, this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 300 : 0);\n this.stateManager.state.isPanelVisible = false;\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"]) {\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: '🔍',\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: '📷',\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 = [\n { label: 'Title', value: info.hoveredNode.title }\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 checkpointInfo = this.getCheckpointInfo(info.hoveredNode);\n if (checkpointInfo) {\n nodeContent.push({ label: 'Model', value: checkpointInfo });\n }\n\n const imageInfo = this.getImageInfo(info.hoveredNode);\n if (imageInfo) {\n if (typeof imageInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imageInfo });\n } else if (typeof imageInfo === 'object' && imageInfo !== null) {\n const imgInfoAny = imageInfo as any;\n nodeContent.push({ label: 'Image Size', value: `${imgInfoAny.width}×${imgInfoAny.height}` });\n if (imgInfoAny.src) {\n nodeContent.push({ label: 'Image Source', value: imgInfoAny.src });\n }\n }\n }\n\n const textBoxContent = this.getTextBoxContent(info.hoveredNode);\n if (textBoxContent) {\n nodeContent.push({ label: 'Text', value: textBoxContent });\n }\n }\n\n const importantParameters = this.getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: '🎯',\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n
\n
📍
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n
\n
\n ${section.icon}\n ${section.title}\n ${section.badge ? `${section.badge}` : ''}\n ${section.id !== 'node' ? '▶' : ''}\n
\n
\n
\n ${section.content.map((item: any) => {\n const value = this.formatValue(item.value, item.label);\n const valueClass = this.getValueClass(item.value);\n const valueAttributes = this.getValueAttributes(item.value);\n return `\n
\n ${item.label}\n ${value}\n
`;\n }).join('')}\n
\n
\n
\n `).join('');\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 * Format a value for display.\n * @param value \n * @param label\n * @returns \n */\n formatValue(value: any, 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 str;\n }\n\n // Show full text for very long values (no truncation)\n return str;\n }\n\n /**\n * Get value class for styling.\n * @param value \n * @returns \n */\n getValueClass(value: any): string {\n if (!value) return '';\n\n const str = String(value);\n let 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 value attributes.\n * @param value \n * @returns \n */\n getValueAttributes(value: any): 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=\"${str.replace(/\"/g, '"')}\"`;\n }\n\n return '';\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 = 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 // ============== Helper methods for node information extraction ==============\n\n /**\n * Get checkpoint/model info from a node.\n * @param nodeInfo \n * @returns \n */\n getCheckpointInfo(nodeInfo: any): 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 * Get image info from a node.\n * @param nodeInfo \n * @returns \n */\n getImageInfo(nodeInfo: any): string | object | 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 widget.value;\n }\n }\n }\n\n if (nodeInfo.properties && nodeInfo.properties.img) {\n const img = nodeInfo.properties.img;\n return {\n width: img.width || \"unknown\",\n height: img.height || \"unknown\",\n src: img.src ? img.src.split(/[\\/\\\\]/).pop() : \"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 \n * @returns \n */\n getTextBoxContent(nodeInfo: any): 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\n // Show full text without truncation\n return widget.value;\n }\n }\n }\n return null;\n }\n\n /**\n * Get important node parameters based on node type.\n * @param nodeInfo \n * @returns \n */\n getImportantNodeParameters(nodeInfo: any): any[] {\n const parameters: any[] = [];\n\n // For complex nodes that have many parameters, show ALL widgets\n const nodeType = nodeInfo.type ? nodeInfo.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = nodeInfo.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 if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n // Show all widgets for complex nodes, but filter out some duplicates\n if (widget.name && widget.name !== '') {\n const widgetName = widget.name.toLowerCase();\n\n // Skip some generic widget names that might be duplicates\n if (widgetName.includes('title') ||\n widgetName === 'node' ||\n widgetName === 'id' ||\n widgetName === 'type' ||\n widgetName === 'mode') {\n continue;\n }\n\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n return parameters;\n }\n\n // Special handling for Save nodes - only show essential parameters\n let importantParams: string[];\n if (isSaveNode) {\n importantParams = [\n 'filename_prefix', 'filename', 'directory', 'path',\n 'format', 'quality', 'extension'\n ];\n } else {\n // For other nodes, use the filtered list but avoid duplicates with specific extractions\n importantParams = [\n 'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',\n 'positive', 'negative', 'width', 'height', 'denoise', 'strength',\n 'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',\n // KSampler specific parameters (for other samplers)\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 // Additional common parameters (but avoid duplicates with specific extractors)\n 'model', 'vae', 'clip', 'lora', 'checkpoint',\n 'latent', 'image', 'mask', 'filename', 'directory',\n 'prompt', 'conditioning', 'filename_prefix',\n // New detection vocabulary\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',\n // Additional detection vocabulary\n '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',\n // Camera and 3D parameters\n 'camera_pose', 'fx', 'cx', 'fy', 'cy'\n // Note: 'text' and 'string' removed to avoid duplication with getTextBoxContent\n ];\n }\n\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n const paramName = widget.name.toLowerCase();\n if (importantParams.some(param => paramName.includes(param))) {\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n\n return parameters;\n }\n\n /**\n * Format widget value for display.\n * @param value \n * @returns \n */\n formatWidgetValue(value: any): 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 /**\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 cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/styles.js b/web/info-panel/styles.js deleted file mode 100644 index 552f1bf..0000000 --- a/web/info-panel/styles.js +++ /dev/null @@ -1,568 +0,0 @@ -function getInfoPanelCSS() { - return ` - .magnify-info-panel { - border-radius: 12px; - backdrop-filter: blur(20px); - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1); - overflow: hidden; - font-size: 16px; - line-height: 1.6; - pointer-events: auto; - } - - .magnify-info-panel * { - pointer-events: auto; - } - - .magnify-info-panel.theme-dark { - background: linear-gradient(135deg, rgba(0, 0, 0, 0.96), rgba(10, 10, 15, 0.96)); - color: var(--info-panel-text-color, #e0e0e0); - border: 1px solid rgba(100, 100, 120, 0.4); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 16px rgba(0, 0, 0, 0.2); - backdrop-filter: blur(12px); - } - - .magnify-info-panel.theme-light { - background: linear-gradient(135deg, rgba(250, 250, 255, 0.96), rgba(240, 240, 250, 0.96)); - color: var(--info-panel-text-color, #2a2a2a); - border: 1px solid rgba(200, 200, 220, 0.5); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 16px rgba(0, 0, 0, 0.05); - backdrop-filter: blur(12px); - } - - .magnify-info-panel.visible { - transform: translateY(0) !important; - } - - .magnify-info-panel.panel-pinned { - cursor: grab; - box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); - } - - .magnify-info-panel.panel-locked { - cursor: default !important; - box-shadow: 0 0 0 2px rgba(255, 69, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3); - } - - .magnify-info-panel.panel-locked::after { - content: '📌'; - position: absolute; - top: 8px; - right: 8px; - font-size: 14px; - opacity: 0.7; - pointer-events: none; - z-index: 1; - } - - .magnify-info-panel.panel-pinned.panel-dragging { - cursor: grabbing; - box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7); - transform: scale(1.02) !important; - z-index: 100001 !important; - transition: none !important; - } - - .magnify-info-panel.panel-pinned::before { - content: ''; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - background: linear-gradient(45deg, rgba(255, 215, 0, 0.05) 0%, transparent 100%); - pointer-events: none; - border-radius: inherit; - } - - .panel-dragging * { - pointer-events: none !important; - user-select: none !important; - transition: none !important; - } - - .magnify-info-panel.panel-minimized .panel-content { - max-height: 0 !important; - overflow: hidden; - transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); - } - - .floating-controls { - position: fixed; - display: flex; - padding: 4px; - background: rgba(40, 40, 50, 0.95); - border: 1px solid rgba(255, 255, 255, 0.3); - border-radius: 4px; - opacity: 1; - z-index: 100000; - backdrop-filter: blur(8px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); - } - - .floating-controls.vertical-layout { - flex-direction: column; - gap: 4px; - } - - .floating-controls.horizontal-layout { - flex-direction: row; - gap: 4px; - } - - .control-btn { - background: rgba(60, 60, 70, 0.8); - border: 1px solid rgba(255, 255, 255, 0.2); - width: 28px; - height: 28px; - font-size: 14px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - color: #ddd; - border-radius: 3px; - transition: background-color 0.15s ease; - } - - .control-btn:hover { - background: rgba(80, 80, 90, 0.9); - color: white; - } - - .control-btn:active { - background: rgba(100, 100, 110, 0.9); - } - - .control-btn:disabled { - background: rgba(40, 40, 50, 0.5); - color: #666; - cursor: not-allowed; - opacity: 0.5; - } - - .control-btn.active { - background: rgba(255, 200, 50, 0.3); - border-color: rgba(255, 200, 50, 0.6); - color: #ffcc33; - } - - .control-btn.lock-btn.active { - background: rgba(255, 69, 0, 0.3); - border-color: rgba(255, 69, 0, 0.6); - color: #ff4500; - } - - .panel-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 14px 18px; - background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05)); - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - user-select: none; - } - - .header-content { - display: flex; - align-items: center; - gap: 10px; - } - - .header-icon { - font-size: 18px; - opacity: 0.9; - } - - .header-title { - font-weight: 600; - font-size: 18px; - } - - .header-subtitle { - font-size: 14px; - opacity: 0.7; - font-style: italic; - } - - .header-controls { - display: flex; - gap: 6px; - } - - .minimize-btn { - background: rgba(255, 255, 255, 0.1); - border: none; - border-radius: 6px; - color: inherit; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s; - font-weight: bold; - font-size: 16px; - } - - .minimize-btn:hover { - background: rgba(255, 255, 255, 0.2); - transform: scale(1.05); - } - - .minimize-btn:active { - transform: scale(0.95); - } - - .panel-content { - padding: 0; - max-height: calc(100% - 60px); - overflow-y: auto; - scrollbar-width: thin; - } - - .panel-content::-webkit-scrollbar { - width: 8px; - } - - .panel-content::-webkit-scrollbar-track { - background: rgba(0, 0, 0, 0.1); - } - - .panel-content::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.3); - border-radius: 3px; - } - - .info-section { - border-bottom: 1px solid rgba(255, 255, 255, 0.1); - margin-bottom: 8px; - border-radius: 10px; - overflow: visible; - background: rgba(255, 255, 255, 0.02); - transition: all 0.3s ease; - } - - .info-section:hover { - background: rgba(255, 255, 255, 0.04); - transform: translateY(-1px); - } - - .section-header { - display: flex; - align-items: center; - gap: 12px; - padding: 16px 20px; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - font-weight: 500; - border-radius: 8px; - margin: 3px; - position: relative; - overflow: hidden; - } - - .section-header::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.05) 50%, transparent 100%); - transform: translateX(-100%); - transition: transform 0.6s ease; - } - - .section-header:hover::before { - transform: translateX(100%); - } - - .section-header:hover { - background: rgba(255, 255, 255, 0.08); - transform: translateX(2px); - box-shadow: inset 2px 0 0 rgba(116, 185, 255, 0.5); - } - - .section-header:active { - background: rgba(255, 255, 255, 0.12); - transform: translateX(1px); - } - - .section-header.expanded { - background: rgba(116, 185, 255, 0.1); - border-left: 2px solid var(--info-panel-accent-color, #74b9ff); - } - - .info-section[data-section="node"] { - background: linear-gradient(135deg, rgba(116, 185, 255, 0.05), rgba(160, 212, 104, 0.05)); - border: 1px solid rgba(116, 185, 255, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="node"] .section-header { - background: linear-gradient(135deg, rgba(116, 185, 255, 0.15), rgba(160, 212, 104, 0.15)); - border-radius: 8px 8px 0 0; - cursor: default; - } - - .info-section[data-section="node"] .section-header:hover { - transform: none; - box-shadow: none; - } - - .info-section[data-section="node"] .section-title { - color: var(--info-panel-accent-color, #74b9ff); - font-weight: 700; - } - - .info-section[data-section="inspector"] { - background: linear-gradient(135deg, rgba(100, 150, 255, 0.05), rgba(74, 144, 226, 0.05)); - border: 1px solid rgba(100, 150, 255, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="inspector"] .section-header { - background: linear-gradient(135deg, rgba(100, 150, 255, 0.15), rgba(74, 144, 226, 0.15)); - border-radius: 8px 8px 0 0; - } - - .info-section[data-section="inspector"] .section-title { - color: #6496ff; - font-weight: 700; - } - - .info-section[data-section="media"] { - background: linear-gradient(135deg, rgba(253, 203, 110, 0.05), rgba(255, 215, 0, 0.05)); - border: 1px solid rgba(253, 203, 110, 0.2); - border-radius: 8px; - margin: 6px; - } - - .info-section[data-section="media"] .section-header { - background: linear-gradient(135deg, rgba(253, 203, 110, 0.15), rgba(255, 215, 0, 0.15)); - border-radius: 8px 8px 0 0; - } - - .info-section[data-section="media"] .section-title { - color: var(--info-panel-accent-color, #fdcb6e); - font-weight: 700; - } - - .section-icon { - font-size: 18px; - width: 22px; - text-align: center; - } - - .section-title { - flex: 1; - font-size: 16px; - font-weight: 600; - letter-spacing: 0.3px; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); - } - - .section-badge { - background: rgba(100, 150, 255, 0.2); - color: #8bb3ff; - padding: 3px 10px; - border-radius: 10px; - font-size: 13px; - font-weight: 500; - } - - .expand-icon { - font-size: 14px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: rgba(255, 255, 255, 0.6); - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - background: rgba(255, 255, 255, 0.05); - margin-left: auto; - } - - .section-header:hover .expand-icon { - color: var(--info-panel-accent-color, #74b9ff); - background: rgba(116, 185, 255, 0.15); - transform: scale(1.1); - } - - .section-header.expanded .expand-icon { - transform: rotate(90deg) scale(1.1); - color: var(--info-panel-accent-color, #74b9ff); - background: rgba(116, 185, 255, 0.2); - } - - .section-content { - max-height: 0; - overflow: visible; - transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; - opacity: 0; - } - - .section-content.expanded { - max-height: 2000px; - opacity: 1; - overflow: visible; - } - - .section-body { - padding: 0 18px 16px 24px; - } - - .info-row { - display: flex; - justify-content: space-between; - align-items: flex-start; - padding: 10px 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); - transition: all 0.2s ease; - border-radius: 4px; - margin: 0 -8px; - padding-left: 8px; - padding-right: 8px; - } - - .info-row:last-child { - border-bottom: none; - } - - .info-row:hover { - background: rgba(255, 255, 255, 0.03); - border-bottom-color: rgba(255, 255, 255, 0.08); - } - - .info-label { - font-weight: 500; - opacity: 0.85; - font-size: 15px; - min-width: 80px; - padding-right: 12px; - line-height: 1.5; - } - - .info-value { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 14px; - color: var(--info-panel-accent-color, #a0d468); - background: rgba(160, 212, 104, 0.12); - padding: 4px 10px; - border-radius: 6px; - max-width: 100%; - word-wrap: break-word; - word-break: break-word; - hyphens: auto; - line-height: 1.4; - text-align: left; - white-space: pre-wrap; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - transition: all 0.2s ease; - } - - .info-value:hover { - background: rgba(160, 212, 104, 0.18); - transform: translateY(-1px); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); - } - - .info-value.long-text { - white-space: pre-wrap; - word-wrap: break-word; - max-width: 100%; - line-height: 1.4; - } - - .empty-state { - text-align: center; - padding: 50px 25px; - opacity: 0.6; - } - - .empty-state-icon { - font-size: 36px; - margin-bottom: 15px; - opacity: 0.5; - } - - /* Copy to clipboard functionality */ - .info-value[title]:hover::after { - content: '📋'; - position: absolute; - right: -20px; - top: 50%; - transform: translateY(-50%); - font-size: 12px; - opacity: 0.7; - } - - /* Smooth loading states */ - .section-body.loading { - opacity: 0.5; - pointer-events: none; - } - - .section-body.loading::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - border: 2px solid transparent; - border-top: 2px solid var(--info-panel-accent-color, #74b9ff); - border-radius: 50%; - animation: spin 1s linear infinite; - transform: translate(-50%, -50%); - } - - @keyframes spin { - to { transform: translate(-50%, -50%) rotate(360deg); } - } - - /* Enhanced scrollbar for better UX */ - .section-body::-webkit-scrollbar { - width: 4px; - } - - .section-body::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.05); - border-radius: 2px; - } - - .section-body::-webkit-scrollbar-thumb { - background: var(--info-panel-accent-color, #74b9ff); - border-radius: 2px; - opacity: 0.6; - } - - .section-body::-webkit-scrollbar-thumb:hover { - opacity: 1; - } - - .empty-state-text { - font-size: 16px; - font-style: italic; - } - - @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } - } - - .pulse { - animation: pulse 2s infinite; - } - `; -} -export { - getInfoPanelCSS -}; -//# sourceMappingURL=styles.js.map diff --git a/web/info-panel/styles.js.map b/web/info-panel/styles.js.map deleted file mode 100644 index 1e9cc8e..0000000 --- a/web/info-panel/styles.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"styles.js","sources":["../../src/info-panel/styles.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Styles (TypeScript)\n * \n * Complete CSS styles for the info panel component.\n */\n\n/**\n * Get the complete CSS for the info panel.\n * @returns CSS string\n */\nexport function getInfoPanelCSS(): string {\n return `\n .magnify-info-panel {\n border-radius: 12px;\n backdrop-filter: blur(20px);\n box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1);\n overflow: hidden;\n font-size: 16px;\n line-height: 1.6;\n pointer-events: auto;\n }\n \n .magnify-info-panel * {\n pointer-events: auto;\n }\n \n .magnify-info-panel.theme-dark {\n background: linear-gradient(135deg, rgba(0, 0, 0, 0.96), rgba(10, 10, 15, 0.96));\n color: var(--info-panel-text-color, #e0e0e0);\n border: 1px solid rgba(100, 100, 120, 0.4);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 16px rgba(0, 0, 0, 0.2);\n backdrop-filter: blur(12px);\n }\n \n .magnify-info-panel.theme-light {\n background: linear-gradient(135deg, rgba(250, 250, 255, 0.96), rgba(240, 240, 250, 0.96));\n color: var(--info-panel-text-color, #2a2a2a);\n border: 1px solid rgba(200, 200, 220, 0.5);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1), 0 2px 16px rgba(0, 0, 0, 0.05);\n backdrop-filter: blur(12px);\n }\n \n .magnify-info-panel.visible {\n transform: translateY(0) !important;\n }\n \n .magnify-info-panel.panel-pinned {\n cursor: grab;\n box-shadow: 0 0 0 2px rgba(255, 215, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3);\n }\n \n .magnify-info-panel.panel-locked {\n cursor: default !important;\n box-shadow: 0 0 0 2px rgba(255, 69, 0, 0.5), 0 20px 40px rgba(0, 0, 0, 0.3);\n }\n \n .magnify-info-panel.panel-locked::after {\n content: '📌';\n position: absolute;\n top: 8px;\n right: 8px;\n font-size: 14px;\n opacity: 0.7;\n pointer-events: none;\n z-index: 1;\n }\n \n .magnify-info-panel.panel-pinned.panel-dragging {\n cursor: grabbing;\n box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7);\n transform: scale(1.02) !important;\n z-index: 100001 !important;\n transition: none !important;\n }\n \n .magnify-info-panel.panel-pinned::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(45deg, rgba(255, 215, 0, 0.05) 0%, transparent 100%);\n pointer-events: none;\n border-radius: inherit;\n }\n \n .panel-dragging * {\n pointer-events: none !important;\n user-select: none !important;\n transition: none !important;\n }\n \n .magnify-info-panel.panel-minimized .panel-content {\n max-height: 0 !important;\n overflow: hidden;\n transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n \n .floating-controls {\n position: fixed;\n display: flex;\n padding: 4px;\n background: rgba(40, 40, 50, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.3);\n border-radius: 4px;\n opacity: 1;\n z-index: 100000;\n backdrop-filter: blur(8px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .floating-controls.vertical-layout {\n flex-direction: column;\n gap: 4px;\n }\n \n .floating-controls.horizontal-layout {\n flex-direction: row;\n gap: 4px;\n }\n \n .control-btn {\n background: rgba(60, 60, 70, 0.8);\n border: 1px solid rgba(255, 255, 255, 0.2);\n width: 28px;\n height: 28px;\n font-size: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n color: #ddd;\n border-radius: 3px;\n transition: background-color 0.15s ease;\n }\n \n .control-btn:hover {\n background: rgba(80, 80, 90, 0.9);\n color: white;\n }\n \n .control-btn:active {\n background: rgba(100, 100, 110, 0.9);\n }\n \n .control-btn:disabled {\n background: rgba(40, 40, 50, 0.5);\n color: #666;\n cursor: not-allowed;\n opacity: 0.5;\n }\n \n .control-btn.active {\n background: rgba(255, 200, 50, 0.3);\n border-color: rgba(255, 200, 50, 0.6);\n color: #ffcc33;\n }\n \n .control-btn.lock-btn.active {\n background: rgba(255, 69, 0, 0.3);\n border-color: rgba(255, 69, 0, 0.6);\n color: #ff4500;\n }\n \n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n background: linear-gradient(135deg, rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.05));\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n user-select: none;\n }\n \n .header-content {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n \n .header-icon {\n font-size: 18px;\n opacity: 0.9;\n }\n \n .header-title {\n font-weight: 600;\n font-size: 18px;\n }\n \n .header-subtitle {\n font-size: 14px;\n opacity: 0.7;\n font-style: italic;\n }\n \n .header-controls {\n display: flex;\n gap: 6px;\n }\n \n .minimize-btn {\n background: rgba(255, 255, 255, 0.1);\n border: none;\n border-radius: 6px;\n color: inherit;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n transition: all 0.2s;\n font-weight: bold;\n font-size: 16px;\n }\n \n .minimize-btn:hover {\n background: rgba(255, 255, 255, 0.2);\n transform: scale(1.05);\n }\n \n .minimize-btn:active {\n transform: scale(0.95);\n }\n \n .panel-content {\n padding: 0;\n max-height: calc(100% - 60px);\n overflow-y: auto;\n scrollbar-width: thin;\n }\n \n .panel-content::-webkit-scrollbar {\n width: 8px;\n }\n \n .panel-content::-webkit-scrollbar-track {\n background: rgba(0, 0, 0, 0.1);\n }\n \n .panel-content::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.3);\n border-radius: 3px;\n }\n \n .info-section {\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n margin-bottom: 8px;\n border-radius: 10px;\n overflow: visible;\n background: rgba(255, 255, 255, 0.02);\n transition: all 0.3s ease;\n }\n \n .info-section:hover {\n background: rgba(255, 255, 255, 0.04);\n transform: translateY(-1px);\n }\n \n .section-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n font-weight: 500;\n border-radius: 8px;\n margin: 3px;\n position: relative;\n overflow: hidden;\n }\n \n .section-header::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.05) 50%, transparent 100%);\n transform: translateX(-100%);\n transition: transform 0.6s ease;\n }\n \n .section-header:hover::before {\n transform: translateX(100%);\n }\n \n .section-header:hover {\n background: rgba(255, 255, 255, 0.08);\n transform: translateX(2px);\n box-shadow: inset 2px 0 0 rgba(116, 185, 255, 0.5);\n }\n \n .section-header:active {\n background: rgba(255, 255, 255, 0.12);\n transform: translateX(1px);\n }\n \n .section-header.expanded {\n background: rgba(116, 185, 255, 0.1);\n border-left: 2px solid var(--info-panel-accent-color, #74b9ff);\n }\n \n .info-section[data-section=\"node\"] {\n background: linear-gradient(135deg, rgba(116, 185, 255, 0.05), rgba(160, 212, 104, 0.05));\n border: 1px solid rgba(116, 185, 255, 0.2);\n border-radius: 8px;\n margin: 6px;\n }\n \n .info-section[data-section=\"node\"] .section-header {\n background: linear-gradient(135deg, rgba(116, 185, 255, 0.15), rgba(160, 212, 104, 0.15));\n border-radius: 8px 8px 0 0;\n cursor: default;\n }\n \n .info-section[data-section=\"node\"] .section-header:hover {\n transform: none;\n box-shadow: none;\n }\n \n .info-section[data-section=\"node\"] .section-title {\n color: var(--info-panel-accent-color, #74b9ff);\n font-weight: 700;\n }\n \n .info-section[data-section=\"inspector\"] {\n background: linear-gradient(135deg, rgba(100, 150, 255, 0.05), rgba(74, 144, 226, 0.05));\n border: 1px solid rgba(100, 150, 255, 0.2);\n border-radius: 8px;\n margin: 6px;\n }\n \n .info-section[data-section=\"inspector\"] .section-header {\n background: linear-gradient(135deg, rgba(100, 150, 255, 0.15), rgba(74, 144, 226, 0.15));\n border-radius: 8px 8px 0 0;\n }\n \n .info-section[data-section=\"inspector\"] .section-title {\n color: #6496ff;\n font-weight: 700;\n }\n \n .info-section[data-section=\"media\"] {\n background: linear-gradient(135deg, rgba(253, 203, 110, 0.05), rgba(255, 215, 0, 0.05));\n border: 1px solid rgba(253, 203, 110, 0.2);\n border-radius: 8px;\n margin: 6px;\n }\n \n .info-section[data-section=\"media\"] .section-header {\n background: linear-gradient(135deg, rgba(253, 203, 110, 0.15), rgba(255, 215, 0, 0.15));\n border-radius: 8px 8px 0 0;\n }\n \n .info-section[data-section=\"media\"] .section-title {\n color: var(--info-panel-accent-color, #fdcb6e);\n font-weight: 700;\n }\n \n .section-icon {\n font-size: 18px;\n width: 22px;\n text-align: center;\n }\n \n .section-title {\n flex: 1;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.3px;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);\n }\n \n .section-badge {\n background: rgba(100, 150, 255, 0.2);\n color: #8bb3ff;\n padding: 3px 10px;\n border-radius: 10px;\n font-size: 13px;\n font-weight: 500;\n }\n \n .expand-icon {\n font-size: 14px;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n color: rgba(255, 255, 255, 0.6);\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background: rgba(255, 255, 255, 0.05);\n margin-left: auto;\n }\n \n .section-header:hover .expand-icon {\n color: var(--info-panel-accent-color, #74b9ff);\n background: rgba(116, 185, 255, 0.15);\n transform: scale(1.1);\n }\n \n .section-header.expanded .expand-icon {\n transform: rotate(90deg) scale(1.1);\n color: var(--info-panel-accent-color, #74b9ff);\n background: rgba(116, 185, 255, 0.2);\n }\n \n .section-content {\n max-height: 0;\n overflow: visible;\n transition: max-height 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;\n opacity: 0;\n }\n\n .section-content.expanded {\n max-height: 2000px;\n opacity: 1;\n overflow: visible;\n }\n \n .section-body {\n padding: 0 18px 16px 24px;\n }\n \n .info-row {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n padding: 10px 0;\n border-bottom: 1px solid rgba(255, 255, 255, 0.05);\n transition: all 0.2s ease;\n border-radius: 4px;\n margin: 0 -8px;\n padding-left: 8px;\n padding-right: 8px;\n }\n \n .info-row:last-child {\n border-bottom: none;\n }\n \n .info-row:hover {\n background: rgba(255, 255, 255, 0.03);\n border-bottom-color: rgba(255, 255, 255, 0.08);\n }\n \n .info-label {\n font-weight: 500;\n opacity: 0.85;\n font-size: 15px;\n min-width: 80px;\n padding-right: 12px;\n line-height: 1.5;\n }\n \n .info-value {\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 padding: 4px 10px;\n border-radius: 6px;\n max-width: 100%;\n word-wrap: break-word;\n word-break: break-word;\n hyphens: auto;\n line-height: 1.4;\n text-align: left;\n white-space: pre-wrap;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n transition: all 0.2s ease;\n }\n \n .info-value:hover {\n background: rgba(160, 212, 104, 0.18);\n transform: translateY(-1px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n }\n \n .info-value.long-text {\n white-space: pre-wrap;\n word-wrap: break-word;\n max-width: 100%;\n line-height: 1.4;\n }\n \n .empty-state {\n text-align: center;\n padding: 50px 25px;\n opacity: 0.6;\n }\n \n .empty-state-icon {\n font-size: 36px;\n margin-bottom: 15px;\n opacity: 0.5;\n }\n \n /* Copy to clipboard functionality */\n .info-value[title]:hover::after {\n content: '📋';\n position: absolute;\n right: -20px;\n top: 50%;\n transform: translateY(-50%);\n font-size: 12px;\n opacity: 0.7;\n }\n \n /* Smooth loading states */\n .section-body.loading {\n opacity: 0.5;\n pointer-events: none;\n }\n \n .section-body.loading::after {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n width: 20px;\n height: 20px;\n border: 2px solid transparent;\n border-top: 2px solid var(--info-panel-accent-color, #74b9ff);\n border-radius: 50%;\n animation: spin 1s linear infinite;\n transform: translate(-50%, -50%);\n }\n \n @keyframes spin {\n to { transform: translate(-50%, -50%) rotate(360deg); }\n }\n \n /* Enhanced scrollbar for better UX */\n .section-body::-webkit-scrollbar {\n width: 4px;\n }\n \n .section-body::-webkit-scrollbar-track {\n background: rgba(255, 255, 255, 0.05);\n border-radius: 2px;\n }\n \n .section-body::-webkit-scrollbar-thumb {\n background: var(--info-panel-accent-color, #74b9ff);\n border-radius: 2px;\n opacity: 0.6;\n }\n \n .section-body::-webkit-scrollbar-thumb:hover {\n opacity: 1;\n }\n \n .empty-state-text {\n font-size: 16px;\n font-style: italic;\n }\n \n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n }\n \n .pulse {\n animation: pulse 2s infinite;\n }\n `;\n}\n"],"names":[],"mappings":"AAUO,SAAS,kBAA0B;AACtC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkjBX;"} \ No newline at end of file