From 514e760d5f6f4719ed98013aa71de860d052b4ac Mon Sep 17 00:00:00 2001 From: AEmotionStudio Date: Thu, 1 Jan 2026 19:47:32 -0800 Subject: [PATCH] feat: Introduce a new sidebar for settings and inspector, and add glass reset options and info panel positioning improvements. --- css/info-panel.css | 664 ++++++++++++++++++++++ css/sidebar.css | 352 ++++++++++++ src/info-panel/InfoPanel.ts | 2 + src/info-panel/PositionManager.ts | 7 + src/info-panel/StateManager.ts | 4 +- src/info-panel/UIManager.ts | 59 +- src/magnify-glass/EventHandler.ts | 4 +- src/magnify-glass/MagnifyGlass.ts | 7 +- src/magnify_glass.ts | 4 + src/shared/constants.ts | 5 +- src/shared/settings/glassSettings.ts | 78 +++ src/sidebar/SidebarInspector.ts | 261 +++++++++ src/sidebar/SidebarSettings.ts | 678 +++++++++++++++++++++++ src/sidebar/index.ts | 8 + src/sidebar/sidebar.ts | 106 ++++ src/types/comfyui.d.ts | 1 + web/info-panel-old.css | 664 ++++++++++++++++++++++ web/info-panel.css | 12 +- web/info-panel/InfoPanel.js | 1 + web/info-panel/InfoPanel.js.map | 2 +- web/info-panel/PositionManager.js | 5 + web/info-panel/PositionManager.js.map | 2 +- web/info-panel/StateManager.js | 6 +- web/info-panel/StateManager.js.map | 2 +- web/info-panel/UIManager.js | 36 +- web/info-panel/UIManager.js.map | 2 +- web/magnify-glass/EventHandler.js | 4 +- web/magnify-glass/EventHandler.js.map | 2 +- web/magnify-glass/MagnifyGlass.js | 5 +- web/magnify-glass/MagnifyGlass.js.map | 2 +- web/magnify_glass.js | 2 + web/magnify_glass.js.map | 2 +- web/shared/constants.js | 6 +- web/shared/constants.js.map | 2 +- web/shared/settings/glassSettings.js | 71 +++ web/shared/settings/glassSettings.js.map | 2 +- web/sidebar.css | 352 ++++++++++++ web/sidebar/SidebarInspector.js | 164 ++++++ web/sidebar/SidebarInspector.js.map | 1 + web/sidebar/SidebarSettings.js | 552 ++++++++++++++++++ web/sidebar/SidebarSettings.js.map | 1 + web/sidebar/sidebar.js | 64 +++ web/sidebar/sidebar.js.map | 1 + 43 files changed, 4138 insertions(+), 67 deletions(-) create mode 100644 css/info-panel.css create mode 100644 css/sidebar.css create mode 100644 src/sidebar/SidebarInspector.ts create mode 100644 src/sidebar/SidebarSettings.ts create mode 100644 src/sidebar/index.ts create mode 100644 src/sidebar/sidebar.ts create mode 100644 web/info-panel-old.css create mode 100644 web/sidebar.css create mode 100644 web/sidebar/SidebarInspector.js create mode 100644 web/sidebar/SidebarInspector.js.map create mode 100644 web/sidebar/SidebarSettings.js create mode 100644 web/sidebar/SidebarSettings.js.map create mode 100644 web/sidebar/sidebar.js create mode 100644 web/sidebar/sidebar.js.map diff --git a/css/info-panel.css b/css/info-panel.css new file mode 100644 index 0000000..ce9d79d --- /dev/null +++ b/css/info-panel.css @@ -0,0 +1,664 @@ +/** + * ComfyUI MagnifyGlass - Info Panel Styles + * + * Complete CSS styles for the info panel component. + */ + +/* SVG Icon Styling */ +.magnify-info-panel svg, +.floating-controls svg { + width: 1em; + height: 1em; + vertical-align: middle; + fill: none; + stroke: currentColor; + pointer-events: none; + /* Allow clicks to pass through to button */ +} + +.header-icon svg { + width: 18px; + height: 18px; +} + +.section-icon svg { + width: 18px; + height: 18px; +} + +.control-btn svg { + width: 14px; + height: 14px; +} + +.expand-icon svg { + width: 12px; + height: 12px; +} + +.empty-state-icon svg { + width: 36px; + height: 36px; + stroke-width: 1.5; +} + +.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: none; + /* Hidden initially - JS will show when positioned */ + 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; + cursor: grab; + transition: background 0.15s ease; +} + +.panel-header:hover { + background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08)); +} + +.panel-header:active { + cursor: grabbing; +} + +.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/css/sidebar.css b/css/sidebar.css new file mode 100644 index 0000000..6f458a3 --- /dev/null +++ b/css/sidebar.css @@ -0,0 +1,352 @@ +/** + * ComfyUI MagnifyGlass - Sidebar CSS + * + * Styles for the sidebar settings and inspector panels. + */ + +.magnify-sidebar { + display: flex; + flex-direction: column; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 13px; + color: var(--fg-color, #ddd); + background: transparent; + padding: 0; +} + +.magnify-sidebar-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color, #333); + background: rgba(0, 0, 0, 0.2); +} + +.magnify-sidebar-header svg { + width: 20px; + height: 20px; + fill: currentColor; +} + +.magnify-sidebar-header h2 { + margin: 0; + font-size: 14px; + font-weight: 600; +} + +.magnify-sidebar-content { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +/* Section styling */ +.magnify-sidebar-section { + margin-bottom: 4px; +} + +.magnify-sidebar-section-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + cursor: pointer; + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid var(--border-color, #333); + transition: background 0.15s; +} + +.magnify-sidebar-section-header:hover { + background: rgba(255, 255, 255, 0.06); +} + +.magnify-sidebar-section-header svg { + width: 12px; + height: 12px; + transition: transform 0.2s; +} + +.magnify-sidebar-section-header.collapsed svg { + transform: rotate(-90deg); +} + +.magnify-sidebar-section-header span { + font-weight: 500; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.magnify-sidebar-section-body { + padding: 12px 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.magnify-sidebar-section-body.collapsed { + display: none; +} + +/* Control row */ +.magnify-control-row { + display: flex; + flex-direction: column; + gap: 4px; +} + +.magnify-control-row label { + font-size: 11px; + color: var(--descrip-text, #999); + font-weight: 500; +} + +.magnify-control-row .control-value { + font-size: 11px; + color: var(--fg-color, #ddd); + margin-left: auto; +} + +.magnify-control-label-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +/* Slider styling */ +.magnify-slider { + width: 100%; + height: 6px; + border-radius: 3px; + background: rgba(255, 255, 255, 0.1); + appearance: none; + cursor: pointer; +} + +.magnify-slider::-webkit-slider-thumb { + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--p-primary-color, #3b82f6); + cursor: pointer; + transition: transform 0.1s; +} + +.magnify-slider::-webkit-slider-thumb:hover { + transform: scale(1.15); +} + +.magnify-slider::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--p-primary-color, #3b82f6); + border: none; + cursor: pointer; +} + +/* Toggle styling */ +.magnify-toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; +} + +.magnify-toggle-row label { + font-size: 12px; + color: var(--fg-color, #ddd); +} + +.magnify-toggle { + position: relative; + width: 36px; + height: 20px; + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; + cursor: pointer; + transition: background 0.2s; +} + +.magnify-toggle.active { + background: var(--p-primary-color, #3b82f6); +} + +.magnify-toggle::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background: white; + border-radius: 50%; + transition: transform 0.2s; +} + +.magnify-toggle.active::after { + transform: translateX(16px); +} + +/* Select styling */ +.magnify-select { + width: 100%; + padding: 6px 10px; + border: 1px solid var(--border-color, #444); + border-radius: 4px; + background: rgba(0, 0, 0, 0.3); + color: var(--fg-color, #ddd); + font-size: 12px; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L2 4h8z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} + +.magnify-select:focus { + outline: none; + border-color: var(--p-primary-color, #3b82f6); +} + +/* Inspector styles */ +.magnify-inspector-section { + padding: 12px 16px; +} + +.magnify-inspector-group { + margin-bottom: 16px; +} + +.magnify-inspector-group:last-child { + margin-bottom: 0; +} + +.magnify-inspector-group-title { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--descrip-text, #888); + margin-bottom: 8px; + font-weight: 600; +} + +.magnify-inspector-row { + display: flex; + justify-content: space-between; + padding: 4px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.magnify-inspector-row:last-child { + border-bottom: none; +} + +.magnify-inspector-label { + color: var(--descrip-text, #999); + font-size: 12px; +} + +.magnify-inspector-value { + color: var(--fg-color, #ddd); + font-size: 12px; + font-family: monospace; + text-align: right; + max-width: 60%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.magnify-inspector-empty { + color: var(--descrip-text, #666); + font-style: italic; + font-size: 12px; + text-align: center; + padding: 20px 0; +} + +/* Node badge */ +.magnify-node-badge { + display: inline-block; + padding: 2px 8px; + background: var(--p-primary-color, #3b82f6); + border-radius: 4px; + font-size: 11px; + font-weight: 500; + margin-bottom: 8px; +} + +/* Button styling */ +.magnify-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 12px; + border: none; + border-radius: 4px; + background: var(--p-primary-color, #3b82f6); + color: white; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, transform 0.1s; +} + +.magnify-btn:hover { + background: var(--p-primary-hover-color, #2563eb); +} + +.magnify-btn:active { + transform: scale(0.98); +} + +.magnify-btn-secondary { + background: rgba(255, 255, 255, 0.1); +} + +.magnify-btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); +} + +/* Color picker styling */ +.magnify-color-row { + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.magnify-color-wrapper { + display: flex; + align-items: center; + gap: 8px; +} + +.magnify-color-input { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--border-color, #444); + border-radius: 4px; + cursor: pointer; + background: transparent; +} + +.magnify-color-input::-webkit-color-swatch-wrapper { + padding: 2px; +} + +.magnify-color-input::-webkit-color-swatch { + border-radius: 2px; + border: none; +} + +.magnify-color-preview { + font-size: 11px; + font-family: monospace; + color: var(--descrip-text, #999); +} \ No newline at end of file diff --git a/src/info-panel/InfoPanel.ts b/src/info-panel/InfoPanel.ts index 4c52c31..0403023 100644 --- a/src/info-panel/InfoPanel.ts +++ b/src/info-panel/InfoPanel.ts @@ -176,6 +176,8 @@ export class InfoPanel { if (this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelEnabled"] && this.magnifyGlass.state.active) { this.uiManager.show(); + // Ensure position is updated after potential dimension changes + this.positionManager.positionPanel(); } else { this.uiManager.hide(); } diff --git a/src/info-panel/PositionManager.ts b/src/info-panel/PositionManager.ts index 1117e06..135a242 100644 --- a/src/info-panel/PositionManager.ts +++ b/src/info-panel/PositionManager.ts @@ -133,6 +133,13 @@ export class PositionManager { positionFloatingControls(controlsElement: HTMLElement | null): void { if (!controlsElement) return; + // Check if controls should be visible based on settings + const settings = this.stateManager.state.settings; + if (settings["🔍MagnifyGlass.ShowHoveringControls"] === false) { + controlsElement.style.display = 'none'; + return; + } + const isPanelVisible = this.stateManager.state.isPanelVisible; const magnifyGlass = window.comfyUIMagnifyGlass; let referenceRect: DOMRect | null = null; diff --git a/src/info-panel/StateManager.ts b/src/info-panel/StateManager.ts index 51dedd6..6ba038b 100644 --- a/src/info-panel/StateManager.ts +++ b/src/info-panel/StateManager.ts @@ -59,7 +59,7 @@ export class StateManager { // Panel visibility and positioning isPanelVisible: false, wasPanelVisibleBeforeHide: false, - isPanelMinimized: false, + isPanelMinimized: true, isPanelPinned: false, isPanelLocked: false, // New lock state isAutoPinned: false, // Track if panel was auto-pinned by glass hide @@ -259,7 +259,7 @@ export class StateManager { target.textContent?.toLowerCase().includes('light') || target.textContent?.toLowerCase().includes('dark') || target.title?.toLowerCase().includes('theme') || - target.className?.toLowerCase().includes('theme'); + (typeof target.className === 'string' && target.className.toLowerCase().includes('theme')); if (isThemeButton) { setTimeout(() => { diff --git a/src/info-panel/UIManager.ts b/src/info-panel/UIManager.ts index 37c005b..27059bf 100644 --- a/src/info-panel/UIManager.ts +++ b/src/info-panel/UIManager.ts @@ -76,6 +76,10 @@ export class UIManager { this.elements.panel.appendChild(this.elements.content); this.applyStyles(); + // Apply initial minimized state if set + if (this.stateManager.state.isPanelMinimized) { + this.elements.panel.classList.add('panel-minimized'); + } document.body.appendChild(this.elements.panel); // Add click event delegation for panel elements @@ -114,7 +118,7 @@ export class UIManager { }); // Create floating controls after panel is in DOM - if (this.stateManager.state.settings["🔍MagnifyGlass.ShowHoveringControls"]) { + if (!this.elements.controls) { this.createFloatingControls(); // Update control states immediately after creation this.updateControlStates(); @@ -321,43 +325,52 @@ export class UIManager { const settings = this.stateManager.state.settings; - // Apply custom text color via CSS variable if provided + // Debug Log + console.log(`[UIManager] applyStyles - MaxHeight: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}`); + + // 1. Apply dimensions + this.elements.panel.style.width = `${settings["🔍MagnifyGlass.InfoPanelWidth"]}px`; + // Set both height and max-height so the panel always shows at this size + const maxHeight = settings["🔍MagnifyGlass.InfoPanelMaxHeight"]; + this.elements.panel.style.height = `${maxHeight}px`; + this.elements.panel.style.maxHeight = `${maxHeight}px`; + + // 2. Apply static styles (only needed once or if overwritten, but safe to set) + this.elements.panel.style.position = 'absolute'; + this.elements.panel.style.zIndex = '99999'; + this.elements.panel.style.transform = 'translateY(-10px)'; + this.elements.panel.style.pointerEvents = 'auto'; + this.elements.panel.style.userSelect = 'none'; + this.elements.panel.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"; + + // 3. Apply transition + this.elements.panel.style.transition = settings["🔍MagnifyGlass.InfoPanelAnimations"] + ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' + : 'none'; + + // 4. Apply Custom Colors (CSS Variables) const textColor = settings["🔍MagnifyGlass.InfoPanelTextColor"] as string | undefined; if (textColor && typeof textColor === 'string') { - // Ensure color has # symbol const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`; this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor); } - // Apply custom accent color via CSS variable if provided const accentColor = settings["🔍MagnifyGlass.InfoPanelAccentColor"] as string | undefined; if (accentColor && typeof accentColor === 'string') { - // Ensure color has # symbol const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`; this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor); } - // Apply opacity setting if panel is visible (convert percentage to decimal) + // 5. Apply Opacity + // Logic: If visible, use setting. If hidden, use 0. + // We do NOT touch 'display' here to avoid hiding it if it's currently shown. + // 'display' is managed by show() / hide() methods exclusively. if (this.stateManager.state.isPanelVisible) { const opacityPercent = Number(settings["🔍MagnifyGlass.InfoPanelOpacity"]) || 100; this.elements.panel.style.opacity = (opacityPercent / 100).toString(); + } else { + this.elements.panel.style.opacity = '0'; } - - this.elements.panel.style.cssText = ` - position: absolute; - width: ${settings["🔍MagnifyGlass.InfoPanelWidth"]}px; - max-height: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}px; - z-index: 99999; - display: none; - opacity: 0; - transform: translateY(-10px); - transition: ${settings["🔍MagnifyGlass.InfoPanelAnimations"] ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none'}; - pointer-events: auto; - user-select: none; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - ${textColor && typeof textColor === 'string' ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''} - ${accentColor && typeof accentColor === 'string' ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''} - `; } /** @@ -456,7 +469,7 @@ export class UIManager { const settings = this.stateManager.state.settings; // Inspector section - if (settings["🔍MagnifyGlass.ShowInspectorTab"]) { + if (settings["🔍MagnifyGlass.ShowInspectorTab"] && info.cursor && info.cursor.canvas) { const inspectorContent = [ { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` }, { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` }, diff --git a/src/magnify-glass/EventHandler.ts b/src/magnify-glass/EventHandler.ts index 4f7d4f7..1c030ff 100644 --- a/src/magnify-glass/EventHandler.ts +++ b/src/magnify-glass/EventHandler.ts @@ -6,7 +6,7 @@ import { isUserTyping } from '../shared/utils'; import { Logger } from '../shared/logger'; -import { DEFAULT_PADDING } from '../shared/constants'; +import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants'; import type { MagnifyGlass } from './MagnifyGlass'; /** @@ -212,7 +212,7 @@ export class EventHandler { if (!this.magnifyGlass.state.wasActivatedBefore && this.magnifyGlass.ui.glassDiv) { // First activation - position at top right of the window this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`; - this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`; + this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`; this.magnifyGlass.state.wasActivatedBefore = true; } else { this.magnifyGlass.ui.positionGlass(clientX, clientY); diff --git a/src/magnify-glass/MagnifyGlass.ts b/src/magnify-glass/MagnifyGlass.ts index 19bc625..13290e9 100644 --- a/src/magnify-glass/MagnifyGlass.ts +++ b/src/magnify-glass/MagnifyGlass.ts @@ -6,7 +6,7 @@ import type { ComfyApp, ComfyNode } from '../types/comfyui'; import { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils'; -import { DEFAULT_PADDING } from '../shared/constants'; +import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants'; import { registerGlassSettings } from '../shared/settings'; import { ConfigManager } from './ConfigManager'; import { MagnifierState } from './MagnifierState'; @@ -378,6 +378,9 @@ export class MagnifyGlass { resetOffsets(): void { this.config.resetOffsets(); + // Also disable follow cursor + this.config.followCursor = false; + if (this.state.active) { this.updateMagnifiedView(); } @@ -386,7 +389,7 @@ export class MagnifyGlass { if (this.ui.glassDiv) { const glassSize = this.config.glassSize; this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`; - this.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`; + this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`; this.state.wasActivatedBefore = false; } diff --git a/src/magnify_glass.ts b/src/magnify_glass.ts index 98ac4a0..936acd9 100644 --- a/src/magnify_glass.ts +++ b/src/magnify_glass.ts @@ -6,6 +6,7 @@ import { MagnifyGlass } from './magnify-glass'; import { Logger } from './shared/logger'; +import { initSidebar } from './sidebar'; // @ts-ignore import { app } from "/scripts/app.js"; @@ -23,6 +24,9 @@ app.registerExtension({ // Expose to window for other extensions (like info panel) to access window.comfyUIMagnifyGlass = magnifyGlass; + // Initialize sidebar + initSidebar(); + Logger.info('Magnify Glass extension initialized'); } }); diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 6d0364c..74a2c86 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -52,6 +52,7 @@ export const TOGGLE_FOLLOW_KEYS = ["f", "g", "h", "j", "k"] as const; // UI Constants export const DEFAULT_PADDING = 20; +export const DEFAULT_GLASS_Y_OFFSET = 120; // Starting Y position offset (below top bar) export const DEFAULT_GLASS_SIZE = 300; export const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage export const DEFAULT_BORDER_WIDTH = 1; @@ -59,8 +60,8 @@ export const DEFAULT_BORDER_COLOR = "#6b7280"; export const DEFAULT_OFFSET_STEP = 5; // Panel Constants -export const DEFAULT_PANEL_WIDTH = 320; -export const DEFAULT_PANEL_MAX_HEIGHT = 1000; +export const DEFAULT_PANEL_WIDTH = 300; +export const DEFAULT_PANEL_MAX_HEIGHT = 300; export const DEFAULT_PANEL_OPACITY = 100; // Z-Index hierarchy diff --git a/src/shared/settings/glassSettings.ts b/src/shared/settings/glassSettings.ts index 6d6c2d4..226e482 100644 --- a/src/shared/settings/glassSettings.ts +++ b/src/shared/settings/glassSettings.ts @@ -284,4 +284,82 @@ export function registerGlassSettings(magnifyGlass: any): void { } } }); + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.Action.ResetPosition", + name: "🔄 Magnify Glass: Reset Position", + type: "boolean", + defaultValue: false, + tooltip: "Toggle to reset the glass position and disable follow cursor.", + onChange: (value: any) => { + if (value) { + if (magnifyGlass?.resetOffsets) { + magnifyGlass.resetOffsets(); + } + // Reset the toggle back to false + setTimeout(() => { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.Action.ResetPosition", false); + } catch (e) { /* ignore */ } + }, 500); + } + } + }); + + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.Action.ResetAll", + name: "⚠️ Magnify Glass: Reset ALL Settings", + type: "boolean", + defaultValue: false, + tooltip: "Toggle to reset ALL settings to defaults.", + onChange: (value: any) => { + if (value) { + if (confirm("Reset ALL Magnify Glass settings?")) { + // Glass Defaults + app.ui.settings.setSettingValue('🔍MagnifyGlass.ZoomFactor', 300); + app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassSize', 250); + app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderWidth', 2); + app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderEnabled', true); + app.ui.settings.setSettingValue('🔍MagnifyGlass.FollowCursor', false); + app.ui.settings.setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ActivationKey', 'x'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ResetKey', 'o'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.AltRequired', false); + app.ui.settings.setSettingValue('🔍MagnifyGlass.OffsetStep', 10); + app.ui.settings.setSettingValue('🔍MagnifyGlass.DebugMode', false); + + // Panel Defaults + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 300); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ShowHoveringControls', true); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g'); + app.ui.settings.setSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u'); + + // Also force position reset + if (magnifyGlass?.resetOffsets) { + magnifyGlass.resetOffsets(); + } + } + + // Reset the toggle back to false + setTimeout(() => { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.Action.ResetAll", false); + } catch (e) { /* ignore */ } + }, 500); + } + } + }); } diff --git a/src/sidebar/SidebarInspector.ts b/src/sidebar/SidebarInspector.ts new file mode 100644 index 0000000..32829e4 --- /dev/null +++ b/src/sidebar/SidebarInspector.ts @@ -0,0 +1,261 @@ +/** + * ComfyUI MagnifyGlass - Sidebar Inspector Panel + * + * Real-time inspector view for the sidebar. + */ + +// @ts-ignore +import { app } from "/scripts/app.js"; + +/** + * Icons for the inspector panel + */ +const Icons = { + chevronDown: ``, +}; + +/** + * Inspector state + */ +interface InspectorState { + cursor: { x: number; y: number }; + canvasScale: number; + hoveredNode: any | null; +} + +let inspectorState: InspectorState = { + cursor: { x: 0, y: 0 }, + canvasScale: 1, + hoveredNode: null +}; + +let inspectorBody: HTMLElement | null = null; +let updateInterval: number | null = null; + +/** + * Format a number for display + */ +function formatNumber(value: number, decimals: number = 0): string { + return value.toFixed(decimals); +} + +/** + * Get the currently hovered node from the graph + */ +function getHoveredNode(): any | null { + try { + const graph = app.graph; + if (!graph || !graph.canvas) return null; + + const canvas = graph.canvas; + const mousePos = canvas.graph_mouse; + + if (!mousePos) return null; + + // Find node at mouse position + const nodes = graph._nodes; + for (let i = nodes.length - 1; i >= 0; i--) { + const node = nodes[i]; + if (node.pos && node.size) { + const [x, y] = node.pos; + const [w, h] = node.size; + if (mousePos[0] >= x && mousePos[0] <= x + w && + mousePos[1] >= y && mousePos[1] <= y + h) { + return node; + } + } + } + return null; + } catch { + return null; + } +} + +/** + * Get canvas information + */ +function getCanvasInfo(): { scale: number; cursor: { x: number; y: number } } { + try { + const graph = app.graph; + if (!graph || !graph.canvas) { + return { scale: 1, cursor: { x: 0, y: 0 } }; + } + + const canvas = graph.canvas; + const scale = canvas.ds?.scale || 1; + const mousePos = canvas.graph_mouse || [0, 0]; + + return { + scale: scale * 100, + cursor: { x: mousePos[0] || 0, y: mousePos[1] || 0 } + }; + } catch { + return { scale: 1, cursor: { x: 0, y: 0 } }; + } +} + +/** + * Create an inspector row + */ +function createRow(label: string, value: string): HTMLElement { + const row = document.createElement('div'); + row.className = 'magnify-inspector-row'; + + const labelEl = document.createElement('span'); + labelEl.className = 'magnify-inspector-label'; + labelEl.textContent = label; + + const valueEl = document.createElement('span'); + valueEl.className = 'magnify-inspector-value'; + valueEl.textContent = value; + valueEl.title = value; + + row.appendChild(labelEl); + row.appendChild(valueEl); + + return row; +} + +/** + * Create a group with title + */ +function createGroup(title: string): HTMLElement { + const group = document.createElement('div'); + group.className = 'magnify-inspector-group'; + + const titleEl = document.createElement('div'); + titleEl.className = 'magnify-inspector-group-title'; + titleEl.textContent = title; + + group.appendChild(titleEl); + + return group; +} + +/** + * Update the inspector display + */ +function updateInspector(): void { + if (!inspectorBody) return; + + const canvasInfo = getCanvasInfo(); + const hoveredNode = getHoveredNode(); + + // Clear body + inspectorBody.innerHTML = ''; + + // Canvas info group + const canvasGroup = createGroup('Canvas'); + canvasGroup.appendChild(createRow('Cursor', `(${formatNumber(canvasInfo.cursor.x)}, ${formatNumber(canvasInfo.cursor.y)})`)); + canvasGroup.appendChild(createRow('Scale', `${formatNumber(canvasInfo.scale, 1)}%`)); + inspectorBody.appendChild(canvasGroup); + + // MagnifyGlass info + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass) { + const glassGroup = createGroup('Magnify Glass'); + glassGroup.appendChild(createRow('Zoom', `${formatNumber((magnifyGlass.config?.zoomFactor || 1) * 100)}%`)); + glassGroup.appendChild(createRow('Active', magnifyGlass.state?.active ? 'Yes' : 'No')); + inspectorBody.appendChild(glassGroup); + } + + // Node info group + if (hoveredNode) { + const nodeGroup = createGroup('Hovered Node'); + + // Node badge + const badge = document.createElement('div'); + badge.className = 'magnify-node-badge'; + badge.textContent = hoveredNode.type || 'Unknown'; + nodeGroup.appendChild(badge); + + nodeGroup.appendChild(createRow('Title', hoveredNode.title || 'Untitled')); + nodeGroup.appendChild(createRow('ID', String(hoveredNode.id))); + + // Position + if (hoveredNode.pos) { + nodeGroup.appendChild(createRow('Position', `(${formatNumber(hoveredNode.pos[0])}, ${formatNumber(hoveredNode.pos[1])})`)); + } + + // Size + if (hoveredNode.size) { + nodeGroup.appendChild(createRow('Size', `${formatNumber(hoveredNode.size[0])} × ${formatNumber(hoveredNode.size[1])}`)); + } + + // Widgets (first few) + if (hoveredNode.widgets && hoveredNode.widgets.length > 0) { + const widgetGroup = createGroup('Parameters'); + const widgetsToShow = hoveredNode.widgets.slice(0, 6); + for (const widget of widgetsToShow) { + if (widget.name && widget.value !== undefined) { + let value = String(widget.value); + if (value.length > 30) value = value.substring(0, 27) + '...'; + widgetGroup.appendChild(createRow(widget.name, value)); + } + } + if (hoveredNode.widgets.length > 6) { + const moreEl = document.createElement('div'); + moreEl.style.cssText = 'font-size: 11px; color: #666; padding: 4px 0;'; + moreEl.textContent = `+${hoveredNode.widgets.length - 6} more...`; + widgetGroup.appendChild(moreEl); + } + inspectorBody.appendChild(widgetGroup); + } + + inspectorBody.appendChild(nodeGroup); + } else { + // Empty state + const empty = document.createElement('div'); + empty.className = 'magnify-inspector-empty'; + empty.textContent = 'Hover over a node to inspect'; + inspectorBody.appendChild(empty); + } +} + +/** + * Render the inspector panel into the container + */ +export function renderInspectorPanel(container: HTMLElement): void { + const section = document.createElement('div'); + section.className = 'magnify-sidebar-section'; + + // Section header + const header = document.createElement('div'); + header.className = 'magnify-sidebar-section-header'; + header.innerHTML = `${Icons.chevronDown}Inspector`; + + // Section body + const body = document.createElement('div'); + body.className = 'magnify-sidebar-section-body magnify-inspector-section'; + inspectorBody = body; + + // Toggle collapse + header.addEventListener('click', () => { + header.classList.toggle('collapsed'); + body.classList.toggle('collapsed'); + }); + + section.appendChild(header); + section.appendChild(body); + container.appendChild(section); + + // Initial update + updateInspector(); + + // Start update interval + if (updateInterval) { + clearInterval(updateInterval); + } + updateInterval = window.setInterval(updateInspector, 100); +} + +/** + * Cleanup the inspector (call when sidebar is destroyed) + */ +export function cleanupInspector(): void { + if (updateInterval) { + clearInterval(updateInterval); + updateInterval = null; + } + inspectorBody = null; +} diff --git a/src/sidebar/SidebarSettings.ts b/src/sidebar/SidebarSettings.ts new file mode 100644 index 0000000..8ed66a5 --- /dev/null +++ b/src/sidebar/SidebarSettings.ts @@ -0,0 +1,678 @@ +/** + * ComfyUI MagnifyGlass - Sidebar Settings Panel + * + * All settings controls for the sidebar. + */ + +// @ts-ignore +import { app } from "/scripts/app.js"; +import { + GLASS_POSITIONS, + GLASS_SHAPES, + ACTIVATION_KEYS, + RESET_KEYS, + TOGGLE_FOLLOW_KEYS, + PANEL_POSITIONS +} from '../shared/constants'; + +/** + * Icons for the settings panel + */ +const Icons = { + chevronDown: ``, + reset: ``, +}; + +/** + * Create a slider control with tooltip + */ +/** + * Create a slider control with tooltip + */ +function createSlider( + label: string, + value: number, + min: number, + max: number, + step: number, + unit: string, + onChange: (value: number) => void, + tooltip?: string, + onInput?: (value: number) => void +): HTMLElement { + const row = document.createElement('div'); + row.className = 'magnify-control-row'; + if (tooltip) row.title = tooltip; + + const labelRow = document.createElement('div'); + labelRow.className = 'magnify-control-label-row'; + + const labelEl = document.createElement('label'); + labelEl.textContent = label; + + const valueEl = document.createElement('span'); + valueEl.className = 'control-value'; + valueEl.textContent = `${value}${unit}`; + + labelRow.appendChild(labelEl); + labelRow.appendChild(valueEl); + + const slider = document.createElement('input'); + slider.type = 'range'; + slider.className = 'magnify-slider'; + slider.min = String(min); + slider.max = String(max); + slider.step = String(step); + slider.value = String(value); + + // Prevent ComfyUI from capturing mouse events during drag + slider.addEventListener('mousedown', (e) => e.stopPropagation()); + slider.addEventListener('touchstart', (e) => e.stopPropagation()); + slider.addEventListener('pointerdown', (e) => e.stopPropagation()); + + slider.addEventListener('input', (e) => { + e.stopPropagation(); + const newValue = parseFloat(slider.value); + valueEl.textContent = `${newValue}${unit}`; + if (onInput) onInput(newValue); + }); + + slider.addEventListener('change', (e) => { + e.stopPropagation(); + const newValue = parseFloat(slider.value); + onChange(newValue); + }); + + row.appendChild(labelRow); + row.appendChild(slider); + + return row; +} + +/** + * Create a toggle control with tooltip + */ +function createToggle( + label: string, + checked: boolean, + onChange: (checked: boolean) => void, + tooltip?: string +): HTMLElement { + const row = document.createElement('div'); + row.className = 'magnify-toggle-row'; + if (tooltip) row.title = tooltip; + + const labelEl = document.createElement('label'); + labelEl.textContent = label; + + const toggle = document.createElement('div'); + toggle.className = `magnify-toggle${checked ? ' active' : ''}`; + + toggle.addEventListener('click', () => { + const isActive = toggle.classList.toggle('active'); + onChange(isActive); + }); + + row.appendChild(labelEl); + row.appendChild(toggle); + + return row; +} + +/** + * Create a select dropdown control with tooltip + */ +function createSelect( + label: string, + value: string, + options: readonly string[] | string[], + onChange: (value: string) => void, + tooltip?: string +): HTMLElement { + const row = document.createElement('div'); + row.className = 'magnify-control-row'; + if (tooltip) row.title = tooltip; + + const labelEl = document.createElement('label'); + labelEl.textContent = label; + + const select = document.createElement('select'); + select.className = 'magnify-select'; + + options.forEach(opt => { + const option = document.createElement('option'); + option.value = opt; + option.textContent = opt; + if (opt === value) option.selected = true; + select.appendChild(option); + }); + + select.addEventListener('change', () => onChange(select.value)); + + row.appendChild(labelEl); + row.appendChild(select); + + return row; +} + +/** + * Create a color picker control with tooltip + */ +/** + * Create a color picker control with tooltip + */ +function createColorPicker( + label: string, + value: string, + onChange: (value: string) => void, + tooltip?: string, + onInput?: (value: string) => void +): HTMLElement { + const row = document.createElement('div'); + row.className = 'magnify-control-row magnify-color-row'; + if (tooltip) row.title = tooltip; + + const labelEl = document.createElement('label'); + labelEl.textContent = label; + + const colorWrapper = document.createElement('div'); + colorWrapper.className = 'magnify-color-wrapper'; + + const colorInput = document.createElement('input'); + colorInput.type = 'color'; + colorInput.className = 'magnify-color-input'; + colorInput.value = value; + + const colorPreview = document.createElement('span'); + colorPreview.className = 'magnify-color-preview'; + colorPreview.textContent = value; + + colorInput.addEventListener('input', () => { + colorPreview.textContent = colorInput.value; + if (onInput) onInput(colorInput.value); + }); + + colorInput.addEventListener('change', () => { + onChange(colorInput.value); + }); + + colorWrapper.appendChild(colorInput); + colorWrapper.appendChild(colorPreview); + + row.appendChild(labelEl); + row.appendChild(colorWrapper); + + return row; +} + +/** + * Get current MagnifyGlass settings from app.ui.settings + */ +function getSettingValue(id: string, defaultValue: any): any { + try { + const value = app.ui.settings.getSettingValue(id); + return value !== undefined ? value : defaultValue; + } catch { + return defaultValue; + } +} + +/** + * Set a MagnifyGlass setting via app.ui.settings + */ +function setSettingValue(id: string, value: any): void { + try { + app.ui.settings.setSettingValue(id, value); + } catch (e) { + console.warn(`Failed to set setting ${id}:`, e); + } +} + +/** Storage key for section states */ +const SECTION_STATE_KEY = 'magnifyglass-sidebar-sections'; + +/** + * Get saved section states from localStorage + */ +function getSectionStates(): Record { + try { + const saved = localStorage.getItem(SECTION_STATE_KEY); + return saved ? JSON.parse(saved) : {}; + } catch { + return {}; + } +} + +/** + * Save section state to localStorage + */ +function saveSectionState(title: string, collapsed: boolean): void { + try { + const states = getSectionStates(); + states[title] = collapsed; + localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(states)); + } catch { + // Ignore storage errors + } +} + +/** + * Create a collapsible section with persistent state + */ +function createSection(title: string, defaultCollapsed: boolean = false): { section: HTMLElement; body: HTMLElement } { + const section = document.createElement('div'); + section.className = 'magnify-sidebar-section'; + + // Check if we have a saved state for this section + const savedStates = getSectionStates(); + const collapsed = savedStates[title] !== undefined ? savedStates[title] : defaultCollapsed; + + const header = document.createElement('div'); + header.className = `magnify-sidebar-section-header${collapsed ? ' collapsed' : ''}`; + header.innerHTML = `${Icons.chevronDown}${title}`; + + const body = document.createElement('div'); + body.className = `magnify-sidebar-section-body${collapsed ? ' collapsed' : ''}`; + + header.addEventListener('click', () => { + const isCollapsed = header.classList.toggle('collapsed'); + body.classList.toggle('collapsed'); + // Save the new state + saveSectionState(title, isCollapsed); + }); + + section.appendChild(header); + section.appendChild(body); + + return { section, body }; +} + +/** + * Render the settings panel into the container + */ +export function renderSettingsPanel(container: HTMLElement): void { + // Get current hotkey values for tooltips + const activationKey = getSettingValue('🔍MagnifyGlass.ActivationKey', 'x').toUpperCase(); + const resetKey = getSettingValue('🔍MagnifyGlass.ResetKey', 'o').toUpperCase(); + const toggleFollowKey = getSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h').toUpperCase(); + + // ===== Glass Settings Section ===== + const glassSection = createSection('Glass Settings'); + + glassSection.body.appendChild(createSlider('Zoom Factor', + getSettingValue('🔍MagnifyGlass.ZoomFactor', 300), 100, 1000, 25, '%', + (value) => setSettingValue('🔍MagnifyGlass.ZoomFactor', value), + 'Magnification level of the glass (100-1000%)', + (value) => { + const mg = (window as any).comfyUIMagnifyGlass; + if (mg?.config) { + mg.config.zoomFactor = value / 100; + if (mg.state.active) mg.updateMagnifiedView(); + } + } + )); + + glassSection.body.appendChild(createSlider('Glass Size', + getSettingValue('🔍MagnifyGlass.GlassSize', 250), 50, 500, 10, 'px', + (value) => setSettingValue('🔍MagnifyGlass.GlassSize', value), + 'Size of the magnifying glass in pixels', + (value) => { + const mg = (window as any).comfyUIMagnifyGlass; + if (mg?.config) { + mg.config.glassSize = value; + mg.applyUiChanges(); + if (mg.state.active) mg.updateMagnifiedView(); + } + } + )); + + glassSection.body.appendChild(createSelect('Shape', + getSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square'), GLASS_SHAPES, + (value) => setSettingValue('🔍MagnifyGlass.GlassShape', value), + 'Shape of the magnifying glass' + )); + + glassSection.body.appendChild(createSelect('Position', + getSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right'), GLASS_POSITIONS, + (value) => setSettingValue('🔍MagnifyGlass.GlassPosition', value), + `Default position of the glass. Press ${resetKey} to reset.` + )); + + glassSection.body.appendChild(createSelect('Filtering', + getSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear'), ['Linear', 'Nearest'], + (value) => setSettingValue('🔍MagnifyGlass.TextureFiltering', value), + 'Texture filtering: Linear (smooth) or Nearest (pixelated)' + )); + + glassSection.body.appendChild(createSlider('Border Width', + getSettingValue('🔍MagnifyGlass.BorderWidth', 2), 0, 10, 0.5, 'px', + (value) => setSettingValue('🔍MagnifyGlass.BorderWidth', value), + 'Width of the glass border in pixels', + (value) => { + const mg = (window as any).comfyUIMagnifyGlass; + if (mg?.config) { + mg.config.borderWidth = value; + mg.applyUiChanges(); + } + } + )); + + glassSection.body.appendChild(createColorPicker('Border Color', + getSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff'), + (value) => setSettingValue('🔍MagnifyGlass.BorderColor', value), + 'Color of the glass border', + (value) => { + const mg = (window as any).comfyUIMagnifyGlass; + if (mg?.config) { + mg.config.borderColor = value; + mg.applyUiChanges(); + } + } + )); + + glassSection.body.appendChild(createToggle('Show Border', + getSettingValue('🔍MagnifyGlass.BorderEnabled', true), + (checked) => setSettingValue('🔍MagnifyGlass.BorderEnabled', checked), + 'Show or hide the glass border' + )); + + glassSection.body.appendChild(createToggle('Follow Cursor', + getSettingValue('🔍MagnifyGlass.FollowCursor', false), + (checked) => setSettingValue('🔍MagnifyGlass.FollowCursor', checked), + `Glass follows the cursor. Hotkey: ${toggleFollowKey}` + )); + + glassSection.body.appendChild(createToggle('Always Active Mode', + getSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true), + (checked) => setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', checked), + `When enabled, glass stays visible. Toggle with ${activationKey}` + )); + + container.appendChild(glassSection.section); + + // ===== Hotkeys Section ===== + const hotkeySection = createSection('Hotkeys', true); + + hotkeySection.body.appendChild(createSelect('Activation Key', + getSettingValue('🔍MagnifyGlass.ActivationKey', 'x'), ACTIVATION_KEYS, + (value) => setSettingValue('🔍MagnifyGlass.ActivationKey', value), + 'Key to toggle or hold to activate the magnifying glass' + )); + + hotkeySection.body.appendChild(createSelect('Reset Key', + getSettingValue('🔍MagnifyGlass.ResetKey', 'o'), RESET_KEYS, + (value) => setSettingValue('🔍MagnifyGlass.ResetKey', value), + 'Key to reset glass position and disable follow cursor' + )); + + hotkeySection.body.appendChild(createSelect('Toggle Follow Key', + getSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h'), TOGGLE_FOLLOW_KEYS, + (value) => setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', value), + 'Key to toggle follow cursor mode' + )); + + hotkeySection.body.appendChild(createToggle('Require Alt Key', + getSettingValue('🔍MagnifyGlass.AltRequired', false), + (checked) => setSettingValue('🔍MagnifyGlass.AltRequired', checked), + 'Require Alt key to be held with activation key' + )); + + hotkeySection.body.appendChild(createSlider('Offset Step', + getSettingValue('🔍MagnifyGlass.OffsetStep', 10), 1, 50, 1, 'px', + (value) => setSettingValue('🔍MagnifyGlass.OffsetStep', value), + 'Amount to move glass per arrow key press (pixels)' + )); + + container.appendChild(hotkeySection.section); + + // ===== Info Panel Section ===== + const panelSection = createSection('Info Panel', true); + + const toggleHotkey = getSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i').toUpperCase(); + const glassToggleKey = getSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g').toUpperCase(); + const pinHotkeyVal = getSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u').toUpperCase(); + + panelSection.body.appendChild(createToggle('Enable Panel', + getSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true), + (checked) => setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', checked), + `Show info panel with node details. Toggle: ${toggleHotkey}` + )); + + panelSection.body.appendChild(createSelect('Position', + getSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom'), PANEL_POSITIONS, + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelPosition', value), + 'Position of the info panel relative to the glass' + )); + + panelSection.body.appendChild(createSlider('Width', + getSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300), 200, 600, 20, 'px', + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelWidth', value), + 'Width of the info panel', + (value) => { + const infoPanel = (window as any).infoPanelManager; + if (infoPanel?.stateManager?.state?.settings) { + infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelWidth'] = value; + infoPanel.uiManager.applyStyles(); + + // Ensure visible and reflow if active + if (infoPanel.magnifyGlass.state.active) { + infoPanel.uiManager.show(); + infoPanel.positionManager.positionPanel(); + } + } + } + )); + + panelSection.body.appendChild(createSlider('Max Height', + getSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 300), 200, 1500, 50, 'px', + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', value), + 'Maximum height of the info panel', + (value) => { + console.log('[MaxHeight] Handler called with value:', value); + const infoPanel = (window as any).infoPanelManager; + console.log('[MaxHeight] infoPanelManager:', infoPanel ? 'found' : 'NOT FOUND'); + if (infoPanel?.stateManager?.state?.settings) { + console.log('[MaxHeight] Updating setting and calling applyStyles'); + infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelMaxHeight'] = value; + infoPanel.uiManager.applyStyles(); + + // Ensure visible and reflow if active + if (infoPanel.magnifyGlass.state.active) { + console.log('[MaxHeight] Glass is active, calling show() and positionPanel()'); + infoPanel.uiManager.show(); + infoPanel.positionManager.positionPanel(); + } else { + console.log('[MaxHeight] Glass is NOT active, skipping show()'); + } + } else { + console.warn('[MaxHeight] Check failed - stateManager/state/settings missing'); + } + } + )); + + panelSection.body.appendChild(createSlider('Opacity', + getSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95), 10, 100, 5, '%', + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', value), + 'Background opacity of the info panel', + (value) => { + const infoPanel = (window as any).infoPanelManager; + if (infoPanel?.stateManager?.state?.settings) { + infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelOpacity'] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + + panelSection.body.appendChild(createColorPicker('Text Color', + getSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280'), + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', value), + 'Text color in the info panel', + (value) => { + const infoPanel = (window as any).infoPanelManager; + if (infoPanel?.stateManager?.state?.settings) { + infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelTextColor'] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + + panelSection.body.appendChild(createColorPicker('Accent Color', + getSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6'), + (value) => setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', value), + 'Accent color for highlights', + (value) => { + const infoPanel = (window as any).infoPanelManager; + if (infoPanel?.stateManager?.state?.settings) { + infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelAccentColor'] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + + panelSection.body.appendChild(createToggle('Animations', + getSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false), + (checked) => setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', checked), + 'Enable smooth animations for panel updates' + )); + + panelSection.body.appendChild(createToggle('Hover Controls', + getSettingValue('🔍MagnifyGlass.ShowHoveringControls', true), + (checked) => setSettingValue('🔍MagnifyGlass.ShowHoveringControls', checked), + 'Show floating control buttons near the panel' + )); + + panelSection.body.appendChild(createSelect('Controls Position', + getSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom'), + ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'], + (value) => setSettingValue('🔍MagnifyGlass.ControlsPosition', value), + 'Position of floating control buttons' + )); + + container.appendChild(panelSection.section); + + // ===== Panel Hotkeys Section ===== + const panelHotkeySection = createSection('Panel Hotkeys', true); + + panelHotkeySection.body.appendChild(createSelect('Toggle Key', + getSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i'), ['i', 'j', 'k', 'l', 'n', 'm'], + (value) => setSettingValue('🔍MagnifyGlass.ToggleHotkey', value), + 'Key to toggle info panel visibility' + )); + + panelHotkeySection.body.appendChild(createSelect('Glass Toggle Key', + getSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g'), ['g', 'f', 'v', 'b'], + (value) => setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', value), + 'Key to toggle glass preview in panel' + )); + + panelHotkeySection.body.appendChild(createSelect('Pin Key', + getSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u'), ['u', 'p', 'y', 't'], + (value) => setSettingValue('🔍MagnifyGlass.PinPanelHotkey', value), + 'Key to pin/unpin the info panel position' + )); + + container.appendChild(panelHotkeySection.section); + + // ===== Debug Section ===== + const debugSection = createSection('Debug', true); + + debugSection.body.appendChild(createToggle('Debug Mode', + getSettingValue('🔍MagnifyGlass.DebugMode', false), + (checked) => setSettingValue('🔍MagnifyGlass.DebugMode', checked), + 'Enable debug logging and visual indicators' + )); + + container.appendChild(debugSection.section); + + // ===== Button Row ===== + const buttonRow = document.createElement('div'); + buttonRow.style.padding = '16px'; + buttonRow.style.borderTop = '1px solid var(--border-color, #333)'; + buttonRow.style.display = 'flex'; + buttonRow.style.flexDirection = 'column'; + buttonRow.style.gap = '8px'; + + // Reset Position Button + const resetBtn = document.createElement('button'); + resetBtn.className = 'magnify-btn'; + resetBtn.style.width = '100%'; + resetBtn.innerHTML = `${Icons.reset} Reset Position`; + resetBtn.title = `Reset glass position and disable follow cursor. Hotkey: ${resetKey}`; + + resetBtn.addEventListener('click', (e) => { + e.stopPropagation(); + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass && magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + // Update follow cursor toggle in UI + const followToggles = container.querySelectorAll('.magnify-toggle-row'); + followToggles.forEach(row => { + const label = row.querySelector('label'); + if (label && label.textContent === 'Follow Cursor') { + const toggle = row.querySelector('.magnify-toggle'); + if (toggle) toggle.classList.remove('active'); + } + }); + } + }); + + buttonRow.appendChild(resetBtn); + + // Reset All Settings Button + const resetAllBtn = document.createElement('button'); + resetAllBtn.className = 'magnify-btn magnify-btn-secondary'; + resetAllBtn.style.width = '100%'; + resetAllBtn.innerHTML = `${Icons.reset} Reset All Settings`; + resetAllBtn.title = 'Reset all settings to their default values'; + + resetAllBtn.addEventListener('click', (e) => { + e.stopPropagation(); + + // Confirm with user + if (!confirm('Reset all MagnifyGlass settings to defaults?')) { + return; + } + + // Default glass settings + setSettingValue('🔍MagnifyGlass.ZoomFactor', 300); + setSettingValue('🔍MagnifyGlass.GlassSize', 250); + setSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square'); + setSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right'); + setSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear'); + setSettingValue('🔍MagnifyGlass.BorderWidth', 2); + setSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff'); + setSettingValue('🔍MagnifyGlass.BorderEnabled', true); + setSettingValue('🔍MagnifyGlass.FollowCursor', false); + setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true); + setSettingValue('🔍MagnifyGlass.ActivationKey', 'x'); + setSettingValue('🔍MagnifyGlass.ResetKey', 'o'); + setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h'); + setSettingValue('🔍MagnifyGlass.AltRequired', false); + setSettingValue('🔍MagnifyGlass.OffsetStep', 10); + setSettingValue('🔍MagnifyGlass.DebugMode', false); + + // Default panel settings + setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true); + setSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom'); + setSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300); + setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 600); + setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95); + setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280'); + setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6'); + setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false); + setSettingValue('🔍MagnifyGlass.ShowHoveringControls', true); + setSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom'); + setSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i'); + setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g'); + setSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u'); + + // Also reset position + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass && magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + } + }); + + buttonRow.appendChild(resetAllBtn); + container.appendChild(buttonRow); +} diff --git a/src/sidebar/index.ts b/src/sidebar/index.ts new file mode 100644 index 0000000..e6f72cd --- /dev/null +++ b/src/sidebar/index.ts @@ -0,0 +1,8 @@ +/** + * ComfyUI MagnifyGlass - Sidebar Index + * + * Re-exports all sidebar modules. + */ + +export { registerSidebar, initSidebar } from './sidebar'; +export { renderSettingsPanel } from './SidebarSettings'; diff --git a/src/sidebar/sidebar.ts b/src/sidebar/sidebar.ts new file mode 100644 index 0000000..dbb5236 --- /dev/null +++ b/src/sidebar/sidebar.ts @@ -0,0 +1,106 @@ +/** + * ComfyUI MagnifyGlass - Sidebar Entry Point + * + * Registers the MagnifyGlass sidebar tab with ComfyUI. + */ + +// @ts-ignore +import { app } from "/scripts/app.js"; +import { renderSettingsPanel } from './SidebarSettings'; + +/** + * Magnify glass icon SVG + */ +const MAGNIFY_ICON = ``; + +/** Track if sidebar has been registered */ +let sidebarRegistered = false; + +/** + * Load sidebar CSS + */ +function loadSidebarStyles(): void { + if (!document.getElementById('magnify-sidebar-styles')) { + const link = document.createElement('link'); + link.id = 'magnify-sidebar-styles'; + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = 'extensions/comfyui-magnifyglass/sidebar.css'; + document.head.appendChild(link); + } +} + +/** + * Render the sidebar content + */ +function renderSidebar(container: HTMLElement): void { + // Load styles + loadSidebarStyles(); + + // Clear existing content to prevent duplicates + container.innerHTML = ''; + + // Create main container + const sidebar = document.createElement('div'); + sidebar.className = 'magnify-sidebar'; + + // Header + const header = document.createElement('div'); + header.className = 'magnify-sidebar-header'; + header.innerHTML = `${MAGNIFY_ICON}

Magnify G.

`; + sidebar.appendChild(header); + + // Content area + const content = document.createElement('div'); + content.className = 'magnify-sidebar-content'; + + // Render settings panel (contains all settings) + renderSettingsPanel(content); + + sidebar.appendChild(content); + container.appendChild(sidebar); +} + +/** + * Register the sidebar tab with ComfyUI + */ +export function registerSidebar(): void { + // Prevent duplicate registration + if (sidebarRegistered) { + return; + } + + // Wait for app to be ready + if (!app.extensionManager) { + console.warn('MagnifyGlass: extensionManager not available, sidebar registration skipped'); + return; + } + + try { + app.extensionManager.registerSidebarTab({ + id: 'magnifyglass', + icon: 'pi pi-search', + title: 'Magnify G.', + tooltip: 'MagnifyGlass Settings', + type: 'custom', + render: (el: HTMLElement) => { + renderSidebar(el); + } + }); + + sidebarRegistered = true; + console.log('MagnifyGlass: Sidebar registered successfully'); + } catch (e) { + console.warn('MagnifyGlass: Failed to register sidebar:', e); + } +} + +/** + * Initialize sidebar (called after app setup) + */ +export function initSidebar(): void { + // Register on next tick to ensure app is ready + setTimeout(() => { + registerSidebar(); + }, 100); +} diff --git a/src/types/comfyui.d.ts b/src/types/comfyui.d.ts index 73c4f41..aace0b5 100644 --- a/src/types/comfyui.d.ts +++ b/src/types/comfyui.d.ts @@ -215,6 +215,7 @@ export interface MagnifyGlassInstance { updateMagnifiedView(): void; applyUiChanges(): void; toggle(): void; + resetOffsets(): void; } export interface MagnifyGlassState { diff --git a/web/info-panel-old.css b/web/info-panel-old.css new file mode 100644 index 0000000..ce9d79d --- /dev/null +++ b/web/info-panel-old.css @@ -0,0 +1,664 @@ +/** + * ComfyUI MagnifyGlass - Info Panel Styles + * + * Complete CSS styles for the info panel component. + */ + +/* SVG Icon Styling */ +.magnify-info-panel svg, +.floating-controls svg { + width: 1em; + height: 1em; + vertical-align: middle; + fill: none; + stroke: currentColor; + pointer-events: none; + /* Allow clicks to pass through to button */ +} + +.header-icon svg { + width: 18px; + height: 18px; +} + +.section-icon svg { + width: 18px; + height: 18px; +} + +.control-btn svg { + width: 14px; + height: 14px; +} + +.expand-icon svg { + width: 12px; + height: 12px; +} + +.empty-state-icon svg { + width: 36px; + height: 36px; + stroke-width: 1.5; +} + +.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: none; + /* Hidden initially - JS will show when positioned */ + 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; + cursor: grab; + transition: background 0.15s ease; +} + +.panel-header:hover { + background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.08)); +} + +.panel-header:active { + cursor: grabbing; +} + +.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.css b/web/info-panel.css index ce9d79d..864e259 100644 --- a/web/info-panel.css +++ b/web/info-panel.css @@ -50,6 +50,8 @@ font-size: 16px; line-height: 1.6; pointer-events: auto; + display: flex; + flex-direction: column; } .magnify-info-panel * { @@ -163,9 +165,16 @@ transition: none !important; } +.magnify-info-panel.panel-minimized { + height: auto !important; +} + .magnify-info-panel.panel-minimized .panel-content { max-height: 0 !important; + height: 0 !important; overflow: hidden; + padding: 0 !important; + margin: 0 !important; transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1); } @@ -310,7 +319,8 @@ .panel-content { padding: 0; - max-height: calc(100% - 60px); + flex: 1; + min-height: 0; overflow-y: auto; scrollbar-width: thin; } diff --git a/web/info-panel/InfoPanel.js b/web/info-panel/InfoPanel.js index c864f52..a467b8d 100644 --- a/web/info-panel/InfoPanel.js +++ b/web/info-panel/InfoPanel.js @@ -123,6 +123,7 @@ class InfoPanel { this.uiManager.applyStyles(); if (this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelEnabled"] && this.magnifyGlass.state.active) { this.uiManager.show(); + this.positionManager.positionPanel(); } else { this.uiManager.hide(); } diff --git a/web/info-panel/InfoPanel.js.map b/web/info-panel/InfoPanel.js.map index b622868..15acc9b 100644 --- a/web/info-panel/InfoPanel.js.map +++ b/web/info-panel/InfoPanel.js.map @@ -1 +1 @@ -{"version":3,"file":"InfoPanel.js","sources":["../../src/info-panel/InfoPanel.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Orchestrator (TypeScript)\n * \n * Complete Info Panel Manager extracted from magnify_info_panel.js\n * Main class that orchestrates all info panel components.\n */\n\nimport type { MagnifyGlassInstance } from '../types/comfyui';\nimport { registerPanelSettings } from '../shared/settings';\nimport { Logger } from '../shared/logger';\nimport { StateManager } from './StateManager';\nimport { UIManager } from './UIManager';\nimport { PositionManager } from './PositionManager';\nimport { EventManager } from './EventManager';\nimport { InformationGatherer } from './InformationGatherer';\n\n/**\n * Professional Info Panel Manager.\n * Orchestrates all the other components for the info panel.\n */\nexport class InfoPanel {\n magnifyGlass: MagnifyGlassInstance;\n stateManager: StateManager;\n uiManager: UIManager;\n positionManager: PositionManager;\n eventManager: EventManager;\n informationGatherer: InformationGatherer;\n\n constructor(magnifyGlass: MagnifyGlassInstance) {\n this.magnifyGlass = magnifyGlass;\n\n // Initialize Managers\n this.stateManager = new StateManager();\n this.uiManager = new UIManager(this.stateManager);\n this.informationGatherer = new InformationGatherer();\n\n // Position Manager needs the UI elements\n this.positionManager = new PositionManager(this.stateManager, this.uiManager.elements.panel!);\n\n // Event Manager needs state and UI manager\n this.eventManager = new EventManager(this.stateManager, this.uiManager.elements.panel!, this.positionManager, {\n toggleVisibility: () => {\n if (this.stateManager.state.isPanelVisible) {\n this.uiManager.hide();\n } else {\n this.uiManager.show();\n }\n },\n togglePin: () => {\n this.stateManager.togglePinning();\n this.uiManager.updatePinnedState();\n },\n updatePosition: () => this.positionManager.positionPanel()\n });\n\n this.hookIntoMagnifyGlass();\n\n // Register settings with ComfyUI\n registerPanelSettings(this.stateManager, this.uiManager, this.positionManager);\n\n Logger.info('Info Panel initialized successfully');\n }\n\n hookIntoMagnifyGlass(): void {\n // Hook into magnify glass update cycle\n const originalUpdateMagnifiedView = this.magnifyGlass.updateMagnifiedView.bind(this.magnifyGlass);\n\n this.magnifyGlass.updateMagnifiedView = (() => {\n originalUpdateMagnifiedView();\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] && this.magnifyGlass.state.active) {\n this.scheduleInfoUpdate();\n }\n }).bind(this);\n\n // Hook into show/hide\n const originalShow = this.magnifyGlass.ui.show.bind(this.magnifyGlass.ui);\n const originalHide = this.magnifyGlass.ui.hide.bind(this.magnifyGlass.ui);\n\n this.magnifyGlass.ui.show = (() => {\n originalShow();\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n // Restore state: If it was visible before hide, make it visible now.\n // Otherwise only show controls.\n if (this.stateManager.state.wasPanelVisibleBeforeHide) {\n this.uiManager.show();\n\n // Position properly\n setTimeout(() => {\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n } else {\n // Do NOT force panel visible. Only show controls.\n const showControls = this.stateManager.state.settings[\"🔍MagnifyGlass.ShowHoveringControls\"] !== false;\n if (showControls && this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'flex';\n }\n\n // Ensure states are updated\n this.uiManager.updateControlStates();\n\n // Position controls after showing (relative to glass since panel is hidden)\n setTimeout(() => {\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n }\n }\n }).bind(this);\n\n this.magnifyGlass.ui.hide = (() => {\n // Save state before hiding\n this.stateManager.state.wasPanelVisibleBeforeHide = this.stateManager.state.isPanelVisible;\n\n originalHide();\n this.uiManager.hide();\n // Explicitly hide floating controls when glass is disabled\n if (this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'none';\n }\n }).bind(this);\n }\n\n scheduleInfoUpdate(): void {\n if (this.stateManager.state.updateScheduled) return;\n\n this.stateManager.state.updateScheduled = true;\n requestAnimationFrame(() => {\n this.updateInfo();\n this.stateManager.state.updateScheduled = false;\n });\n }\n\n updateInfo(): void {\n const settings = this.stateManager.state.settings;\n const isActive = this.magnifyGlass.state.active;\n\n // Safety: If disabled or inactive, ensure hidden and return\n if (!settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] || !isActive) {\n if (this.uiManager.elements.controls && this.uiManager.elements.controls.style.display !== 'none') {\n this.uiManager.elements.controls.style.display = 'none';\n }\n return;\n }\n\n const info = this.informationGatherer.gatherInformation();\n this.stateManager.setCurrentInfo(info);\n\n // Update the UI\n this.uiManager.displayInfo(info);\n\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n\n // Handle node hover state logic if needed for internal state\n if (info.hoveredNode) {\n this.stateManager.state.isHoveringNode = true;\n this.stateManager.expandNodeSections();\n if (this.stateManager.state.lastNodeId !== info.hoveredNode.id) {\n this.stateManager.state.lastNodeId = info.hoveredNode.id;\n }\n } else {\n this.stateManager.state.isHoveringNode = false;\n if (this.stateManager.state.lastNodeId !== null) {\n this.stateManager.state.lastNodeId = null;\n if (!this.stateManager.state.isPanelHovered) {\n this.stateManager.scheduleAutoCollapse();\n }\n }\n }\n }\n\n updateSettings(): void {\n const changes = this.stateManager.updateSettings();\n this.uiManager.applyStyles();\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] && this.magnifyGlass.state.active) {\n this.uiManager.show();\n } else {\n this.uiManager.hide();\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"InfoPanel.js","sources":["../../src/info-panel/InfoPanel.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel Orchestrator (TypeScript)\n * \n * Complete Info Panel Manager extracted from magnify_info_panel.js\n * Main class that orchestrates all info panel components.\n */\n\nimport type { MagnifyGlassInstance } from '../types/comfyui';\nimport { registerPanelSettings } from '../shared/settings';\nimport { Logger } from '../shared/logger';\nimport { StateManager } from './StateManager';\nimport { UIManager } from './UIManager';\nimport { PositionManager } from './PositionManager';\nimport { EventManager } from './EventManager';\nimport { InformationGatherer } from './InformationGatherer';\n\n/**\n * Professional Info Panel Manager.\n * Orchestrates all the other components for the info panel.\n */\nexport class InfoPanel {\n magnifyGlass: MagnifyGlassInstance;\n stateManager: StateManager;\n uiManager: UIManager;\n positionManager: PositionManager;\n eventManager: EventManager;\n informationGatherer: InformationGatherer;\n\n constructor(magnifyGlass: MagnifyGlassInstance) {\n this.magnifyGlass = magnifyGlass;\n\n // Initialize Managers\n this.stateManager = new StateManager();\n this.uiManager = new UIManager(this.stateManager);\n this.informationGatherer = new InformationGatherer();\n\n // Position Manager needs the UI elements\n this.positionManager = new PositionManager(this.stateManager, this.uiManager.elements.panel!);\n\n // Event Manager needs state and UI manager\n this.eventManager = new EventManager(this.stateManager, this.uiManager.elements.panel!, this.positionManager, {\n toggleVisibility: () => {\n if (this.stateManager.state.isPanelVisible) {\n this.uiManager.hide();\n } else {\n this.uiManager.show();\n }\n },\n togglePin: () => {\n this.stateManager.togglePinning();\n this.uiManager.updatePinnedState();\n },\n updatePosition: () => this.positionManager.positionPanel()\n });\n\n this.hookIntoMagnifyGlass();\n\n // Register settings with ComfyUI\n registerPanelSettings(this.stateManager, this.uiManager, this.positionManager);\n\n Logger.info('Info Panel initialized successfully');\n }\n\n hookIntoMagnifyGlass(): void {\n // Hook into magnify glass update cycle\n const originalUpdateMagnifiedView = this.magnifyGlass.updateMagnifiedView.bind(this.magnifyGlass);\n\n this.magnifyGlass.updateMagnifiedView = (() => {\n originalUpdateMagnifiedView();\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] && this.magnifyGlass.state.active) {\n this.scheduleInfoUpdate();\n }\n }).bind(this);\n\n // Hook into show/hide\n const originalShow = this.magnifyGlass.ui.show.bind(this.magnifyGlass.ui);\n const originalHide = this.magnifyGlass.ui.hide.bind(this.magnifyGlass.ui);\n\n this.magnifyGlass.ui.show = (() => {\n originalShow();\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n // Restore state: If it was visible before hide, make it visible now.\n // Otherwise only show controls.\n if (this.stateManager.state.wasPanelVisibleBeforeHide) {\n this.uiManager.show();\n\n // Position properly\n setTimeout(() => {\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n } else {\n // Do NOT force panel visible. Only show controls.\n const showControls = this.stateManager.state.settings[\"🔍MagnifyGlass.ShowHoveringControls\"] !== false;\n if (showControls && this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'flex';\n }\n\n // Ensure states are updated\n this.uiManager.updateControlStates();\n\n // Position controls after showing (relative to glass since panel is hidden)\n setTimeout(() => {\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n }, 10);\n }\n }\n }).bind(this);\n\n this.magnifyGlass.ui.hide = (() => {\n // Save state before hiding\n this.stateManager.state.wasPanelVisibleBeforeHide = this.stateManager.state.isPanelVisible;\n\n originalHide();\n this.uiManager.hide();\n // Explicitly hide floating controls when glass is disabled\n if (this.uiManager.elements.controls) {\n this.uiManager.elements.controls.style.display = 'none';\n }\n }).bind(this);\n }\n\n scheduleInfoUpdate(): void {\n if (this.stateManager.state.updateScheduled) return;\n\n this.stateManager.state.updateScheduled = true;\n requestAnimationFrame(() => {\n this.updateInfo();\n this.stateManager.state.updateScheduled = false;\n });\n }\n\n updateInfo(): void {\n const settings = this.stateManager.state.settings;\n const isActive = this.magnifyGlass.state.active;\n\n // Safety: If disabled or inactive, ensure hidden and return\n if (!settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] || !isActive) {\n if (this.uiManager.elements.controls && this.uiManager.elements.controls.style.display !== 'none') {\n this.uiManager.elements.controls.style.display = 'none';\n }\n return;\n }\n\n const info = this.informationGatherer.gatherInformation();\n this.stateManager.setCurrentInfo(info);\n\n // Update the UI\n this.uiManager.displayInfo(info);\n\n this.positionManager.positionPanel();\n this.positionManager.positionFloatingControls(this.uiManager.elements.controls);\n\n // Handle node hover state logic if needed for internal state\n if (info.hoveredNode) {\n this.stateManager.state.isHoveringNode = true;\n this.stateManager.expandNodeSections();\n if (this.stateManager.state.lastNodeId !== info.hoveredNode.id) {\n this.stateManager.state.lastNodeId = info.hoveredNode.id;\n }\n } else {\n this.stateManager.state.isHoveringNode = false;\n if (this.stateManager.state.lastNodeId !== null) {\n this.stateManager.state.lastNodeId = null;\n if (!this.stateManager.state.isPanelHovered) {\n this.stateManager.scheduleAutoCollapse();\n }\n }\n }\n }\n\n updateSettings(): void {\n const changes = this.stateManager.updateSettings();\n this.uiManager.applyStyles();\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"] && this.magnifyGlass.state.active) {\n this.uiManager.show();\n // Ensure position is updated after potential dimension changes\n this.positionManager.positionPanel();\n } else {\n this.uiManager.hide();\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/PositionManager.js b/web/info-panel/PositionManager.js index e228b51..0c1eb5b 100644 --- a/web/info-panel/PositionManager.js +++ b/web/info-panel/PositionManager.js @@ -99,6 +99,11 @@ class PositionManager { */ positionFloatingControls(controlsElement) { if (!controlsElement) return; + const settings = this.stateManager.state.settings; + if (settings["🔍MagnifyGlass.ShowHoveringControls"] === false) { + controlsElement.style.display = "none"; + return; + } const isPanelVisible = this.stateManager.state.isPanelVisible; const magnifyGlass = window.comfyUIMagnifyGlass; let referenceRect = null; diff --git a/web/info-panel/PositionManager.js.map b/web/info-panel/PositionManager.js.map index 8bb1630..898cdff 100644 --- a/web/info-panel/PositionManager.js.map +++ b/web/info-panel/PositionManager.js.map @@ -1 +1 @@ -{"version":3,"file":"PositionManager.js","sources":["../../src/info-panel/PositionManager.ts"],"sourcesContent":["/**\n * Position Manager class.\n * Handles all positioning logic for the info panel.\n */\n\nimport { StateManager } from './StateManager';\n\nexport class PositionManager {\n stateManager: StateManager;\n panelElement: HTMLElement;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n }\n\n positionPanel(): void {\n if (!this.panelElement) return;\n\n // If panel is pinned, keep it at its pinned position\n if (this.stateManager.state.isPanelPinned) {\n this.applyPinnedPosition();\n return;\n }\n\n // Normal positioning logic\n this.calculateNormalPosition();\n }\n\n applyPinnedPosition(): void {\n if (!this.panelElement) return;\n\n let { x, y } = this.stateManager.state.pinnedPosition;\n\n // If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position\n if (x === 0 && y === 0) {\n this.calculateNormalPosition();\n // Save the calculated position as the new pinned position\n if (this.panelElement) {\n const rect = this.panelElement.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n return;\n }\n\n const panelWidth = this.panelElement.offsetWidth || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n const margin = 10;\n\n const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));\n const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));\n\n this.panelElement.style.left = `${boundedX}px`;\n this.panelElement.style.top = `${boundedY}px`;\n }\n\n calculateNormalPosition(): void {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass || !this.panelElement) return;\n\n const settings = this.stateManager.state.settings;\n const panelWidth = Number(settings[\"🔍MagnifyGlass.InfoPanelWidth\"]) || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n\n let left: number;\n let top: number;\n const margin = 15;\n\n // Check for valid glass rect first\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);\n\n // Check for valid mouse position\n const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;\n const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;\n const hasValidMousePosition = mouseX > 0 || mouseY > 0;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass is hidden, use mouse position\n if (hasValidMousePosition) {\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n } else if (hasValidGlassRect) {\n // Position relative to glass\n const position = settings[\"🔍MagnifyGlass.InfoPanelPosition\"];\n switch (position) {\n case \"Right\":\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n case \"Left\":\n left = glassRect!.left - panelWidth - margin;\n top = glassRect!.top;\n break;\n case \"Top\":\n left = glassRect!.left;\n top = glassRect!.top - panelHeight - margin;\n break;\n case \"Bottom\":\n left = glassRect!.left;\n top = glassRect!.bottom + margin;\n break;\n default:\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n }\n } else if (hasValidMousePosition) {\n // Fall back to mouse position\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Final fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n\n left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - panelHeight - 10));\n\n this.panelElement.style.left = `${left}px`;\n this.panelElement.style.top = `${top}px`;\n }\n\n /**\n * Position the floating controls relative to the panel.\n */\n positionFloatingControls(controlsElement: HTMLElement | null): void {\n if (!controlsElement) return;\n\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const magnifyGlass = window.comfyUIMagnifyGlass;\n let referenceRect: DOMRect | null = null;\n\n if (isPanelVisible && this.panelElement) {\n // Position relative to panel\n referenceRect = this.panelElement.getBoundingClientRect();\n } else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) {\n // Position relative to magnify glass\n referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect();\n // Don't show controls if glass is hidden/invalid\n if (referenceRect.width === 0 || referenceRect.height === 0) {\n controlsElement.style.display = 'none';\n return;\n }\n }\n\n if (!referenceRect) return;\n\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"right\";\n const margin = 8;\n\n let left: number;\n let top: number;\n\n switch (controlsPosition) {\n case \"left\":\n left = referenceRect.left - controlsElement.offsetWidth - margin;\n top = referenceRect.top;\n break;\n case \"right\":\n left = referenceRect.right + margin;\n top = referenceRect.top;\n break;\n case \"top\":\n left = referenceRect.left;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = referenceRect.left;\n top = referenceRect.bottom + margin;\n break;\n case \"top-right\":\n left = referenceRect.right - controlsElement.offsetWidth;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = referenceRect.right + margin;\n top = referenceRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"PositionManager.js","sources":["../../src/info-panel/PositionManager.ts"],"sourcesContent":["/**\n * Position Manager class.\n * Handles all positioning logic for the info panel.\n */\n\nimport { StateManager } from './StateManager';\n\nexport class PositionManager {\n stateManager: StateManager;\n panelElement: HTMLElement;\n\n constructor(stateManager: StateManager, panelElement: HTMLElement) {\n this.stateManager = stateManager;\n this.panelElement = panelElement;\n }\n\n positionPanel(): void {\n if (!this.panelElement) return;\n\n // If panel is pinned, keep it at its pinned position\n if (this.stateManager.state.isPanelPinned) {\n this.applyPinnedPosition();\n return;\n }\n\n // Normal positioning logic\n this.calculateNormalPosition();\n }\n\n applyPinnedPosition(): void {\n if (!this.panelElement) return;\n\n let { x, y } = this.stateManager.state.pinnedPosition;\n\n // If pinnedPosition is uninitialized (0,0), use calculateNormalPosition to get a valid position\n if (x === 0 && y === 0) {\n this.calculateNormalPosition();\n // Save the calculated position as the new pinned position\n if (this.panelElement) {\n const rect = this.panelElement.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n return;\n }\n\n const panelWidth = this.panelElement.offsetWidth || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n const margin = 10;\n\n const boundedX = Math.max(margin, Math.min(x, window.innerWidth - panelWidth - margin));\n const boundedY = Math.max(margin, Math.min(y, window.innerHeight - panelHeight - margin));\n\n this.panelElement.style.left = `${boundedX}px`;\n this.panelElement.style.top = `${boundedY}px`;\n }\n\n calculateNormalPosition(): void {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass || !this.panelElement) return;\n\n const settings = this.stateManager.state.settings;\n const panelWidth = Number(settings[\"🔍MagnifyGlass.InfoPanelWidth\"]) || 300;\n const panelHeight = this.panelElement.offsetHeight || 400;\n\n let left: number;\n let top: number;\n const margin = 15;\n\n // Check for valid glass rect first\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n const hasValidGlassRect = glassRect && (glassRect.right > 0 || glassRect.top > 0);\n\n // Check for valid mouse position\n const mouseX = magnifyGlass.lastKnownMousePosition?.x || 0;\n const mouseY = magnifyGlass.lastKnownMousePosition?.y || 0;\n const hasValidMousePosition = mouseX > 0 || mouseY > 0;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass is hidden, use mouse position\n if (hasValidMousePosition) {\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n } else if (hasValidGlassRect) {\n // Position relative to glass\n const position = settings[\"🔍MagnifyGlass.InfoPanelPosition\"];\n switch (position) {\n case \"Right\":\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n case \"Left\":\n left = glassRect!.left - panelWidth - margin;\n top = glassRect!.top;\n break;\n case \"Top\":\n left = glassRect!.left;\n top = glassRect!.top - panelHeight - margin;\n break;\n case \"Bottom\":\n left = glassRect!.left;\n top = glassRect!.bottom + margin;\n break;\n default:\n left = glassRect!.right + margin;\n top = glassRect!.top;\n break;\n }\n } else if (hasValidMousePosition) {\n // Fall back to mouse position\n left = mouseX - (panelWidth / 2);\n top = mouseY - 20;\n } else {\n // Final fallback: right side of screen\n left = window.innerWidth - panelWidth - 50;\n top = 100;\n }\n\n left = Math.max(10, Math.min(left, window.innerWidth - panelWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - panelHeight - 10));\n\n this.panelElement.style.left = `${left}px`;\n this.panelElement.style.top = `${top}px`;\n }\n\n /**\n * Position the floating controls relative to the panel.\n */\n positionFloatingControls(controlsElement: HTMLElement | null): void {\n if (!controlsElement) return;\n\n // Check if controls should be visible based on settings\n const settings = this.stateManager.state.settings;\n if (settings[\"🔍MagnifyGlass.ShowHoveringControls\"] === false) {\n controlsElement.style.display = 'none';\n return;\n }\n\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const magnifyGlass = window.comfyUIMagnifyGlass;\n let referenceRect: DOMRect | null = null;\n\n if (isPanelVisible && this.panelElement) {\n // Position relative to panel\n referenceRect = this.panelElement.getBoundingClientRect();\n } else if (magnifyGlass && magnifyGlass.ui && magnifyGlass.ui.glassDiv) {\n // Position relative to magnify glass\n referenceRect = magnifyGlass.ui.glassDiv.getBoundingClientRect();\n // Don't show controls if glass is hidden/invalid\n if (referenceRect.width === 0 || referenceRect.height === 0) {\n controlsElement.style.display = 'none';\n return;\n }\n }\n\n if (!referenceRect) return;\n\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"right\";\n const margin = 8;\n\n let left: number;\n let top: number;\n\n switch (controlsPosition) {\n case \"left\":\n left = referenceRect.left - controlsElement.offsetWidth - margin;\n top = referenceRect.top;\n break;\n case \"right\":\n left = referenceRect.right + margin;\n top = referenceRect.top;\n break;\n case \"top\":\n left = referenceRect.left;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = referenceRect.left;\n top = referenceRect.bottom + margin;\n break;\n case \"top-right\":\n left = referenceRect.right - controlsElement.offsetWidth;\n top = referenceRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = referenceRect.right + margin;\n top = referenceRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/info-panel/StateManager.js b/web/info-panel/StateManager.js index 9b9ff8c..4365c5d 100644 --- a/web/info-panel/StateManager.js +++ b/web/info-panel/StateManager.js @@ -11,7 +11,7 @@ class StateManager { // Panel visibility and positioning isPanelVisible: false, wasPanelVisibleBeforeHide: false, - isPanelMinimized: false, + isPanelMinimized: true, isPanelPinned: false, isPanelLocked: false, // New lock state @@ -155,9 +155,9 @@ class StateManager { } setupThemeButtonListeners() { document.addEventListener("click", (e) => { - var _a, _b, _c, _d, _e; + var _a, _b, _c, _d; const target = e.target; - const isThemeButton = ((_a = target.textContent) == null ? void 0 : _a.toLowerCase().includes("theme")) || ((_b = target.textContent) == null ? void 0 : _b.toLowerCase().includes("light")) || ((_c = target.textContent) == null ? void 0 : _c.toLowerCase().includes("dark")) || ((_d = target.title) == null ? void 0 : _d.toLowerCase().includes("theme")) || ((_e = target.className) == null ? void 0 : _e.toLowerCase().includes("theme")); + const isThemeButton = ((_a = target.textContent) == null ? void 0 : _a.toLowerCase().includes("theme")) || ((_b = target.textContent) == null ? void 0 : _b.toLowerCase().includes("light")) || ((_c = target.textContent) == null ? void 0 : _c.toLowerCase().includes("dark")) || ((_d = target.title) == null ? void 0 : _d.toLowerCase().includes("theme")) || typeof target.className === "string" && target.className.toLowerCase().includes("theme"); if (isThemeButton) { setTimeout(() => { if (this.detectCurrentTheme()) { diff --git a/web/info-panel/StateManager.js.map b/web/info-panel/StateManager.js.map index 5300259..2fb6e49 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 type { GatheredInfo, SettingsChange } from '../types/comfyui';\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 wasPanelVisibleBeforeHide: boolean; // Persist state across glass toggles\n isPanelMinimized: boolean;\n isPanelPinned: boolean;\n isPanelLocked: boolean; // New lock state\n isAutoPinned: boolean; // Track if panel was auto-pinned by glass hide\n pinnedPosition: { x: number; y: number };\n lastPinnedPosition: { x: number; y: number } | null; // Remember last pinned location\n\n // Glass visibility\n isGlassPreviewVisible: boolean;\n\n // Section expansion\n expandedSections: Set;\n\n // Interaction states\n isPanelHovered: boolean;\n isHoveringNode: boolean;\n\n // Auto-collapse\n autoExpandTimer: ReturnType | null;\n lastNodeId: string | number | null;\n\n // Update scheduling\n updateScheduled: boolean;\n isInitialLoading: boolean;\n\n // Current data\n currentInfo: GatheredInfo | null;\n\n // Settings cache - using index signature for dynamic ComfyUI settings\n settings: { [key: string]: string | number | boolean };\n\n // Auto-detected theme\n currentTheme: string;\n}\n\n/**\n * State Manager class.\n * Handles all state-related operations and provides clean state transitions.\n */\nexport class StateManager {\n state: InfoPanelStateData;\n\n constructor() {\n this.state = {\n // Panel visibility and positioning\n isPanelVisible: false,\n wasPanelVisibleBeforeHide: false,\n isPanelMinimized: false,\n isPanelPinned: false,\n isPanelLocked: false, // New lock state\n isAutoPinned: false, // Track if panel was auto-pinned by glass hide\n pinnedPosition: { x: 0, y: 0 },\n lastPinnedPosition: null, // Remember last pinned location\n\n // Glass visibility\n isGlassPreviewVisible: true,\n\n // Section expansion\n expandedSections: new Set(['node']),\n\n // Interaction states\n isPanelHovered: false,\n isHoveringNode: false,\n\n // Auto-collapse\n autoExpandTimer: null,\n lastNodeId: null,\n\n // Update scheduling\n updateScheduled: false,\n isInitialLoading: false,\n\n // Current data\n currentInfo: null,\n\n // 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(): Record {\n const oldSettings = { ...this.state.settings };\n this.loadSettings();\n\n // Return what changed for reactive updates\n const changes: Record = {};\n Object.keys(this.state.settings).forEach(key => {\n if (oldSettings[key] !== this.state.settings[key]) {\n changes[key] = {\n old: oldSettings[key],\n new: this.state.settings[key]\n };\n }\n });\n\n return changes;\n }\n\n togglePanelVisibility(): boolean {\n this.state.isPanelVisible = !this.state.isPanelVisible;\n return this.state.isPanelVisible;\n }\n\n toggleGlassPreview(): boolean {\n this.state.isGlassPreviewVisible = !this.state.isGlassPreviewVisible;\n return this.state.isGlassPreviewVisible;\n }\n\n togglePinning(): boolean {\n const wasPinned = this.state.isPanelPinned;\n this.state.isPanelPinned = !this.state.isPanelPinned;\n\n // When unpinning, also unlock and clear auto-pin flag\n if (!this.state.isPanelPinned) {\n this.state.isPanelLocked = false;\n this.state.isAutoPinned = false; // Clear auto-pin flag when manually unpinning\n }\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: GatheredInfo): 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 type { GatheredInfo, SettingsChange } from '../types/comfyui';\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 wasPanelVisibleBeforeHide: boolean; // Persist state across glass toggles\n isPanelMinimized: boolean;\n isPanelPinned: boolean;\n isPanelLocked: boolean; // New lock state\n isAutoPinned: boolean; // Track if panel was auto-pinned by glass hide\n pinnedPosition: { x: number; y: number };\n lastPinnedPosition: { x: number; y: number } | null; // Remember last pinned location\n\n // Glass visibility\n isGlassPreviewVisible: boolean;\n\n // Section expansion\n expandedSections: Set;\n\n // Interaction states\n isPanelHovered: boolean;\n isHoveringNode: boolean;\n\n // Auto-collapse\n autoExpandTimer: ReturnType | null;\n lastNodeId: string | number | null;\n\n // Update scheduling\n updateScheduled: boolean;\n isInitialLoading: boolean;\n\n // Current data\n currentInfo: GatheredInfo | null;\n\n // Settings cache - using index signature for dynamic ComfyUI settings\n settings: { [key: string]: string | number | boolean };\n\n // Auto-detected theme\n currentTheme: string;\n}\n\n/**\n * State Manager class.\n * Handles all state-related operations and provides clean state transitions.\n */\nexport class StateManager {\n state: InfoPanelStateData;\n\n constructor() {\n this.state = {\n // Panel visibility and positioning\n isPanelVisible: false,\n wasPanelVisibleBeforeHide: false,\n isPanelMinimized: true,\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: null,\n\n // Settings cache\n settings: {},\n\n // Auto-detected theme\n currentTheme: 'dark' // Will be auto-detected\n };\n\n this.initThemeDetection();\n this.loadSettings();\n }\n\n initThemeDetection(): void {\n // Detect ComfyUI's current theme\n this.detectCurrentTheme();\n\n // Force an immediate check in case we missed the initial state\n setTimeout(() => {\n if (this.detectCurrentTheme()) {\n this.notifyThemeChange();\n }\n }, 1000);\n\n // Set up mutation observer to watch for theme changes\n this.setupThemeObserver();\n\n // Listen for system theme changes as fallback\n if (window.matchMedia) {\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');\n if (prefersDark.addListener) {\n prefersDark.addListener(() => this.detectCurrentTheme());\n } else if (prefersDark.addEventListener) {\n prefersDark.addEventListener('change', () => this.detectCurrentTheme());\n }\n }\n\n // Add click listener to theme toggle buttons for immediate detection\n this.setupThemeButtonListeners();\n }\n\n detectCurrentTheme(): boolean {\n let detectedTheme = 'dark'; // Default fallback\n\n // Method 1: Try to read from ComfyUI's color palette setting\n try {\n // ComfyUI stores theme in 'Comfy.ColorPalette' setting\n const colorPalette = getSettingValue('Comfy.ColorPalette', '');\n\n if (colorPalette) {\n // Extract theme name from various possible formats\n const paletteStr = String(colorPalette).toLowerCase();\n\n if (paletteStr.includes('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 (typeof target.className === 'string' && target.className.toLowerCase().includes('theme'));\n\n if (isThemeButton) {\n setTimeout(() => {\n if (this.detectCurrentTheme()) {\n this.notifyThemeChange();\n }\n }, 100);\n }\n });\n }\n\n notifyThemeChange(): void {\n // Update settings to reflect new theme\n this.state.settings[\"🔍MagnifyGlass.InfoPanelTheme\"] = this.state.currentTheme;\n\n // Notify UI components of theme change immediately\n if (window.infoPanelManager && window.infoPanelManager.uiManager) {\n window.infoPanelManager.uiManager.updateTheme(this.state.currentTheme);\n }\n }\n\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(): Record {\n const oldSettings = { ...this.state.settings };\n this.loadSettings();\n\n // Return what changed for reactive updates\n const changes: Record = {};\n Object.keys(this.state.settings).forEach(key => {\n if (oldSettings[key] !== this.state.settings[key]) {\n changes[key] = {\n old: oldSettings[key],\n new: this.state.settings[key]\n };\n }\n });\n\n return changes;\n }\n\n togglePanelVisibility(): boolean {\n this.state.isPanelVisible = !this.state.isPanelVisible;\n return this.state.isPanelVisible;\n }\n\n toggleGlassPreview(): boolean {\n this.state.isGlassPreviewVisible = !this.state.isGlassPreviewVisible;\n return this.state.isGlassPreviewVisible;\n }\n\n togglePinning(): boolean {\n const wasPinned = this.state.isPanelPinned;\n this.state.isPanelPinned = !this.state.isPanelPinned;\n\n // When unpinning, also unlock and clear auto-pin flag\n if (!this.state.isPanelPinned) {\n this.state.isPanelLocked = false;\n this.state.isAutoPinned = false; // Clear auto-pin flag when manually unpinning\n }\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: GatheredInfo): 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 bfb8037..7f66774 100644 --- a/web/info-panel/UIManager.js +++ b/web/info-panel/UIManager.js @@ -43,6 +43,9 @@ class UIManager { this.elements.panel.appendChild(this.elements.header); this.elements.panel.appendChild(this.elements.content); this.applyStyles(); + if (this.stateManager.state.isPanelMinimized) { + this.elements.panel.classList.add("panel-minimized"); + } document.body.appendChild(this.elements.panel); this.elements.panel.addEventListener("click", (e) => { const target = e.target; @@ -70,7 +73,7 @@ class UIManager { } } }); - if (this.stateManager.state.settings["🔍MagnifyGlass.ShowHoveringControls"]) { + if (!this.elements.controls) { this.createFloatingControls(); this.updateControlStates(); } @@ -214,6 +217,18 @@ class UIManager { applyStyles() { if (!this.elements.panel) return; const settings = this.stateManager.state.settings; + console.log(`[UIManager] applyStyles - MaxHeight: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}`); + this.elements.panel.style.width = `${settings["🔍MagnifyGlass.InfoPanelWidth"]}px`; + const maxHeight = settings["🔍MagnifyGlass.InfoPanelMaxHeight"]; + this.elements.panel.style.height = `${maxHeight}px`; + this.elements.panel.style.maxHeight = `${maxHeight}px`; + this.elements.panel.style.position = "absolute"; + this.elements.panel.style.zIndex = "99999"; + this.elements.panel.style.transform = "translateY(-10px)"; + this.elements.panel.style.pointerEvents = "auto"; + this.elements.panel.style.userSelect = "none"; + this.elements.panel.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"; + this.elements.panel.style.transition = settings["🔍MagnifyGlass.InfoPanelAnimations"] ? "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)" : "none"; const textColor = settings["🔍MagnifyGlass.InfoPanelTextColor"]; if (textColor && typeof textColor === "string") { const normalizedTextColor = textColor.startsWith("#") ? textColor : `#${textColor}`; @@ -227,22 +242,9 @@ class UIManager { if (this.stateManager.state.isPanelVisible) { const opacityPercent = Number(settings["🔍MagnifyGlass.InfoPanelOpacity"]) || 100; this.elements.panel.style.opacity = (opacityPercent / 100).toString(); + } else { + this.elements.panel.style.opacity = "0"; } - this.elements.panel.style.cssText = ` - position: absolute; - width: ${settings["🔍MagnifyGlass.InfoPanelWidth"]}px; - max-height: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}px; - z-index: 99999; - display: none; - opacity: 0; - transform: translateY(-10px); - transition: ${settings["🔍MagnifyGlass.InfoPanelAnimations"] ? "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)" : "none"}; - pointer-events: auto; - user-select: none; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - ${textColor && typeof textColor === "string" ? `--info-panel-text-color: ${textColor.startsWith("#") ? textColor : `#${textColor}`};` : ""} - ${accentColor && typeof accentColor === "string" ? `--info-panel-accent-color: ${accentColor.startsWith("#") ? accentColor : `#${accentColor}`};` : ""} - `; } /** * Show the panel. @@ -321,7 +323,7 @@ class UIManager { buildSections(info) { const sections = []; const settings = this.stateManager.state.settings; - if (settings["🔍MagnifyGlass.ShowInspectorTab"]) { + if (settings["🔍MagnifyGlass.ShowInspectorTab"] && info.cursor && info.cursor.canvas) { const inspectorContent = [ { label: "Cursor Canvas", value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` }, { label: "Canvas Scale", value: `${(info.canvas.scale * 100).toFixed(1)}%` }, diff --git a/web/info-panel/UIManager.js.map b/web/info-panel/UIManager.js.map index 6db8230..bfa0698 100644 --- a/web/info-panel/UIManager.js.map +++ b/web/info-panel/UIManager.js.map @@ -1 +1 @@ -{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\nimport { Logger } from '../shared/logger';\nimport { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';\nimport {\n getCheckpointInfo,\n getImageInfo,\n getTextBoxContent,\n getImportantNodeParameters,\n type ImageInfoResult\n} from './NodeDataExtractor';\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
${Icons.magnifyGlass}
\n
Inspector
\n
Real-time analysis
\n
\n
\n \n
\n `;\n\n // Content container\n this.elements.content = document.createElement(\"div\");\n this.elements.content.className = \"panel-content\";\n\n this.elements.panel.appendChild(this.elements.header);\n this.elements.panel.appendChild(this.elements.content);\n\n this.applyStyles();\n document.body.appendChild(this.elements.panel);\n\n // Add click event delegation for panel elements\n this.elements.panel.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n\n // Handle minimize button\n const minimizeBtn = target.closest('[data-action=\"minimize\"]');\n if (minimizeBtn) {\n this.stateManager.state.isPanelMinimized = !this.stateManager.state.isPanelMinimized;\n this.updateMinimizedState();\n return;\n }\n\n // Handle section header clicks for expand/collapse\n const sectionHeader = target.closest('.section-header') as HTMLElement;\n if (sectionHeader) {\n const sectionId = sectionHeader.getAttribute('data-section');\n // Skip node section (always expanded)\n if (sectionId && sectionId !== 'node') {\n const isExpanded = this.stateManager.state.expandedSections.has(sectionId);\n if (isExpanded) {\n this.stateManager.state.expandedSections.delete(sectionId);\n } else {\n this.stateManager.state.expandedSections.add(sectionId);\n }\n\n // Update visual state\n sectionHeader.classList.toggle('expanded', !isExpanded);\n const sectionContent = sectionHeader.nextElementSibling as HTMLElement;\n if (sectionContent && sectionContent.classList.contains('section-content')) {\n sectionContent.classList.toggle('expanded', !isExpanded);\n }\n }\n }\n });\n\n // Create floating controls after panel is in DOM\n if (this.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\n /**\n * BUTTON MAPPING DOCUMENTATION:\n * =============================\n * Button 1 (Unlock Icon): data-action=\"pin\" → controls isPanelPinned\n * - Unpinned (default): Panel follows magnify glass\n * - Pinned: Panel stays at fixed position (unlocked from following glass)\n * \n * Button 2 (Pin Icon): data-action=\"lock\" → controls isPanelLocked\n * - Unlocked (default): Panel can be dragged\n * - Locked: Panel cannot be dragged (pinned in place)\n * \n * NOTE: The data-action names are counterintuitive but kept for backwards compatibility.\n * The ICONS correctly represent the functionality:\n * - Unlock icon = unlock from following glass\n * - Pin icon = pin position (prevent drag)\n */\n this.elements.controls.innerHTML = `\n \n \n \n \n `;\n\n // Insert before the panel in the document body, not as a child\n document.body.appendChild(this.elements.controls);\n\n // Hide and position off-screen initially - will be shown when magnify glass activates and positions them\n this.elements.controls.style.display = 'none';\n this.elements.controls.style.visibility = 'hidden';\n this.elements.controls.style.left = '-9999px';\n this.elements.controls.style.top = '-9999px';\n\n // Add click event delegation for control buttons\n this.elements.controls.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n const button = target.closest('button[data-action]') as HTMLButtonElement;\n if (!button) return;\n\n const action = button.getAttribute('data-action');\n Logger.debug(`Control button clicked: ${action}`);\n\n switch (action) {\n case 'pin':\n this.stateManager.togglePinning();\n this.updatePinnedState();\n this.updateControlStates();\n break;\n case 'lock':\n this.stateManager.state.isPanelLocked = !this.stateManager.state.isPanelLocked;\n this.updateControlStates();\n if (this.elements.panel) {\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n }\n break;\n case 'toggle-panel':\n if (this.stateManager.state.isPanelVisible) {\n this.hide();\n } else {\n this.show();\n }\n this.updateControlStates();\n break;\n case 'toggle-glass':\n this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;\n\n // Link Glass Visibility to Panel Pinning\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass Hidden -> Enter \"Unlocked Mode\" (Pinned to Screen, Draggable)\n if (this.elements.panel) {\n const rect = this.elements.panel.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n this.stateManager.state.isPanelPinned = true;\n this.stateManager.state.isPanelLocked = false; // Ensure dragging is allowed\n } else {\n // Glass Shown -> Enter \"Locked Position\" (Follow Glass)\n this.stateManager.state.isPanelPinned = false;\n // Note: Dragging is automatically disabled when !isPanelPinned by EventManager logic\n }\n\n this.updateControlStates();\n this.updatePinnedState(); // Update visual class\n\n // Toggle ONLY the visual visibility (opacity) of the glass preview\n // The tool remains active so the inspector can track position\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.ui?.setPreviewVisibility) {\n magnifyGlass.ui.setPreviewVisibility(this.stateManager.state.isGlassPreviewVisible);\n }\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = String(this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\");\n this.updateControlsLayout(controlsPosition);\n\n // Initial state update\n this.updateControlStates();\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const isGlassVisible = this.stateManager.state.isGlassPreviewVisible;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n // Hide unlock button when panel is hidden\n pinBtn.style.display = isPanelVisible ? 'flex' : 'none';\n\n // Disable unlock (unpin) button if glass is hidden\n // This prevents entering \"Follow Mouse\" mode when glass is invisible\n if (!isGlassVisible) {\n pinBtn.disabled = true;\n pinBtn.style.opacity = '0.5';\n pinBtn.title = \"Cannot unlock panel from screen when glass preview is hidden\";\n } else {\n pinBtn.disabled = false;\n pinBtn.style.opacity = '';\n }\n }\n\n if (lockBtn) {\n // Only show pin button when panel is visible AND unlocked from glass\n const showLockBtn = isPanelVisible && this.stateManager.state.isPanelPinned;\n lockBtn.style.display = showLockBtn ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Unpin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n // Active means \"Panel is Visible\"\n visibilityBtn.classList.toggle('active', isPanelVisible);\n visibilityBtn.title = isPanelVisible ? \"Hide Panel\" : \"Show Panel\";\n\n // Disable hide button if glass is hidden (prevent hiding everything)\n if (!isGlassVisible) {\n visibilityBtn.disabled = true;\n visibilityBtn.style.opacity = '0.5';\n visibilityBtn.title = \"Cannot hide panel when glass preview is hidden\";\n } else {\n visibilityBtn.disabled = false;\n visibilityBtn.style.opacity = '';\n }\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', isGlassVisible);\n glassBtn.title = isGlassVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n\n // Only show glass toggle button if the inspector panel is visible\n glassBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n }\n\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\"] as string | undefined;\n if (textColor && typeof textColor === 'string') {\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\"] as string | undefined;\n if (accentColor && typeof accentColor === 'string') {\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 = Number(settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\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 && typeof textColor === 'string' ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}\n ${accentColor && typeof accentColor === 'string' ? `--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 // Controls are shown by positionFloatingControls() after proper positioning\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = Number(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n this.updateControlStates();\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n // Do NOT hide controls when panel is hidden - they should remain visible on glass\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 this.updateControlStates();\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\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: Icons.info,\n title: 'Inspector',\n content: inspectorContent\n });\n }\n\n // Media section\n if (info.media) {\n const mediaContent = [\n { label: 'Type', value: info.media.tagName },\n { label: 'Source', value: info.media.src }\n ];\n\n if (info.media.naturalSize) {\n mediaContent.push({ label: 'Natural', value: info.media.naturalSize });\n }\n\n sections.push({\n id: 'media',\n icon: Icons.camera,\n title: 'Media',\n badge: info.media.tagName,\n content: mediaContent\n });\n }\n\n // Node Details section\n if (info.hoveredNode) {\n const nodeContent = [\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 checkpoint = getCheckpointInfo(info.hoveredNode);\n if (checkpoint) {\n nodeContent.push({ label: 'Model', value: checkpoint });\n }\n\n const imgInfo = getImageInfo(info.hoveredNode);\n if (imgInfo) {\n if (typeof imgInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imgInfo });\n } else if (typeof imgInfo === 'object' && imgInfo !== null) {\n const imgResult = imgInfo as ImageInfoResult;\n nodeContent.push({ label: 'Image Size', value: `${imgResult.width}×${imgResult.height}` });\n if (imgResult.src) {\n nodeContent.push({ label: 'Image Source', value: imgResult.src });\n }\n }\n }\n\n const textContent = getTextBoxContent(info.hoveredNode);\n if (textContent) {\n nodeContent.push({ label: 'Text', value: textContent });\n }\n }\n\n const importantParameters = getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: Icons.box,\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n
\n
${Icons.mapPin}
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n
\n
\n ${section.icon}\n ${section.title}\n ${section.badge ? `${section.badge}` : ''}\n ${section.id !== 'node' ? `${Icons.chevronRight}` : ''}\n
\n
\n
\n ${section.content.map((item: any) => {\n const value = formatValue(item.value, item.label);\n const valueClass = getValueClass(item.value);\n const valueAttributes = getValueAttributes(item.value);\n 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 * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = String(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] || '');\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`;\n subtitleElement.style.color = accentColor;\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName}`;\n subtitleElement.style.color = accentColor;\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type}`;\n subtitleElement.style.color = accentColor;\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\nimport { Logger } from '../shared/logger';\nimport { formatValue, getValueClass, getValueAttributes, formatWidgetValue } from './ValueFormatter';\nimport {\n getCheckpointInfo,\n getImageInfo,\n getTextBoxContent,\n getImportantNodeParameters,\n type ImageInfoResult\n} from './NodeDataExtractor';\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
${Icons.magnifyGlass}
\n
Inspector
\n
Real-time analysis
\n
\n
\n \n
\n `;\n\n // Content container\n this.elements.content = document.createElement(\"div\");\n this.elements.content.className = \"panel-content\";\n\n this.elements.panel.appendChild(this.elements.header);\n this.elements.panel.appendChild(this.elements.content);\n\n this.applyStyles();\n // Apply initial minimized state if set\n if (this.stateManager.state.isPanelMinimized) {\n this.elements.panel.classList.add('panel-minimized');\n }\n document.body.appendChild(this.elements.panel);\n\n // Add click event delegation for panel elements\n this.elements.panel.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n\n // Handle minimize button\n const minimizeBtn = target.closest('[data-action=\"minimize\"]');\n if (minimizeBtn) {\n this.stateManager.state.isPanelMinimized = !this.stateManager.state.isPanelMinimized;\n this.updateMinimizedState();\n return;\n }\n\n // Handle section header clicks for expand/collapse\n const sectionHeader = target.closest('.section-header') as HTMLElement;\n if (sectionHeader) {\n const sectionId = sectionHeader.getAttribute('data-section');\n // Skip node section (always expanded)\n if (sectionId && sectionId !== 'node') {\n const isExpanded = this.stateManager.state.expandedSections.has(sectionId);\n if (isExpanded) {\n this.stateManager.state.expandedSections.delete(sectionId);\n } else {\n this.stateManager.state.expandedSections.add(sectionId);\n }\n\n // Update visual state\n sectionHeader.classList.toggle('expanded', !isExpanded);\n const sectionContent = sectionHeader.nextElementSibling as HTMLElement;\n if (sectionContent && sectionContent.classList.contains('section-content')) {\n sectionContent.classList.toggle('expanded', !isExpanded);\n }\n }\n }\n });\n\n // Create floating controls after panel is in DOM\n if (!this.elements.controls) {\n this.createFloatingControls();\n // Update control states immediately after creation\n this.updateControlStates();\n }\n }\n\n /**\n * Create floating control buttons.\n */\n createFloatingControls(): void {\n this.elements.controls = document.createElement(\"div\");\n this.elements.controls.className = \"floating-controls vertical-layout\"; // Default to vertical\n\n /**\n * BUTTON MAPPING DOCUMENTATION:\n * =============================\n * Button 1 (Unlock Icon): data-action=\"pin\" → controls isPanelPinned\n * - Unpinned (default): Panel follows magnify glass\n * - Pinned: Panel stays at fixed position (unlocked from following glass)\n * \n * Button 2 (Pin Icon): data-action=\"lock\" → controls isPanelLocked\n * - Unlocked (default): Panel can be dragged\n * - Locked: Panel cannot be dragged (pinned in place)\n * \n * NOTE: The data-action names are counterintuitive but kept for backwards compatibility.\n * The ICONS correctly represent the functionality:\n * - Unlock icon = unlock from following glass\n * - Pin icon = pin position (prevent drag)\n */\n this.elements.controls.innerHTML = `\n \n \n \n \n `;\n\n // Insert before the panel in the document body, not as a child\n document.body.appendChild(this.elements.controls);\n\n // Hide and position off-screen initially - will be shown when magnify glass activates and positions them\n this.elements.controls.style.display = 'none';\n this.elements.controls.style.visibility = 'hidden';\n this.elements.controls.style.left = '-9999px';\n this.elements.controls.style.top = '-9999px';\n\n // Add click event delegation for control buttons\n this.elements.controls.addEventListener('click', (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n const button = target.closest('button[data-action]') as HTMLButtonElement;\n if (!button) return;\n\n const action = button.getAttribute('data-action');\n Logger.debug(`Control button clicked: ${action}`);\n\n switch (action) {\n case 'pin':\n this.stateManager.togglePinning();\n this.updatePinnedState();\n this.updateControlStates();\n break;\n case 'lock':\n this.stateManager.state.isPanelLocked = !this.stateManager.state.isPanelLocked;\n this.updateControlStates();\n if (this.elements.panel) {\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n }\n break;\n case 'toggle-panel':\n if (this.stateManager.state.isPanelVisible) {\n this.hide();\n } else {\n this.show();\n }\n this.updateControlStates();\n break;\n case 'toggle-glass':\n this.stateManager.state.isGlassPreviewVisible = !this.stateManager.state.isGlassPreviewVisible;\n\n // Link Glass Visibility to Panel Pinning\n if (!this.stateManager.state.isGlassPreviewVisible) {\n // Glass Hidden -> Enter \"Unlocked Mode\" (Pinned to Screen, Draggable)\n if (this.elements.panel) {\n const rect = this.elements.panel.getBoundingClientRect();\n this.stateManager.state.pinnedPosition = { x: rect.left, y: rect.top };\n }\n this.stateManager.state.isPanelPinned = true;\n this.stateManager.state.isPanelLocked = false; // Ensure dragging is allowed\n } else {\n // Glass Shown -> Enter \"Locked Position\" (Follow Glass)\n this.stateManager.state.isPanelPinned = false;\n // Note: Dragging is automatically disabled when !isPanelPinned by EventManager logic\n }\n\n this.updateControlStates();\n this.updatePinnedState(); // Update visual class\n\n // Toggle ONLY the visual visibility (opacity) of the glass preview\n // The tool remains active so the inspector can track position\n const magnifyGlass = (window as any).comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.ui?.setPreviewVisibility) {\n magnifyGlass.ui.setPreviewVisibility(this.stateManager.state.isGlassPreviewVisible);\n }\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = String(this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\");\n this.updateControlsLayout(controlsPosition);\n\n // Initial state update\n this.updateControlStates();\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n const isPanelVisible = this.stateManager.state.isPanelVisible;\n const isGlassVisible = this.stateManager.state.isGlassPreviewVisible;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n // Hide unlock button when panel is hidden\n pinBtn.style.display = isPanelVisible ? 'flex' : 'none';\n\n // Disable unlock (unpin) button if glass is hidden\n // This prevents entering \"Follow Mouse\" mode when glass is invisible\n if (!isGlassVisible) {\n pinBtn.disabled = true;\n pinBtn.style.opacity = '0.5';\n pinBtn.title = \"Cannot unlock panel from screen when glass preview is hidden\";\n } else {\n pinBtn.disabled = false;\n pinBtn.style.opacity = '';\n }\n }\n\n if (lockBtn) {\n // Only show pin button when panel is visible AND unlocked from glass\n const showLockBtn = isPanelVisible && this.stateManager.state.isPanelPinned;\n lockBtn.style.display = showLockBtn ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Unpin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n // Active means \"Panel is Visible\"\n visibilityBtn.classList.toggle('active', isPanelVisible);\n visibilityBtn.title = isPanelVisible ? \"Hide Panel\" : \"Show Panel\";\n\n // Disable hide button if glass is hidden (prevent hiding everything)\n if (!isGlassVisible) {\n visibilityBtn.disabled = true;\n visibilityBtn.style.opacity = '0.5';\n visibilityBtn.title = \"Cannot hide panel when glass preview is hidden\";\n } else {\n visibilityBtn.disabled = false;\n visibilityBtn.style.opacity = '';\n }\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', isGlassVisible);\n glassBtn.title = isGlassVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n\n // Only show glass toggle button if the inspector panel is visible\n glassBtn.style.display = isPanelVisible ? 'flex' : 'none';\n }\n }\n\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 // Debug Log\n console.log(`[UIManager] applyStyles - MaxHeight: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}`);\n\n // 1. Apply dimensions\n this.elements.panel.style.width = `${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px`;\n // Set both height and max-height so the panel always shows at this size\n const maxHeight = settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"];\n this.elements.panel.style.height = `${maxHeight}px`;\n this.elements.panel.style.maxHeight = `${maxHeight}px`;\n\n // 2. Apply static styles (only needed once or if overwritten, but safe to set)\n this.elements.panel.style.position = 'absolute';\n this.elements.panel.style.zIndex = '99999';\n this.elements.panel.style.transform = 'translateY(-10px)';\n this.elements.panel.style.pointerEvents = 'auto';\n this.elements.panel.style.userSelect = 'none';\n this.elements.panel.style.fontFamily = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\";\n\n // 3. Apply transition\n this.elements.panel.style.transition = settings[\"🔍MagnifyGlass.InfoPanelAnimations\"]\n ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'\n : 'none';\n\n // 4. Apply Custom Colors (CSS Variables)\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"] as string | undefined;\n if (textColor && typeof textColor === 'string') {\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] as string | undefined;\n if (accentColor && typeof accentColor === 'string') {\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // 5. Apply Opacity\n // Logic: If visible, use setting. If hidden, use 0.\n // We do NOT touch 'display' here to avoid hiding it if it's currently shown.\n // 'display' is managed by show() / hide() methods exclusively.\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = Number(settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n } else {\n this.elements.panel.style.opacity = '0';\n }\n }\n\n /**\n * Show the panel.\n */\n show(): void {\n if (!this.elements.panel) return;\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n this.elements.panel.style.display = \"block\";\n // Controls are shown by positionFloatingControls() after proper positioning\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = Number(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"]) || 100;\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n this.updateControlStates();\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n // Do NOT hide controls when panel is hidden - they should remain visible on glass\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 this.updateControlStates();\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"] && info.cursor && info.cursor.canvas) {\n const inspectorContent = [\n { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` },\n { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` },\n { label: 'Magnifier Zoom', value: `${info.magnifier.zoomFactor}×` }\n ];\n\n sections.push({\n id: 'inspector',\n icon: Icons.info,\n title: 'Inspector',\n content: inspectorContent\n });\n }\n\n // Media section\n if (info.media) {\n const mediaContent = [\n { label: 'Type', value: info.media.tagName },\n { label: 'Source', value: info.media.src }\n ];\n\n if (info.media.naturalSize) {\n mediaContent.push({ label: 'Natural', value: info.media.naturalSize });\n }\n\n sections.push({\n id: 'media',\n icon: Icons.camera,\n title: 'Media',\n badge: info.media.tagName,\n content: mediaContent\n });\n }\n\n // Node Details section\n if (info.hoveredNode) {\n const nodeContent = [\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 checkpoint = getCheckpointInfo(info.hoveredNode);\n if (checkpoint) {\n nodeContent.push({ label: 'Model', value: checkpoint });\n }\n\n const imgInfo = getImageInfo(info.hoveredNode);\n if (imgInfo) {\n if (typeof imgInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imgInfo });\n } else if (typeof imgInfo === 'object' && imgInfo !== null) {\n const imgResult = imgInfo as ImageInfoResult;\n nodeContent.push({ label: 'Image Size', value: `${imgResult.width}×${imgResult.height}` });\n if (imgResult.src) {\n nodeContent.push({ label: 'Image Source', value: imgResult.src });\n }\n }\n }\n\n const textContent = getTextBoxContent(info.hoveredNode);\n if (textContent) {\n nodeContent.push({ label: 'Text', value: textContent });\n }\n }\n\n const importantParameters = getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: Icons.box,\n title: 'Node',\n badge: info.hoveredNode.type,\n content: nodeContent\n });\n }\n\n return sections;\n }\n\n /**\n * Render sections to the panel.\n * @param sections \n */\n renderSections(sections: any[]): void {\n if (!this.elements.content) return;\n\n if (sections.length === 0) {\n this.elements.content.innerHTML = `\n
\n
${Icons.mapPin}
\n
Empty canvas area
\n
\n `;\n return;\n }\n\n this.elements.content.innerHTML = sections.map(section => `\n
\n
\n ${section.icon}\n ${section.title}\n ${section.badge ? `${section.badge}` : ''}\n ${section.id !== 'node' ? `${Icons.chevronRight}` : ''}\n
\n
\n
\n ${section.content.map((item: any) => {\n const value = formatValue(item.value, item.label);\n const valueClass = getValueClass(item.value);\n const valueAttributes = getValueAttributes(item.value);\n 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 * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = String(this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"] || '');\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`;\n subtitleElement.style.color = accentColor;\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName}`;\n subtitleElement.style.color = accentColor;\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type}`;\n subtitleElement.style.color = accentColor;\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/EventHandler.js b/web/magnify-glass/EventHandler.js index aca3e14..0683416 100644 --- a/web/magnify-glass/EventHandler.js +++ b/web/magnify-glass/EventHandler.js @@ -3,7 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { isUserTyping } from "../shared/utils.js"; import { Logger } from "../shared/logger.js"; -import { DEFAULT_PADDING } from "../shared/constants.js"; +import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from "../shared/constants.js"; class EventHandler { constructor(magnifyGlass) { __publicField(this, "magnifyGlass"); @@ -156,7 +156,7 @@ class EventHandler { const glassSize = this.magnifyGlass.config.glassSize; if (!this.magnifyGlass.state.wasActivatedBefore && this.magnifyGlass.ui.glassDiv) { this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`; - this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`; + this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`; this.magnifyGlass.state.wasActivatedBefore = true; } else { this.magnifyGlass.ui.positionGlass(clientX, clientY); diff --git a/web/magnify-glass/EventHandler.js.map b/web/magnify-glass/EventHandler.js.map index e7a822c..8eb7e6d 100644 --- a/web/magnify-glass/EventHandler.js.map +++ b/web/magnify-glass/EventHandler.js.map @@ -1 +1 @@ -{"version":3,"file":"EventHandler.js","sources":["../../src/magnify-glass/EventHandler.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - EventHandler (TypeScript)\n * \n * Handles keyboard and mouse events for the magnifying glass.\n */\n\nimport { isUserTyping } from '../shared/utils';\nimport { Logger } from '../shared/logger';\nimport { DEFAULT_PADDING } from '../shared/constants';\nimport type { MagnifyGlass } from './MagnifyGlass';\n\n/**\n * Event Handler class.\n * Manages all keyboard and mouse event handling for the magnifier.\n */\nexport class EventHandler {\n magnifyGlass: MagnifyGlass;\n\n constructor(magnifyGlass: MagnifyGlass) {\n this.magnifyGlass = magnifyGlass;\n\n // Bind methods to maintain context\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleKeyUp = this.handleKeyUp.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n }\n\n /**\n * Attach all event listeners.\n */\n attachListeners(): void {\n document.addEventListener(\"keydown\", this.handleKeyDown);\n document.addEventListener(\"keyup\", this.handleKeyUp);\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n }\n\n /**\n * Detach all event listeners.\n */\n detachListeners(): void {\n document.removeEventListener(\"keydown\", this.handleKeyDown);\n document.removeEventListener(\"keyup\", this.handleKeyUp);\n document.removeEventListener(\"mousemove\", this.handleMouseMove);\n }\n\n /**\n * Handle keydown events.\n */\n handleKeyDown(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n // Don't handle hotkeys if user is typing in an input field\n if (isUserTyping()) {\n return;\n }\n\n // Magnifier activation\n Logger.debug(`KeyDown: ${e.key} (config: ${config.activationKey}, active: ${state.active})`);\n if (e.key.toLowerCase() === config.activationKey &&\n (!config.altRequired || e.altKey)) {\n Logger.debug('Activation key matched!');\n if (config.alwaysActiveMode) {\n this.magnifyGlass.toggle();\n } else {\n if (!state.active) {\n this.magnifyGlass.toggle();\n }\n }\n }\n\n // Manual offset adjustment keys (only when magnifier is active)\n if (state.active) {\n let offsetChanged = false;\n const stepSize = e.shiftKey ? config.offsetStep * 5 : config.offsetStep;\n\n if (e.key === \"ArrowUp\") {\n config.offsetY -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowDown\") {\n config.offsetY += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n config.offsetX -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n config.offsetX += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key.toLowerCase() === config.resetKey.toLowerCase() &&\n (!config.altRequired || e.altKey)) {\n this.magnifyGlass.resetOffsets();\n offsetChanged = true;\n e.preventDefault();\n }\n\n // Toggle Follow Cursor Key\n if (e.key.toLowerCase() === config.toggleFollowCursorKey &&\n (!config.altRequired || e.altKey)) {\n config.followCursor = !config.followCursor;\n this.magnifyGlass.debugger.log(`Follow Cursor Toggled: ${config.followCursor ? 'ON' : 'OFF'}`);\n if (state.active && config.followCursor) {\n this.magnifyGlass.ui.positionGlass(\n this.magnifyGlass.lastKnownMousePosition.x,\n this.magnifyGlass.lastKnownMousePosition.y\n );\n }\n e.preventDefault();\n }\n\n if (offsetChanged) {\n config.saveOffsets();\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n }\n\n /**\n * Handle keyup events.\n */\n handleKeyUp(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n if (!config.alwaysActiveMode &&\n (e.key.toLowerCase() === config.activationKey ||\n (config.altRequired && e.key === \"Alt\"))) {\n\n let shouldDeactivate = false;\n if (config.altRequired) {\n if (e.key === \"Alt\" || e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n } else {\n if (e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n }\n\n if (state.active && shouldDeactivate) {\n state.active = false;\n this.magnifyGlass.ui.hide();\n }\n }\n }\n\n /**\n * Handle mousemove events.\n */\n handleMouseMove(e: MouseEvent): void {\n // Always track mouse position\n this.magnifyGlass.lastKnownMousePosition.x = e.clientX;\n this.magnifyGlass.lastKnownMousePosition.y = e.clientY;\n\n if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const cssMouseXOnCanvas = e.clientX - rect.left;\n const cssMouseYOnCanvas = e.clientY - rect.top;\n\n // Check if the cursor is over the canvas element\n if (cssMouseXOnCanvas >= 0 && cssMouseXOnCanvas <= rect.width &&\n cssMouseYOnCanvas >= 0 && cssMouseYOnCanvas <= rect.height) {\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n this.magnifyGlass.ui.positionGlass(e.clientX, e.clientY);\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n\n /**\n * Update the initial position when magnifier is activated.\n */\n updateInitialPosition(): void {\n if (!this.magnifyGlass.litegraphCanvas) return;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const clientX = this.magnifyGlass.lastKnownMousePosition.x;\n const clientY = this.magnifyGlass.lastKnownMousePosition.y;\n\n const isOverCanvas = clientX >= rect.left && clientX <= rect.right &&\n clientY >= rect.top && clientY <= rect.bottom;\n\n if (this.magnifyGlass.config.alwaysActiveMode || isOverCanvas) {\n const cssMouseXOnCanvas = clientX - rect.left;\n const cssMouseYOnCanvas = clientY - rect.top;\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n const glassSize = this.magnifyGlass.config.glassSize;\n\n if (!this.magnifyGlass.state.wasActivatedBefore && this.magnifyGlass.ui.glassDiv) {\n // First activation - position at top right of the window\n this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`;\n this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`;\n this.magnifyGlass.state.wasActivatedBefore = true;\n } else {\n this.magnifyGlass.ui.positionGlass(clientX, clientY);\n }\n\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n}\n"],"names":[],"mappings":";;;;;;AAeO,MAAM,aAAa;AAAA,EAGtB,YAAY,cAA4B;AAFxC;AAGI,SAAK,eAAe;AAGpB,SAAK,gBAAgB,KAAK,cAAc,KAAK,IAAI;AACjD,SAAK,cAAc,KAAK,YAAY,KAAK,IAAI;AAC7C,SAAK,kBAAkB,KAAK,gBAAgB,KAAK,IAAI;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAwB;AACpB,aAAS,iBAAiB,WAAW,KAAK,aAAa;AACvD,aAAS,iBAAiB,SAAS,KAAK,WAAW;AACnD,aAAS,iBAAiB,aAAa,KAAK,eAAe;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAwB;AACpB,aAAS,oBAAoB,WAAW,KAAK,aAAa;AAC1D,aAAS,oBAAoB,SAAS,KAAK,WAAW;AACtD,aAAS,oBAAoB,aAAa,KAAK,eAAe;AAAA,EAClE;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,GAAwB;AAClC,UAAM,SAAS,KAAK,aAAa;AACjC,UAAM,QAAQ,KAAK,aAAa;AAGhC,QAAI,gBAAgB;AAChB;AAAA,IACJ;AAGA,WAAO,MAAM,YAAY,EAAE,GAAG,aAAa,OAAO,aAAa,aAAa,MAAM,MAAM,GAAG;AAC3F,QAAI,EAAE,IAAI,YAAA,MAAkB,OAAO,kBAC9B,CAAC,OAAO,eAAe,EAAE,SAAS;AACnC,aAAO,MAAM,yBAAyB;AACtC,UAAI,OAAO,kBAAkB;AACzB,aAAK,aAAa,OAAA;AAAA,MACtB,OAAO;AACH,YAAI,CAAC,MAAM,QAAQ;AACf,eAAK,aAAa,OAAA;AAAA,QACtB;AAAA,MACJ;AAAA,IACJ;AAGA,QAAI,MAAM,QAAQ;AACd,UAAI,gBAAgB;AACpB,YAAM,WAAW,EAAE,WAAW,OAAO,aAAa,IAAI,OAAO;AAE7D,UAAI,EAAE,QAAQ,WAAW;AACrB,eAAO,WAAW;AAClB,wBAAgB;AAChB,UAAE,eAAA;AAAA,MACN,WAAW,EAAE,QAAQ,aAAa;AAC9B,eAAO,WAAW;AAClB,wBAAgB;AAChB,UAAE,eAAA;AAAA,MACN,WAAW,EAAE,QAAQ,aAAa;AAC9B,eAAO,WAAW;AAClB,wBAAgB;AAChB,UAAE,eAAA;AAAA,MACN,WAAW,EAAE,QAAQ,cAAc;AAC/B,eAAO,WAAW;AAClB,wBAAgB;AAChB,UAAE,eAAA;AAAA,MACN,WAAW,EAAE,IAAI,YAAA,MAAkB,OAAO,SAAS,YAAA,MAC9C,CAAC,OAAO,eAAe,EAAE,SAAS;AACnC,aAAK,aAAa,aAAA;AAClB,wBAAgB;AAChB,UAAE,eAAA;AAAA,MACN;AAGA,UAAI,EAAE,IAAI,YAAA,MAAkB,OAAO,0BAC9B,CAAC,OAAO,eAAe,EAAE,SAAS;AACnC,eAAO,eAAe,CAAC,OAAO;AAC9B,aAAK,aAAa,SAAS,IAAI,0BAA0B,OAAO,eAAe,OAAO,KAAK,EAAE;AAC7F,YAAI,MAAM,UAAU,OAAO,cAAc;AACrC,eAAK,aAAa,GAAG;AAAA,YACjB,KAAK,aAAa,uBAAuB;AAAA,YACzC,KAAK,aAAa,uBAAuB;AAAA,UAAA;AAAA,QAEjD;AACA,UAAE,eAAA;AAAA,MACN;AAEA,UAAI,eAAe;AACf,eAAO,YAAA;AACP,aAAK,aAAa,oBAAA;AAAA,MACtB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,GAAwB;AAChC,UAAM,SAAS,KAAK,aAAa;AACjC,UAAM,QAAQ,KAAK,aAAa;AAEhC,QAAI,CAAC,OAAO,qBACP,EAAE,IAAI,YAAA,MAAkB,OAAO,iBAC3B,OAAO,eAAe,EAAE,QAAQ,QAAS;AAE9C,UAAI,mBAAmB;AACvB,UAAI,OAAO,aAAa;AACpB,YAAI,EAAE,QAAQ,SAAS,EAAE,IAAI,YAAA,MAAkB,OAAO,eAAe;AACjE,6BAAmB;AAAA,QACvB;AAAA,MACJ,OAAO;AACH,YAAI,EAAE,IAAI,YAAA,MAAkB,OAAO,eAAe;AAC9C,6BAAmB;AAAA,QACvB;AAAA,MACJ;AAEA,UAAI,MAAM,UAAU,kBAAkB;AAClC,cAAM,SAAS;AACf,aAAK,aAAa,GAAG,KAAA;AAAA,MACzB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,GAAqB;AAEjC,SAAK,aAAa,uBAAuB,IAAI,EAAE;AAC/C,SAAK,aAAa,uBAAuB,IAAI,EAAE;AAE/C,QAAI,CAAC,KAAK,aAAa,MAAM,UAAU,CAAC,KAAK,aAAa,gBAAiB;AAE3E,UAAM,OAAO,KAAK,aAAa,gBAAgB,sBAAA;AAC/C,UAAM,oBAAoB,EAAE,UAAU,KAAK;AAC3C,UAAM,oBAAoB,EAAE,UAAU,KAAK;AAG3C,QAAI,qBAAqB,KAAK,qBAAqB,KAAK,SACpD,qBAAqB,KAAK,qBAAqB,KAAK,QAAQ;AAE5D,YAAM,gBAAgB,KAAK,aAAa;AACxC,YAAM,SAAS,KAAK,QAAQ,IAAI,cAAc,QAAQ,KAAK,QAAQ;AACnE,YAAM,SAAS,KAAK,SAAS,IAAI,cAAc,SAAS,KAAK,SAAS;AAEtE,YAAM,SAAS,oBAAoB;AACnC,YAAM,SAAS,oBAAoB;AAEnC,WAAK,aAAa,MAAM,IAAI;AAC5B,WAAK,aAAa,MAAM,IAAI;AAE5B,WAAK,aAAa,GAAG,cAAc,EAAE,SAAS,EAAE,OAAO;AACvD,WAAK,aAAa,oBAAA;AAAA,IACtB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA8B;AAC1B,QAAI,CAAC,KAAK,aAAa,gBAAiB;AAExC,UAAM,OAAO,KAAK,aAAa,gBAAgB,sBAAA;AAC/C,UAAM,UAAU,KAAK,aAAa,uBAAuB;AACzD,UAAM,UAAU,KAAK,aAAa,uBAAuB;AAEzD,UAAM,eAAe,WAAW,KAAK,QAAQ,WAAW,KAAK,SACzD,WAAW,KAAK,OAAO,WAAW,KAAK;AAE3C,QAAI,KAAK,aAAa,OAAO,oBAAoB,cAAc;AAC3D,YAAM,oBAAoB,UAAU,KAAK;AACzC,YAAM,oBAAoB,UAAU,KAAK;AAEzC,YAAM,gBAAgB,KAAK,aAAa;AACxC,YAAM,SAAS,KAAK,QAAQ,IAAI,cAAc,QAAQ,KAAK,QAAQ;AACnE,YAAM,SAAS,KAAK,SAAS,IAAI,cAAc,SAAS,KAAK,SAAS;AAEtE,YAAM,SAAS,oBAAoB;AACnC,YAAM,SAAS,oBAAoB;AAEnC,WAAK,aAAa,MAAM,IAAI;AAC5B,WAAK,aAAa,MAAM,IAAI;AAE5B,YAAM,YAAY,KAAK,aAAa,OAAO;AAE3C,UAAI,CAAC,KAAK,aAAa,MAAM,sBAAsB,KAAK,aAAa,GAAG,UAAU;AAE9E,aAAK,aAAa,GAAG,SAAS,MAAM,OAAO,GAAG,OAAO,aAAa,YAAY,eAAe;AAC7F,aAAK,aAAa,GAAG,SAAS,MAAM,MAAM,GAAG,eAAe;AAC5D,aAAK,aAAa,MAAM,qBAAqB;AAAA,MACjD,OAAO;AACH,aAAK,aAAa,GAAG,cAAc,SAAS,OAAO;AAAA,MACvD;AAEA,WAAK,aAAa,oBAAA;AAAA,IACtB;AAAA,EACJ;AACJ;"} \ No newline at end of file +{"version":3,"file":"EventHandler.js","sources":["../../src/magnify-glass/EventHandler.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - EventHandler (TypeScript)\n * \n * Handles keyboard and mouse events for the magnifying glass.\n */\n\nimport { isUserTyping } from '../shared/utils';\nimport { Logger } from '../shared/logger';\nimport { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';\nimport type { MagnifyGlass } from './MagnifyGlass';\n\n/**\n * Event Handler class.\n * Manages all keyboard and mouse event handling for the magnifier.\n */\nexport class EventHandler {\n magnifyGlass: MagnifyGlass;\n\n constructor(magnifyGlass: MagnifyGlass) {\n this.magnifyGlass = magnifyGlass;\n\n // Bind methods to maintain context\n this.handleKeyDown = this.handleKeyDown.bind(this);\n this.handleKeyUp = this.handleKeyUp.bind(this);\n this.handleMouseMove = this.handleMouseMove.bind(this);\n }\n\n /**\n * Attach all event listeners.\n */\n attachListeners(): void {\n document.addEventListener(\"keydown\", this.handleKeyDown);\n document.addEventListener(\"keyup\", this.handleKeyUp);\n document.addEventListener(\"mousemove\", this.handleMouseMove);\n }\n\n /**\n * Detach all event listeners.\n */\n detachListeners(): void {\n document.removeEventListener(\"keydown\", this.handleKeyDown);\n document.removeEventListener(\"keyup\", this.handleKeyUp);\n document.removeEventListener(\"mousemove\", this.handleMouseMove);\n }\n\n /**\n * Handle keydown events.\n */\n handleKeyDown(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n // Don't handle hotkeys if user is typing in an input field\n if (isUserTyping()) {\n return;\n }\n\n // Magnifier activation\n Logger.debug(`KeyDown: ${e.key} (config: ${config.activationKey}, active: ${state.active})`);\n if (e.key.toLowerCase() === config.activationKey &&\n (!config.altRequired || e.altKey)) {\n Logger.debug('Activation key matched!');\n if (config.alwaysActiveMode) {\n this.magnifyGlass.toggle();\n } else {\n if (!state.active) {\n this.magnifyGlass.toggle();\n }\n }\n }\n\n // Manual offset adjustment keys (only when magnifier is active)\n if (state.active) {\n let offsetChanged = false;\n const stepSize = e.shiftKey ? config.offsetStep * 5 : config.offsetStep;\n\n if (e.key === \"ArrowUp\") {\n config.offsetY -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowDown\") {\n config.offsetY += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowLeft\") {\n config.offsetX -= stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key === \"ArrowRight\") {\n config.offsetX += stepSize;\n offsetChanged = true;\n e.preventDefault();\n } else if (e.key.toLowerCase() === config.resetKey.toLowerCase() &&\n (!config.altRequired || e.altKey)) {\n this.magnifyGlass.resetOffsets();\n offsetChanged = true;\n e.preventDefault();\n }\n\n // Toggle Follow Cursor Key\n if (e.key.toLowerCase() === config.toggleFollowCursorKey &&\n (!config.altRequired || e.altKey)) {\n config.followCursor = !config.followCursor;\n this.magnifyGlass.debugger.log(`Follow Cursor Toggled: ${config.followCursor ? 'ON' : 'OFF'}`);\n if (state.active && config.followCursor) {\n this.magnifyGlass.ui.positionGlass(\n this.magnifyGlass.lastKnownMousePosition.x,\n this.magnifyGlass.lastKnownMousePosition.y\n );\n }\n e.preventDefault();\n }\n\n if (offsetChanged) {\n config.saveOffsets();\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n }\n\n /**\n * Handle keyup events.\n */\n handleKeyUp(e: KeyboardEvent): void {\n const config = this.magnifyGlass.config;\n const state = this.magnifyGlass.state;\n\n if (!config.alwaysActiveMode &&\n (e.key.toLowerCase() === config.activationKey ||\n (config.altRequired && e.key === \"Alt\"))) {\n\n let shouldDeactivate = false;\n if (config.altRequired) {\n if (e.key === \"Alt\" || e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n } else {\n if (e.key.toLowerCase() === config.activationKey) {\n shouldDeactivate = true;\n }\n }\n\n if (state.active && shouldDeactivate) {\n state.active = false;\n this.magnifyGlass.ui.hide();\n }\n }\n }\n\n /**\n * Handle mousemove events.\n */\n handleMouseMove(e: MouseEvent): void {\n // Always track mouse position\n this.magnifyGlass.lastKnownMousePosition.x = e.clientX;\n this.magnifyGlass.lastKnownMousePosition.y = e.clientY;\n\n if (!this.magnifyGlass.state.active || !this.magnifyGlass.litegraphCanvas) return;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const cssMouseXOnCanvas = e.clientX - rect.left;\n const cssMouseYOnCanvas = e.clientY - rect.top;\n\n // Check if the cursor is over the canvas element\n if (cssMouseXOnCanvas >= 0 && cssMouseXOnCanvas <= rect.width &&\n cssMouseYOnCanvas >= 0 && cssMouseYOnCanvas <= rect.height) {\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n this.magnifyGlass.ui.positionGlass(e.clientX, e.clientY);\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n\n /**\n * Update the initial position when magnifier is activated.\n */\n updateInitialPosition(): void {\n if (!this.magnifyGlass.litegraphCanvas) return;\n\n const rect = this.magnifyGlass.litegraphCanvas.getBoundingClientRect();\n const clientX = this.magnifyGlass.lastKnownMousePosition.x;\n const clientY = this.magnifyGlass.lastKnownMousePosition.y;\n\n const isOverCanvas = clientX >= rect.left && clientX <= rect.right &&\n clientY >= rect.top && clientY <= rect.bottom;\n\n if (this.magnifyGlass.config.alwaysActiveMode || isOverCanvas) {\n const cssMouseXOnCanvas = clientX - rect.left;\n const cssMouseYOnCanvas = clientY - rect.top;\n\n const canvasElement = this.magnifyGlass.litegraphCanvas;\n const scaleX = rect.width > 0 ? canvasElement.width / rect.width : 1;\n const scaleY = rect.height > 0 ? canvasElement.height / rect.height : 1;\n\n const pixelX = cssMouseXOnCanvas * scaleX;\n const pixelY = cssMouseYOnCanvas * scaleY;\n\n this.magnifyGlass.state.x = pixelX;\n this.magnifyGlass.state.y = pixelY;\n\n const glassSize = this.magnifyGlass.config.glassSize;\n\n if (!this.magnifyGlass.state.wasActivatedBefore && this.magnifyGlass.ui.glassDiv) {\n // First activation - position at top right of the window\n this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`;\n this.magnifyGlass.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.magnifyGlass.state.wasActivatedBefore = true;\n } else {\n this.magnifyGlass.ui.positionGlass(clientX, clientY);\n }\n\n this.magnifyGlass.updateMagnifiedView();\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/magnify-glass/MagnifyGlass.js b/web/magnify-glass/MagnifyGlass.js index 9073945..8319a3a 100644 --- a/web/magnify-glass/MagnifyGlass.js +++ b/web/magnify-glass/MagnifyGlass.js @@ -2,7 +2,7 @@ var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { findLiteGraphCanvas, rectsOverlap } from "../shared/utils.js"; -import { DEFAULT_PADDING } from "../shared/constants.js"; +import { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from "../shared/constants.js"; import { registerGlassSettings } from "../shared/settings/glassSettings.js"; import "/scripts/app.js"; import { ConfigManager } from "./ConfigManager.js"; @@ -285,13 +285,14 @@ class MagnifyGlass { */ resetOffsets() { this.config.resetOffsets(); + this.config.followCursor = false; if (this.state.active) { this.updateMagnifiedView(); } if (this.ui.glassDiv) { const glassSize = this.config.glassSize; this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`; - this.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`; + this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`; this.state.wasActivatedBefore = false; } const extensions = window.comfyUIMagnifyGlassExtensions; diff --git a/web/magnify-glass/MagnifyGlass.js.map b/web/magnify-glass/MagnifyGlass.js.map index 264e444..0bc50d1 100644 --- a/web/magnify-glass/MagnifyGlass.js.map +++ b/web/magnify-glass/MagnifyGlass.js.map @@ -1 +1 @@ -{"version":3,"file":"MagnifyGlass.js","sources":["../../src/magnify-glass/MagnifyGlass.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Main Class (TypeScript)\n * \n * Orchestrates all components of the magnifying glass.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\nimport { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils';\nimport { DEFAULT_PADDING } from '../shared/constants';\nimport { registerGlassSettings } from '../shared/settings';\nimport { ConfigManager } from './ConfigManager';\nimport { MagnifierState } from './MagnifierState';\nimport { UiManager } from './UiManager';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { DebugManager } from './DebugManager';\nimport { EventHandler } from './EventHandler';\n\n// External ComfyUI globals\ndeclare const app: ComfyApp;\ndeclare const LiteGraph: unknown;\n\n/**\n * Main MagnifyGlass class.\n * Orchestrates all components and provides the main API.\n */\nexport class MagnifyGlass {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n renderer: WebGLRenderer | null;\n debugger: DebugManager;\n eventHandler: EventHandler;\n\n /** The LiteGraph canvas */\n litegraphCanvas: HTMLCanvasElement | null;\n\n /** Last known mouse position for better initial positioning */\n lastKnownMousePosition: { x: number; y: number };\n\n /** Whether we're currently over media (for info panel) */\n isOverMedia: boolean;\n\n /** Current media element under cursor */\n currentMediaElement: HTMLImageElement | HTMLVideoElement | null;\n\n constructor() {\n this.config = new ConfigManager();\n this.state = new MagnifierState();\n this.ui = new UiManager(this.config, this.state, () => this.toggle());\n this.renderer = null;\n this.debugger = new DebugManager(this.config, this.state, this.ui);\n this.eventHandler = new EventHandler(this);\n\n // The LiteGraph canvas\n this.litegraphCanvas = null;\n\n // Last known mouse position for better initial positioning\n this.lastKnownMousePosition = { x: 0, y: 0 };\n\n // Media tracking\n this.isOverMedia = false;\n this.currentMediaElement = null;\n }\n\n /**\n * Initialize the magnifying glass.\n */\n init(): void {\n if (typeof LiteGraph === 'undefined' || typeof app === 'undefined' || !app.canvas) {\n this.debugger.log(\"LiteGraph or app not ready, retrying in 100ms.\");\n setTimeout(() => this.init(), 100);\n return;\n }\n this.debugger.log(\"LiteGraph and app ready.\");\n\n // Load saved offsets first\n this.config.loadSavedOffsets();\n\n // Print detailed canvas information\n this.debugger.printCanvasInfo();\n\n // Create UI elements\n this.ui.createElements();\n\n // Setup WebGL renderer\n this.renderer = new WebGLRenderer(this.config, this.state, this.ui);\n if (!this.renderer.isValid()) {\n this.ui.cleanup();\n return;\n }\n\n // Find the LiteGraph canvas\n this.litegraphCanvas = findLiteGraphCanvas();\n this.debugger.log(\"LiteGraph canvas found:\", this.litegraphCanvas);\n if (!this.litegraphCanvas) {\n this.debugger.error(\"Could not find LiteGraph canvas. Magnifier will not work.\");\n this.ui.cleanup();\n return;\n }\n\n // Attach event handlers\n this.eventHandler.attachListeners();\n\n // Register settings with ComfyUI\n registerGlassSettings(this);\n\n this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate.`);\n }\n\n /**\n * Toggle the magnifying glass active state.\n */\n toggle(): void {\n const state = this.state;\n const config = this.config;\n\n if (state.active) {\n state.active = false;\n this.ui.hide();\n } else {\n state.active = true;\n this.ui.show();\n // Update initial position if needed\n if (this.eventHandler) {\n this.eventHandler.updateInitialPosition();\n }\n }\n }\n\n /**\n * Update the magnified view.\n */\n updateMagnifiedView(): void {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n return;\n }\n\n // Get canvas transformation info\n this.updateCanvasTransformation();\n\n // Calculate the source region\n this.calculateSourceRegion();\n\n // Schedule the rendering operation\n if (!this.state.isRenderScheduled) {\n this.state.isRenderScheduled = true;\n requestAnimationFrame(() => {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n this.state.isRenderScheduled = false;\n return;\n }\n // Render the magnified view\n this.renderer.render(this.litegraphCanvas);\n\n // Update debug visualization\n this.debugger.updateDebugView();\n\n // Render HTML overlays\n this.renderHtmlOverlays();\n\n this.state.isRenderScheduled = false;\n });\n }\n }\n\n /**\n * Update canvas transformation state.\n */\n updateCanvasTransformation(): void {\n this.state.canvasScale = 1.0;\n this.state.canvasOffsetX = 0;\n this.state.canvasOffsetY = 0;\n\n if (app?.canvas) {\n const ds = (app.canvas as any).ds;\n if (ds) {\n if (typeof ds.scale === 'number') {\n this.state.canvasScale = ds.scale;\n }\n\n if (ds.offset) {\n this.state.canvasOffsetX = ds.offset[0] || 0;\n this.state.canvasOffsetY = ds.offset[1] || 0;\n }\n }\n }\n }\n\n /**\n * Calculate the source region for magnification.\n */\n calculateSourceRegion(): void {\n const cursorPixelX = this.state.x;\n const cursorPixelY = this.state.y;\n const canvasScale = this.state.canvasScale;\n const canvasOffsetX = this.state.canvasOffsetX;\n const canvasOffsetY = this.state.canvasOffsetY;\n\n if (canvasScale === 0) return;\n\n // Convert cursor canvas pixels to LiteGraph graph coordinates\n const cursorGraphX = (cursorPixelX - canvasOffsetX) / canvasScale;\n const cursorGraphY = (cursorPixelY - canvasOffsetY) / canvasScale;\n\n // Apply manual offset\n const targetGraphCenterX = cursorGraphX + this.config.offsetX;\n const targetGraphCenterY = cursorGraphY + this.config.offsetY;\n\n // Calculate source dimensions in graph units\n const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n\n // Calculate source top-left corner in graph coordinates\n const sourceGraphX = targetGraphCenterX - (sourceGraphWidth / 2);\n const sourceGraphY = targetGraphCenterY - (sourceGraphHeight / 2);\n\n // Convert back to canvas pixel coordinates\n this.state.sourceX = (sourceGraphX * canvasScale) + canvasOffsetX;\n this.state.sourceY = (sourceGraphY * canvasScale) + canvasOffsetY;\n this.state.sourceWidth = sourceGraphWidth * canvasScale;\n this.state.sourceHeight = sourceGraphHeight * canvasScale;\n }\n\n /**\n * Render HTML overlays for text and media in the magnified view.\n */\n renderHtmlOverlays(): void {\n const graph = (app as any).graph;\n if (!this.state.active || !this.ui.htmlOverlayContainer || !graph || !this.litegraphCanvas) {\n if (this.ui.htmlOverlayContainer) this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n this.ui.htmlOverlayContainer.innerHTML = '';\n\n const magnifyRect: Rectangle = {\n x: this.state.sourceX,\n y: this.state.sourceY,\n width: this.state.sourceWidth,\n height: this.state.sourceHeight\n };\n\n const nodes: ComfyNode[] = graph._nodes;\n if (!nodes) return;\n\n for (const node of nodes) {\n const widgets = (node as any).widgets;\n if (!widgets) continue;\n\n for (const widget of widgets) {\n let isTextElement = false;\n let isVideoElement = false;\n let isImageElement = false;\n let elementToProcess: HTMLElement | null = null;\n\n if (widget.element) {\n const element = widget.element as HTMLElement;\n if (widget.type === \"text\" || widget.type === \"string\" || element.tagName === 'TEXTAREA') {\n isTextElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'VIDEO') {\n isVideoElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'IMG') {\n isImageElement = true;\n elementToProcess = element;\n } else {\n const potentialVideo = element.querySelector('video');\n if (potentialVideo) {\n isVideoElement = true;\n elementToProcess = potentialVideo;\n } else {\n const potentialImage = element.querySelector('img');\n if (potentialImage) {\n isImageElement = true;\n elementToProcess = potentialImage;\n }\n }\n }\n }\n\n if (elementToProcess && (isTextElement || isVideoElement || isImageElement)) {\n const widgetRect = elementToProcess.getBoundingClientRect();\n const canvasRect = this.litegraphCanvas!.getBoundingClientRect();\n\n const canvasToViewportScaleX = canvasRect.width > 0 ? (this.litegraphCanvas!.width / canvasRect.width) : 1;\n const canvasToViewportScaleY = canvasRect.height > 0 ? (this.litegraphCanvas!.height / canvasRect.height) : 1;\n\n const widgetCanvasX = (widgetRect.left - canvasRect.left) * canvasToViewportScaleX;\n const widgetCanvasY = (widgetRect.top - canvasRect.top) * canvasToViewportScaleY;\n const widgetCanvasWidth = widgetRect.width * canvasToViewportScaleX;\n const widgetCanvasHeight = widgetRect.height * canvasToViewportScaleY;\n\n const widgetSourceRect: Rectangle = {\n x: widgetCanvasX,\n y: widgetCanvasY,\n width: widgetCanvasWidth,\n height: widgetCanvasHeight\n };\n\n if (rectsOverlap(magnifyRect, widgetSourceRect)) {\n const clonedElement = elementToProcess.cloneNode(true) as HTMLElement;\n clonedElement.style.position = 'absolute';\n clonedElement.style.pointerEvents = 'none';\n\n if (isTextElement) {\n clonedElement.style.backgroundColor = elementToProcess.style.backgroundColor || '#222';\n clonedElement.style.color = elementToProcess.style.color || '#DDD';\n clonedElement.style.border = elementToProcess.style.border || '1px solid #555';\n (clonedElement as HTMLInputElement).disabled = true;\n } else if (isVideoElement) {\n const video = clonedElement as HTMLVideoElement;\n const originalVideo = elementToProcess as HTMLVideoElement;\n video.src = originalVideo.src;\n video.autoplay = originalVideo.autoplay;\n video.loop = originalVideo.loop;\n video.preload = originalVideo.preload;\n video.crossOrigin = originalVideo.crossOrigin;\n video.muted = true;\n if (!originalVideo.paused) {\n video.play().catch(e => console.warn(\"Magnify Glass: Cloned video play failed\", e));\n }\n video.currentTime = originalVideo.currentTime;\n } else if (isImageElement) {\n const img = clonedElement as HTMLImageElement;\n const originalImg = elementToProcess as HTMLImageElement;\n img.src = originalImg.src;\n img.alt = originalImg.alt;\n }\n\n const relativeX = widgetSourceRect.x - magnifyRect.x;\n const relativeY = widgetSourceRect.y - magnifyRect.y;\n const magnifiedX = relativeX * this.config.zoomFactor;\n const magnifiedY = relativeY * this.config.zoomFactor;\n\n clonedElement.style.left = `${magnifiedX}px`;\n clonedElement.style.top = `${magnifiedY}px`;\n clonedElement.style.width = `${widgetSourceRect.width}px`;\n clonedElement.style.height = `${widgetSourceRect.height}px`;\n clonedElement.style.transformOrigin = 'top left';\n clonedElement.style.transform = `scale(${this.config.zoomFactor})`;\n\n if (isTextElement && clonedElement.style.fontSize) {\n const originalFontSize = parseFloat(window.getComputedStyle(elementToProcess).fontSize);\n clonedElement.style.fontSize = `${originalFontSize}px`;\n }\n\n this.ui.htmlOverlayContainer!.appendChild(clonedElement);\n }\n }\n }\n }\n }\n\n /**\n * Update config from current settings values.\n */\n updateConfigFromSettings(): void {\n this.config.loadSettings();\n }\n\n /**\n * Apply UI changes based on current config.\n */\n applyUiChanges(): void {\n this.ui.applyStyles();\n\n if (this.renderer?.gl) {\n this.renderer.updateViewport();\n }\n }\n\n\n\n /**\n * Reset offsets and panel positions to defaults.\n */\n resetOffsets(): void {\n this.config.resetOffsets();\n\n if (this.state.active) {\n this.updateMagnifiedView();\n }\n\n // Reset magnify glass position to top-right corner\n if (this.ui.glassDiv) {\n const glassSize = this.config.glassSize;\n this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_PADDING}px`;\n this.state.wasActivatedBefore = false;\n }\n\n // Reset inspector panel position if exists\n const extensions = window.comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n extensions.forEach((extension: any) => {\n if (extension?.stateManager) {\n extension.stateManager.state.isPanelPinned = false;\n extension.stateManager.state.isPanelLocked = false;\n extension.stateManager.state.isAutoPinned = false;\n extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };\n extension.stateManager.state.lastPinnedPosition = null;\n\n if (extension.positionManager) {\n extension.positionManager.positionPanel();\n }\n if (extension.uiManager?.updateControlStates) {\n extension.uiManager.updateControlStates();\n }\n }\n });\n }\n\n this.debugger.log(\"Reset: Magnify glass and inspector panel positions restored to defaults\");\n }\n\n /**\n * Cleanup all resources.\n */\n cleanup(): void {\n this.eventHandler.detachListeners();\n this.ui.cleanup();\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"MagnifyGlass.js","sources":["../../src/magnify-glass/MagnifyGlass.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Main Class (TypeScript)\n * \n * Orchestrates all components of the magnifying glass.\n */\n\nimport type { ComfyApp, ComfyNode } from '../types/comfyui';\nimport { findLiteGraphCanvas, rectsOverlap, Rectangle } from '../shared/utils';\nimport { DEFAULT_PADDING, DEFAULT_GLASS_Y_OFFSET } from '../shared/constants';\nimport { registerGlassSettings } from '../shared/settings';\nimport { ConfigManager } from './ConfigManager';\nimport { MagnifierState } from './MagnifierState';\nimport { UiManager } from './UiManager';\nimport { WebGLRenderer } from './WebGLRenderer';\nimport { DebugManager } from './DebugManager';\nimport { EventHandler } from './EventHandler';\n\n// External ComfyUI globals\ndeclare const app: ComfyApp;\ndeclare const LiteGraph: unknown;\n\n/**\n * Main MagnifyGlass class.\n * Orchestrates all components and provides the main API.\n */\nexport class MagnifyGlass {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n renderer: WebGLRenderer | null;\n debugger: DebugManager;\n eventHandler: EventHandler;\n\n /** The LiteGraph canvas */\n litegraphCanvas: HTMLCanvasElement | null;\n\n /** Last known mouse position for better initial positioning */\n lastKnownMousePosition: { x: number; y: number };\n\n /** Whether we're currently over media (for info panel) */\n isOverMedia: boolean;\n\n /** Current media element under cursor */\n currentMediaElement: HTMLImageElement | HTMLVideoElement | null;\n\n constructor() {\n this.config = new ConfigManager();\n this.state = new MagnifierState();\n this.ui = new UiManager(this.config, this.state, () => this.toggle());\n this.renderer = null;\n this.debugger = new DebugManager(this.config, this.state, this.ui);\n this.eventHandler = new EventHandler(this);\n\n // The LiteGraph canvas\n this.litegraphCanvas = null;\n\n // Last known mouse position for better initial positioning\n this.lastKnownMousePosition = { x: 0, y: 0 };\n\n // Media tracking\n this.isOverMedia = false;\n this.currentMediaElement = null;\n }\n\n /**\n * Initialize the magnifying glass.\n */\n init(): void {\n if (typeof LiteGraph === 'undefined' || typeof app === 'undefined' || !app.canvas) {\n this.debugger.log(\"LiteGraph or app not ready, retrying in 100ms.\");\n setTimeout(() => this.init(), 100);\n return;\n }\n this.debugger.log(\"LiteGraph and app ready.\");\n\n // Load saved offsets first\n this.config.loadSavedOffsets();\n\n // Print detailed canvas information\n this.debugger.printCanvasInfo();\n\n // Create UI elements\n this.ui.createElements();\n\n // Setup WebGL renderer\n this.renderer = new WebGLRenderer(this.config, this.state, this.ui);\n if (!this.renderer.isValid()) {\n this.ui.cleanup();\n return;\n }\n\n // Find the LiteGraph canvas\n this.litegraphCanvas = findLiteGraphCanvas();\n this.debugger.log(\"LiteGraph canvas found:\", this.litegraphCanvas);\n if (!this.litegraphCanvas) {\n this.debugger.error(\"Could not find LiteGraph canvas. Magnifier will not work.\");\n this.ui.cleanup();\n return;\n }\n\n // Attach event handlers\n this.eventHandler.attachListeners();\n\n // Register settings with ComfyUI\n registerGlassSettings(this);\n\n this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate.`);\n }\n\n /**\n * Toggle the magnifying glass active state.\n */\n toggle(): void {\n const state = this.state;\n const config = this.config;\n\n if (state.active) {\n state.active = false;\n this.ui.hide();\n } else {\n state.active = true;\n this.ui.show();\n // Update initial position if needed\n if (this.eventHandler) {\n this.eventHandler.updateInitialPosition();\n }\n }\n }\n\n /**\n * Update the magnified view.\n */\n updateMagnifiedView(): void {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n return;\n }\n\n // Get canvas transformation info\n this.updateCanvasTransformation();\n\n // Calculate the source region\n this.calculateSourceRegion();\n\n // Schedule the rendering operation\n if (!this.state.isRenderScheduled) {\n this.state.isRenderScheduled = true;\n requestAnimationFrame(() => {\n if (!this.state.active || !this.renderer || !this.litegraphCanvas) {\n this.state.isRenderScheduled = false;\n return;\n }\n // Render the magnified view\n this.renderer.render(this.litegraphCanvas);\n\n // Update debug visualization\n this.debugger.updateDebugView();\n\n // Render HTML overlays\n this.renderHtmlOverlays();\n\n this.state.isRenderScheduled = false;\n });\n }\n }\n\n /**\n * Update canvas transformation state.\n */\n updateCanvasTransformation(): void {\n this.state.canvasScale = 1.0;\n this.state.canvasOffsetX = 0;\n this.state.canvasOffsetY = 0;\n\n if (app?.canvas) {\n const ds = (app.canvas as any).ds;\n if (ds) {\n if (typeof ds.scale === 'number') {\n this.state.canvasScale = ds.scale;\n }\n\n if (ds.offset) {\n this.state.canvasOffsetX = ds.offset[0] || 0;\n this.state.canvasOffsetY = ds.offset[1] || 0;\n }\n }\n }\n }\n\n /**\n * Calculate the source region for magnification.\n */\n calculateSourceRegion(): void {\n const cursorPixelX = this.state.x;\n const cursorPixelY = this.state.y;\n const canvasScale = this.state.canvasScale;\n const canvasOffsetX = this.state.canvasOffsetX;\n const canvasOffsetY = this.state.canvasOffsetY;\n\n if (canvasScale === 0) return;\n\n // Convert cursor canvas pixels to LiteGraph graph coordinates\n const cursorGraphX = (cursorPixelX - canvasOffsetX) / canvasScale;\n const cursorGraphY = (cursorPixelY - canvasOffsetY) / canvasScale;\n\n // Apply manual offset\n const targetGraphCenterX = cursorGraphX + this.config.offsetX;\n const targetGraphCenterY = cursorGraphY + this.config.offsetY;\n\n // Calculate source dimensions in graph units\n const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / canvasScale;\n\n // Calculate source top-left corner in graph coordinates\n const sourceGraphX = targetGraphCenterX - (sourceGraphWidth / 2);\n const sourceGraphY = targetGraphCenterY - (sourceGraphHeight / 2);\n\n // Convert back to canvas pixel coordinates\n this.state.sourceX = (sourceGraphX * canvasScale) + canvasOffsetX;\n this.state.sourceY = (sourceGraphY * canvasScale) + canvasOffsetY;\n this.state.sourceWidth = sourceGraphWidth * canvasScale;\n this.state.sourceHeight = sourceGraphHeight * canvasScale;\n }\n\n /**\n * Render HTML overlays for text and media in the magnified view.\n */\n renderHtmlOverlays(): void {\n const graph = (app as any).graph;\n if (!this.state.active || !this.ui.htmlOverlayContainer || !graph || !this.litegraphCanvas) {\n if (this.ui.htmlOverlayContainer) this.ui.htmlOverlayContainer.innerHTML = '';\n return;\n }\n\n this.ui.htmlOverlayContainer.innerHTML = '';\n\n const magnifyRect: Rectangle = {\n x: this.state.sourceX,\n y: this.state.sourceY,\n width: this.state.sourceWidth,\n height: this.state.sourceHeight\n };\n\n const nodes: ComfyNode[] = graph._nodes;\n if (!nodes) return;\n\n for (const node of nodes) {\n const widgets = (node as any).widgets;\n if (!widgets) continue;\n\n for (const widget of widgets) {\n let isTextElement = false;\n let isVideoElement = false;\n let isImageElement = false;\n let elementToProcess: HTMLElement | null = null;\n\n if (widget.element) {\n const element = widget.element as HTMLElement;\n if (widget.type === \"text\" || widget.type === \"string\" || element.tagName === 'TEXTAREA') {\n isTextElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'VIDEO') {\n isVideoElement = true;\n elementToProcess = element;\n } else if (element.tagName === 'IMG') {\n isImageElement = true;\n elementToProcess = element;\n } else {\n const potentialVideo = element.querySelector('video');\n if (potentialVideo) {\n isVideoElement = true;\n elementToProcess = potentialVideo;\n } else {\n const potentialImage = element.querySelector('img');\n if (potentialImage) {\n isImageElement = true;\n elementToProcess = potentialImage;\n }\n }\n }\n }\n\n if (elementToProcess && (isTextElement || isVideoElement || isImageElement)) {\n const widgetRect = elementToProcess.getBoundingClientRect();\n const canvasRect = this.litegraphCanvas!.getBoundingClientRect();\n\n const canvasToViewportScaleX = canvasRect.width > 0 ? (this.litegraphCanvas!.width / canvasRect.width) : 1;\n const canvasToViewportScaleY = canvasRect.height > 0 ? (this.litegraphCanvas!.height / canvasRect.height) : 1;\n\n const widgetCanvasX = (widgetRect.left - canvasRect.left) * canvasToViewportScaleX;\n const widgetCanvasY = (widgetRect.top - canvasRect.top) * canvasToViewportScaleY;\n const widgetCanvasWidth = widgetRect.width * canvasToViewportScaleX;\n const widgetCanvasHeight = widgetRect.height * canvasToViewportScaleY;\n\n const widgetSourceRect: Rectangle = {\n x: widgetCanvasX,\n y: widgetCanvasY,\n width: widgetCanvasWidth,\n height: widgetCanvasHeight\n };\n\n if (rectsOverlap(magnifyRect, widgetSourceRect)) {\n const clonedElement = elementToProcess.cloneNode(true) as HTMLElement;\n clonedElement.style.position = 'absolute';\n clonedElement.style.pointerEvents = 'none';\n\n if (isTextElement) {\n clonedElement.style.backgroundColor = elementToProcess.style.backgroundColor || '#222';\n clonedElement.style.color = elementToProcess.style.color || '#DDD';\n clonedElement.style.border = elementToProcess.style.border || '1px solid #555';\n (clonedElement as HTMLInputElement).disabled = true;\n } else if (isVideoElement) {\n const video = clonedElement as HTMLVideoElement;\n const originalVideo = elementToProcess as HTMLVideoElement;\n video.src = originalVideo.src;\n video.autoplay = originalVideo.autoplay;\n video.loop = originalVideo.loop;\n video.preload = originalVideo.preload;\n video.crossOrigin = originalVideo.crossOrigin;\n video.muted = true;\n if (!originalVideo.paused) {\n video.play().catch(e => console.warn(\"Magnify Glass: Cloned video play failed\", e));\n }\n video.currentTime = originalVideo.currentTime;\n } else if (isImageElement) {\n const img = clonedElement as HTMLImageElement;\n const originalImg = elementToProcess as HTMLImageElement;\n img.src = originalImg.src;\n img.alt = originalImg.alt;\n }\n\n const relativeX = widgetSourceRect.x - magnifyRect.x;\n const relativeY = widgetSourceRect.y - magnifyRect.y;\n const magnifiedX = relativeX * this.config.zoomFactor;\n const magnifiedY = relativeY * this.config.zoomFactor;\n\n clonedElement.style.left = `${magnifiedX}px`;\n clonedElement.style.top = `${magnifiedY}px`;\n clonedElement.style.width = `${widgetSourceRect.width}px`;\n clonedElement.style.height = `${widgetSourceRect.height}px`;\n clonedElement.style.transformOrigin = 'top left';\n clonedElement.style.transform = `scale(${this.config.zoomFactor})`;\n\n if (isTextElement && clonedElement.style.fontSize) {\n const originalFontSize = parseFloat(window.getComputedStyle(elementToProcess).fontSize);\n clonedElement.style.fontSize = `${originalFontSize}px`;\n }\n\n this.ui.htmlOverlayContainer!.appendChild(clonedElement);\n }\n }\n }\n }\n }\n\n /**\n * Update config from current settings values.\n */\n updateConfigFromSettings(): void {\n this.config.loadSettings();\n }\n\n /**\n * Apply UI changes based on current config.\n */\n applyUiChanges(): void {\n this.ui.applyStyles();\n\n if (this.renderer?.gl) {\n this.renderer.updateViewport();\n }\n }\n\n\n\n /**\n * Reset offsets and panel positions to defaults.\n */\n resetOffsets(): void {\n this.config.resetOffsets();\n\n // Also disable follow cursor\n this.config.followCursor = false;\n\n if (this.state.active) {\n this.updateMagnifiedView();\n }\n\n // Reset magnify glass position to top-right corner\n if (this.ui.glassDiv) {\n const glassSize = this.config.glassSize;\n this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - DEFAULT_PADDING}px`;\n this.ui.glassDiv.style.top = `${DEFAULT_GLASS_Y_OFFSET}px`;\n this.state.wasActivatedBefore = false;\n }\n\n // Reset inspector panel position if exists\n const extensions = window.comfyUIMagnifyGlassExtensions;\n if (extensions && extensions.length > 0) {\n extensions.forEach((extension: any) => {\n if (extension?.stateManager) {\n extension.stateManager.state.isPanelPinned = false;\n extension.stateManager.state.isPanelLocked = false;\n extension.stateManager.state.isAutoPinned = false;\n extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };\n extension.stateManager.state.lastPinnedPosition = null;\n\n if (extension.positionManager) {\n extension.positionManager.positionPanel();\n }\n if (extension.uiManager?.updateControlStates) {\n extension.uiManager.updateControlStates();\n }\n }\n });\n }\n\n this.debugger.log(\"Reset: Magnify glass and inspector panel positions restored to defaults\");\n }\n\n /**\n * Cleanup all resources.\n */\n cleanup(): void {\n this.eventHandler.detachListeners();\n this.ui.cleanup();\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAyBO,MAAM,aAAa;AAAA,EAoBtB,cAAc;AAnBd;AACA;AACA;AACA;AACA;AACA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGA;AAAA;AAGI,SAAK,SAAS,IAAI,cAAA;AAClB,SAAK,QAAQ,IAAI,eAAA;AACjB,SAAK,KAAK,IAAI,UAAU,KAAK,QAAQ,KAAK,OAAO,MAAM,KAAK,QAAQ;AACpE,SAAK,WAAW;AAChB,SAAK,WAAW,IAAI,aAAa,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AACjE,SAAK,eAAe,IAAI,aAAa,IAAI;AAGzC,SAAK,kBAAkB;AAGvB,SAAK,yBAAyB,EAAE,GAAG,GAAG,GAAG,EAAA;AAGzC,SAAK,cAAc;AACnB,SAAK,sBAAsB;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACT,QAAI,OAAO,cAAc,eAAe,OAAO,QAAQ,eAAe,CAAC,IAAI,QAAQ;AAC/E,WAAK,SAAS,IAAI,gDAAgD;AAClE,iBAAW,MAAM,KAAK,KAAA,GAAQ,GAAG;AACjC;AAAA,IACJ;AACA,SAAK,SAAS,IAAI,0BAA0B;AAG5C,SAAK,OAAO,iBAAA;AAGZ,SAAK,SAAS,gBAAA;AAGd,SAAK,GAAG,eAAA;AAGR,SAAK,WAAW,IAAI,cAAc,KAAK,QAAQ,KAAK,OAAO,KAAK,EAAE;AAClE,QAAI,CAAC,KAAK,SAAS,WAAW;AAC1B,WAAK,GAAG,QAAA;AACR;AAAA,IACJ;AAGA,SAAK,kBAAkB,oBAAA;AACvB,SAAK,SAAS,IAAI,2BAA2B,KAAK,eAAe;AACjE,QAAI,CAAC,KAAK,iBAAiB;AACvB,WAAK,SAAS,MAAM,2DAA2D;AAC/E,WAAK,GAAG,QAAA;AACR;AAAA,IACJ;AAGA,SAAK,aAAa,gBAAA;AAGlB,0BAAsB,IAAI;AAE1B,SAAK,SAAS,IAAI,yDAAyD,KAAK,OAAO,cAAc,SAAS,EAAE,GAAG,KAAK,OAAO,cAAc,YAAA,CAAa,eAAe;AAAA,EAC7K;AAAA;AAAA;AAAA;AAAA,EAKA,SAAe;AACX,UAAM,QAAQ,KAAK;AACJ,SAAK;AAEpB,QAAI,MAAM,QAAQ;AACd,YAAM,SAAS;AACf,WAAK,GAAG,KAAA;AAAA,IACZ,OAAO;AACH,YAAM,SAAS;AACf,WAAK,GAAG,KAAA;AAER,UAAI,KAAK,cAAc;AACnB,aAAK,aAAa,sBAAA;AAAA,MACtB;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,sBAA4B;AACxB,QAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,iBAAiB;AAC/D;AAAA,IACJ;AAGA,SAAK,2BAAA;AAGL,SAAK,sBAAA;AAGL,QAAI,CAAC,KAAK,MAAM,mBAAmB;AAC/B,WAAK,MAAM,oBAAoB;AAC/B,4BAAsB,MAAM;AACxB,YAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,YAAY,CAAC,KAAK,iBAAiB;AAC/D,eAAK,MAAM,oBAAoB;AAC/B;AAAA,QACJ;AAEA,aAAK,SAAS,OAAO,KAAK,eAAe;AAGzC,aAAK,SAAS,gBAAA;AAGd,aAAK,mBAAA;AAEL,aAAK,MAAM,oBAAoB;AAAA,MACnC,CAAC;AAAA,IACL;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,6BAAmC;AAC/B,SAAK,MAAM,cAAc;AACzB,SAAK,MAAM,gBAAgB;AAC3B,SAAK,MAAM,gBAAgB;AAE3B,QAAI,2BAAK,QAAQ;AACb,YAAM,KAAM,IAAI,OAAe;AAC/B,UAAI,IAAI;AACJ,YAAI,OAAO,GAAG,UAAU,UAAU;AAC9B,eAAK,MAAM,cAAc,GAAG;AAAA,QAChC;AAEA,YAAI,GAAG,QAAQ;AACX,eAAK,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK;AAC3C,eAAK,MAAM,gBAAgB,GAAG,OAAO,CAAC,KAAK;AAAA,QAC/C;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,wBAA8B;AAC1B,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,eAAe,KAAK,MAAM;AAChC,UAAM,cAAc,KAAK,MAAM;AAC/B,UAAM,gBAAgB,KAAK,MAAM;AACjC,UAAM,gBAAgB,KAAK,MAAM;AAEjC,QAAI,gBAAgB,EAAG;AAGvB,UAAM,gBAAgB,eAAe,iBAAiB;AACtD,UAAM,gBAAgB,eAAe,iBAAiB;AAGtD,UAAM,qBAAqB,eAAe,KAAK,OAAO;AACtD,UAAM,qBAAqB,eAAe,KAAK,OAAO;AAGtD,UAAM,mBAAoB,KAAK,OAAO,YAAY,KAAK,OAAO,aAAc;AAC5E,UAAM,oBAAqB,KAAK,OAAO,YAAY,KAAK,OAAO,aAAc;AAG7E,UAAM,eAAe,qBAAsB,mBAAmB;AAC9D,UAAM,eAAe,qBAAsB,oBAAoB;AAG/D,SAAK,MAAM,UAAW,eAAe,cAAe;AACpD,SAAK,MAAM,UAAW,eAAe,cAAe;AACpD,SAAK,MAAM,cAAc,mBAAmB;AAC5C,SAAK,MAAM,eAAe,oBAAoB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA,EAKA,qBAA2B;AACvB,UAAM,QAAS,IAAY;AAC3B,QAAI,CAAC,KAAK,MAAM,UAAU,CAAC,KAAK,GAAG,wBAAwB,CAAC,SAAS,CAAC,KAAK,iBAAiB;AACxF,UAAI,KAAK,GAAG,qBAAsB,MAAK,GAAG,qBAAqB,YAAY;AAC3E;AAAA,IACJ;AAEA,SAAK,GAAG,qBAAqB,YAAY;AAEzC,UAAM,cAAyB;AAAA,MAC3B,GAAG,KAAK,MAAM;AAAA,MACd,GAAG,KAAK,MAAM;AAAA,MACd,OAAO,KAAK,MAAM;AAAA,MAClB,QAAQ,KAAK,MAAM;AAAA,IAAA;AAGvB,UAAM,QAAqB,MAAM;AACjC,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACtB,YAAM,UAAW,KAAa;AAC9B,UAAI,CAAC,QAAS;AAEd,iBAAW,UAAU,SAAS;AAC1B,YAAI,gBAAgB;AACpB,YAAI,iBAAiB;AACrB,YAAI,iBAAiB;AACrB,YAAI,mBAAuC;AAE3C,YAAI,OAAO,SAAS;AAChB,gBAAM,UAAU,OAAO;AACvB,cAAI,OAAO,SAAS,UAAU,OAAO,SAAS,YAAY,QAAQ,YAAY,YAAY;AACtF,4BAAgB;AAChB,+BAAmB;AAAA,UACvB,WAAW,QAAQ,YAAY,SAAS;AACpC,6BAAiB;AACjB,+BAAmB;AAAA,UACvB,WAAW,QAAQ,YAAY,OAAO;AAClC,6BAAiB;AACjB,+BAAmB;AAAA,UACvB,OAAO;AACH,kBAAM,iBAAiB,QAAQ,cAAc,OAAO;AACpD,gBAAI,gBAAgB;AAChB,+BAAiB;AACjB,iCAAmB;AAAA,YACvB,OAAO;AACH,oBAAM,iBAAiB,QAAQ,cAAc,KAAK;AAClD,kBAAI,gBAAgB;AAChB,iCAAiB;AACjB,mCAAmB;AAAA,cACvB;AAAA,YACJ;AAAA,UACJ;AAAA,QACJ;AAEA,YAAI,qBAAqB,iBAAiB,kBAAkB,iBAAiB;AACzE,gBAAM,aAAa,iBAAiB,sBAAA;AACpC,gBAAM,aAAa,KAAK,gBAAiB,sBAAA;AAEzC,gBAAM,yBAAyB,WAAW,QAAQ,IAAK,KAAK,gBAAiB,QAAQ,WAAW,QAAS;AACzG,gBAAM,yBAAyB,WAAW,SAAS,IAAK,KAAK,gBAAiB,SAAS,WAAW,SAAU;AAE5G,gBAAM,iBAAiB,WAAW,OAAO,WAAW,QAAQ;AAC5D,gBAAM,iBAAiB,WAAW,MAAM,WAAW,OAAO;AAC1D,gBAAM,oBAAoB,WAAW,QAAQ;AAC7C,gBAAM,qBAAqB,WAAW,SAAS;AAE/C,gBAAM,mBAA8B;AAAA,YAChC,GAAG;AAAA,YACH,GAAG;AAAA,YACH,OAAO;AAAA,YACP,QAAQ;AAAA,UAAA;AAGZ,cAAI,aAAa,aAAa,gBAAgB,GAAG;AAC7C,kBAAM,gBAAgB,iBAAiB,UAAU,IAAI;AACrD,0BAAc,MAAM,WAAW;AAC/B,0BAAc,MAAM,gBAAgB;AAEpC,gBAAI,eAAe;AACf,4BAAc,MAAM,kBAAkB,iBAAiB,MAAM,mBAAmB;AAChF,4BAAc,MAAM,QAAQ,iBAAiB,MAAM,SAAS;AAC5D,4BAAc,MAAM,SAAS,iBAAiB,MAAM,UAAU;AAC7D,4BAAmC,WAAW;AAAA,YACnD,WAAW,gBAAgB;AACvB,oBAAM,QAAQ;AACd,oBAAM,gBAAgB;AACtB,oBAAM,MAAM,cAAc;AAC1B,oBAAM,WAAW,cAAc;AAC/B,oBAAM,OAAO,cAAc;AAC3B,oBAAM,UAAU,cAAc;AAC9B,oBAAM,cAAc,cAAc;AAClC,oBAAM,QAAQ;AACd,kBAAI,CAAC,cAAc,QAAQ;AACvB,sBAAM,OAAO,MAAM,CAAA,MAAK,QAAQ,KAAK,2CAA2C,CAAC,CAAC;AAAA,cACtF;AACA,oBAAM,cAAc,cAAc;AAAA,YACtC,WAAW,gBAAgB;AACvB,oBAAM,MAAM;AACZ,oBAAM,cAAc;AACpB,kBAAI,MAAM,YAAY;AACtB,kBAAI,MAAM,YAAY;AAAA,YAC1B;AAEA,kBAAM,YAAY,iBAAiB,IAAI,YAAY;AACnD,kBAAM,YAAY,iBAAiB,IAAI,YAAY;AACnD,kBAAM,aAAa,YAAY,KAAK,OAAO;AAC3C,kBAAM,aAAa,YAAY,KAAK,OAAO;AAE3C,0BAAc,MAAM,OAAO,GAAG,UAAU;AACxC,0BAAc,MAAM,MAAM,GAAG,UAAU;AACvC,0BAAc,MAAM,QAAQ,GAAG,iBAAiB,KAAK;AACrD,0BAAc,MAAM,SAAS,GAAG,iBAAiB,MAAM;AACvD,0BAAc,MAAM,kBAAkB;AACtC,0BAAc,MAAM,YAAY,SAAS,KAAK,OAAO,UAAU;AAE/D,gBAAI,iBAAiB,cAAc,MAAM,UAAU;AAC/C,oBAAM,mBAAmB,WAAW,OAAO,iBAAiB,gBAAgB,EAAE,QAAQ;AACtF,4BAAc,MAAM,WAAW,GAAG,gBAAgB;AAAA,YACtD;AAEA,iBAAK,GAAG,qBAAsB,YAAY,aAAa;AAAA,UAC3D;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAKA,2BAAiC;AAC7B,SAAK,OAAO,aAAA;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAuB;;AACnB,SAAK,GAAG,YAAA;AAER,SAAI,UAAK,aAAL,mBAAe,IAAI;AACnB,WAAK,SAAS,eAAA;AAAA,IAClB;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAOA,eAAqB;AACjB,SAAK,OAAO,aAAA;AAGZ,SAAK,OAAO,eAAe;AAE3B,QAAI,KAAK,MAAM,QAAQ;AACnB,WAAK,oBAAA;AAAA,IACT;AAGA,QAAI,KAAK,GAAG,UAAU;AAClB,YAAM,YAAY,KAAK,OAAO;AAC9B,WAAK,GAAG,SAAS,MAAM,OAAO,GAAG,OAAO,aAAa,YAAY,eAAe;AAChF,WAAK,GAAG,SAAS,MAAM,MAAM,GAAG,sBAAsB;AACtD,WAAK,MAAM,qBAAqB;AAAA,IACpC;AAGA,UAAM,aAAa,OAAO;AAC1B,QAAI,cAAc,WAAW,SAAS,GAAG;AACrC,iBAAW,QAAQ,CAAC,cAAmB;;AACnC,YAAI,uCAAW,cAAc;AACzB,oBAAU,aAAa,MAAM,gBAAgB;AAC7C,oBAAU,aAAa,MAAM,gBAAgB;AAC7C,oBAAU,aAAa,MAAM,eAAe;AAC5C,oBAAU,aAAa,MAAM,iBAAiB,EAAE,GAAG,GAAG,GAAG,EAAA;AACzD,oBAAU,aAAa,MAAM,qBAAqB;AAElD,cAAI,UAAU,iBAAiB;AAC3B,sBAAU,gBAAgB,cAAA;AAAA,UAC9B;AACA,eAAI,eAAU,cAAV,mBAAqB,qBAAqB;AAC1C,sBAAU,UAAU,oBAAA;AAAA,UACxB;AAAA,QACJ;AAAA,MACJ,CAAC;AAAA,IACL;AAEA,SAAK,SAAS,IAAI,yEAAyE;AAAA,EAC/F;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACZ,SAAK,aAAa,gBAAA;AAClB,SAAK,GAAG,QAAA;AAAA,EACZ;AACJ;"} \ No newline at end of file diff --git a/web/magnify_glass.js b/web/magnify_glass.js index 2432f07..5ec9498 100644 --- a/web/magnify_glass.js +++ b/web/magnify_glass.js @@ -1,12 +1,14 @@ import { MagnifyGlass } from "./magnify-glass/MagnifyGlass.js"; import { app } from "/scripts/app.js"; import { Logger } from "./shared/logger.js"; +import { initSidebar } from "./sidebar/sidebar.js"; app.registerExtension({ name: "comfyui.magnify.glass", init() { const magnifyGlass = new MagnifyGlass(); magnifyGlass.init(); window.comfyUIMagnifyGlass = magnifyGlass; + initSidebar(); Logger.info("Magnify Glass extension initialized"); } }); diff --git a/web/magnify_glass.js.map b/web/magnify_glass.js.map index 2bd6d70..5762b35 100644 --- a/web/magnify_glass.js.map +++ b/web/magnify_glass.js.map @@ -1 +1 @@ -{"version":3,"file":"magnify_glass.js","sources":["../src/magnify_glass.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Glass - Main Entry Point\n * \n * Initializes the Magnify Glass extension.\n */\n\nimport { MagnifyGlass } from './magnify-glass';\nimport { Logger } from './shared/logger';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// Global declaration handled in comfyui.d.ts\n\n// We rely on the global 'app' object provided by ComfyUI\n// Register extension\napp.registerExtension({\n name: \"comfyui.magnify.glass\",\n init() {\n // Initialize the magnify glass\n const magnifyGlass = new MagnifyGlass();\n magnifyGlass.init();\n\n // Expose to window for other extensions (like info panel) to access\n window.comfyUIMagnifyGlass = magnifyGlass;\n\n Logger.info('Magnify Glass extension initialized');\n }\n});\n"],"names":[],"mappings":";;;AAeA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,eAAe,IAAI,aAAA;AACzB,iBAAa,KAAA;AAGb,WAAO,sBAAsB;AAE7B,WAAO,KAAK,qCAAqC;AAAA,EACrD;AACJ,CAAC;"} \ No newline at end of file +{"version":3,"file":"magnify_glass.js","sources":["../src/magnify_glass.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Glass - Main Entry Point\n * \n * Initializes the Magnify Glass extension.\n */\n\nimport { MagnifyGlass } from './magnify-glass';\nimport { Logger } from './shared/logger';\nimport { initSidebar } from './sidebar';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// Global declaration handled in comfyui.d.ts\n\n// We rely on the global 'app' object provided by ComfyUI\n// Register extension\napp.registerExtension({\n name: \"comfyui.magnify.glass\",\n init() {\n // Initialize the magnify glass\n const magnifyGlass = new MagnifyGlass();\n magnifyGlass.init();\n\n // Expose to window for other extensions (like info panel) to access\n window.comfyUIMagnifyGlass = magnifyGlass;\n\n // Initialize sidebar\n initSidebar();\n\n Logger.info('Magnify Glass extension initialized');\n }\n});\n"],"names":[],"mappings":";;;;AAgBA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,eAAe,IAAI,aAAA;AACzB,iBAAa,KAAA;AAGb,WAAO,sBAAsB;AAG7B,gBAAA;AAEA,WAAO,KAAK,qCAAqC;AAAA,EACrD;AACJ,CAAC;"} \ No newline at end of file diff --git a/web/shared/constants.js b/web/shared/constants.js index 5e1ee6c..82428a6 100644 --- a/web/shared/constants.js +++ b/web/shared/constants.js @@ -23,13 +23,14 @@ const ACTIVATION_KEYS = ["x", "z", "m", "q", "v", "c"]; const RESET_KEYS = ["r", "o", "p", "k", "l"]; const TOGGLE_FOLLOW_KEYS = ["f", "g", "h", "j", "k"]; const DEFAULT_PADDING = 20; +const DEFAULT_GLASS_Y_OFFSET = 120; const DEFAULT_GLASS_SIZE = 300; const DEFAULT_ZOOM_FACTOR = 300; const DEFAULT_BORDER_WIDTH = 1; const DEFAULT_BORDER_COLOR = "#6b7280"; const DEFAULT_OFFSET_STEP = 5; -const DEFAULT_PANEL_WIDTH = 320; -const DEFAULT_PANEL_MAX_HEIGHT = 1e3; +const DEFAULT_PANEL_WIDTH = 300; +const DEFAULT_PANEL_MAX_HEIGHT = 300; const DEFAULT_PANEL_OPACITY = 100; const Z_INDEX = { GLASS: 98999, @@ -44,6 +45,7 @@ export { DEFAULT_BORDER_COLOR, DEFAULT_BORDER_WIDTH, DEFAULT_GLASS_SIZE, + DEFAULT_GLASS_Y_OFFSET, DEFAULT_OFFSET_STEP, DEFAULT_PADDING, DEFAULT_PANEL_MAX_HEIGHT, diff --git a/web/shared/constants.js.map b/web/shared/constants.js.map index 24b864c..f1e7cf4 100644 --- a/web/shared/constants.js.map +++ b/web/shared/constants.js.map @@ -1 +1 @@ -{"version":3,"file":"constants.js","sources":["../../src/shared/constants.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Shared Constants\n * \n * Configuration constants shared between magnify glass and info panel modules.\n */\n\n// Glass positioning options\nexport const GLASS_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\",\n \"Top-Left\",\n \"Top-Right\",\n \"Bottom-Left\",\n \"Bottom-Right\"\n] as const;\n\nexport type GlassPosition = typeof GLASS_POSITIONS[number];\n\n// Glass shape options\nexport const GLASS_SHAPES = [\n \"Circle\",\n \"Square\",\n \"Rounded Square\"\n] as const;\n\nexport type GlassShape = typeof GLASS_SHAPES[number];\n\n// Texture filtering modes\nexport const TEXTURE_FILTERS = [\n \"Linear\",\n \"Nearest\"\n] as const;\n\nexport type TextureFilter = typeof TEXTURE_FILTERS[number];\n\n// Info panel positions relative to glass\nexport const PANEL_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\"\n] as const;\n\nexport type PanelPosition = typeof PANEL_POSITIONS[number];\n\n// Available keys for settings\nexport const ACTIVATION_KEYS = [\"x\", \"z\", \"m\", \"q\", \"v\", \"c\"] as const;\nexport const RESET_KEYS = [\"r\", \"o\", \"p\", \"k\", \"l\"] as const;\nexport const TOGGLE_FOLLOW_KEYS = [\"f\", \"g\", \"h\", \"j\", \"k\"] as const;\n\n// UI Constants\nexport const DEFAULT_PADDING = 20;\nexport const DEFAULT_GLASS_SIZE = 300;\nexport const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage\nexport const DEFAULT_BORDER_WIDTH = 1;\nexport const DEFAULT_BORDER_COLOR = \"#6b7280\";\nexport const DEFAULT_OFFSET_STEP = 5;\n\n// Panel Constants\nexport const DEFAULT_PANEL_WIDTH = 320;\nexport const DEFAULT_PANEL_MAX_HEIGHT = 1000;\nexport const DEFAULT_PANEL_OPACITY = 100;\n\n// Z-Index hierarchy\nexport const Z_INDEX = {\n GLASS: 98999,\n DEBUG: 99000,\n PANEL: 99999,\n CONTROLS: 99998\n} as const;\n\n// Animation durations (ms)\nexport const ANIMATION = {\n PANEL_TRANSITION: 300,\n AUTO_COLLAPSE_DELAY: 1500,\n THEME_DETECTION_INTERVAL: 2000\n} as const;\n\n// LocalStorage keys\nexport const STORAGE_KEYS = {\n OFFSET_X: 'comfyui_magnify_offset_x',\n OFFSET_Y: 'comfyui_magnify_offset_y'\n} as const;\n"],"names":[],"mappings":"AAOO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,eAAe;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACJ;AAaO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,kBAAkB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AACrD,MAAM,aAAa,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAC3C,MAAM,qBAAqB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAGnD,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;AACjC,MAAM,wBAAwB;AAG9B,MAAM,UAAU;AAAA,EACnB,OAAO;AAAA,EACP,OAAO;AAGX;AAUO,MAAM,eAAe;AAAA,EACxB,UAAU;AAAA,EACV,UAAU;AACd;"} \ No newline at end of file +{"version":3,"file":"constants.js","sources":["../../src/shared/constants.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Shared Constants\n * \n * Configuration constants shared between magnify glass and info panel modules.\n */\n\n// Glass positioning options\nexport const GLASS_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\",\n \"Top-Left\",\n \"Top-Right\",\n \"Bottom-Left\",\n \"Bottom-Right\"\n] as const;\n\nexport type GlassPosition = typeof GLASS_POSITIONS[number];\n\n// Glass shape options\nexport const GLASS_SHAPES = [\n \"Circle\",\n \"Square\",\n \"Rounded Square\"\n] as const;\n\nexport type GlassShape = typeof GLASS_SHAPES[number];\n\n// Texture filtering modes\nexport const TEXTURE_FILTERS = [\n \"Linear\",\n \"Nearest\"\n] as const;\n\nexport type TextureFilter = typeof TEXTURE_FILTERS[number];\n\n// Info panel positions relative to glass\nexport const PANEL_POSITIONS = [\n \"Top\",\n \"Bottom\",\n \"Left\",\n \"Right\"\n] as const;\n\nexport type PanelPosition = typeof PANEL_POSITIONS[number];\n\n// Available keys for settings\nexport const ACTIVATION_KEYS = [\"x\", \"z\", \"m\", \"q\", \"v\", \"c\"] as const;\nexport const RESET_KEYS = [\"r\", \"o\", \"p\", \"k\", \"l\"] as const;\nexport const TOGGLE_FOLLOW_KEYS = [\"f\", \"g\", \"h\", \"j\", \"k\"] as const;\n\n// UI Constants\nexport const DEFAULT_PADDING = 20;\nexport const DEFAULT_GLASS_Y_OFFSET = 120; // Starting Y position offset (below top bar)\nexport const DEFAULT_GLASS_SIZE = 300;\nexport const DEFAULT_ZOOM_FACTOR = 300; // 3x as percentage\nexport const DEFAULT_BORDER_WIDTH = 1;\nexport const DEFAULT_BORDER_COLOR = \"#6b7280\";\nexport const DEFAULT_OFFSET_STEP = 5;\n\n// Panel Constants\nexport const DEFAULT_PANEL_WIDTH = 300;\nexport const DEFAULT_PANEL_MAX_HEIGHT = 300;\nexport const DEFAULT_PANEL_OPACITY = 100;\n\n// Z-Index hierarchy\nexport const Z_INDEX = {\n GLASS: 98999,\n DEBUG: 99000,\n PANEL: 99999,\n CONTROLS: 99998\n} as const;\n\n// Animation durations (ms)\nexport const ANIMATION = {\n PANEL_TRANSITION: 300,\n AUTO_COLLAPSE_DELAY: 1500,\n THEME_DETECTION_INTERVAL: 2000\n} as const;\n\n// LocalStorage keys\nexport const STORAGE_KEYS = {\n OFFSET_X: 'comfyui_magnify_offset_x',\n OFFSET_Y: 'comfyui_magnify_offset_y'\n} as const;\n"],"names":[],"mappings":"AAOO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,eAAe;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACJ;AAaO,MAAM,kBAAkB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ;AAKO,MAAM,kBAAkB,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AACrD,MAAM,aAAa,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAC3C,MAAM,qBAAqB,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG;AAGnD,MAAM,kBAAkB;AACxB,MAAM,yBAAyB;AAC/B,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAG5B,MAAM,sBAAsB;AAC5B,MAAM,2BAA2B;AACjC,MAAM,wBAAwB;AAG9B,MAAM,UAAU;AAAA,EACnB,OAAO;AAAA,EACP,OAAO;AAGX;AAUO,MAAM,eAAe;AAAA,EACxB,UAAU;AAAA,EACV,UAAU;AACd;"} \ No newline at end of file diff --git a/web/shared/settings/glassSettings.js b/web/shared/settings/glassSettings.js index e4213e4..d9a8c06 100644 --- a/web/shared/settings/glassSettings.js +++ b/web/shared/settings/glassSettings.js @@ -250,6 +250,77 @@ function registerGlassSettings(magnifyGlass) { } } }); + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.Action.ResetPosition", + name: "🔄 Magnify Glass: Reset Position", + type: "boolean", + defaultValue: false, + tooltip: "Toggle to reset the glass position and disable follow cursor.", + onChange: (value) => { + if (value) { + if (magnifyGlass == null ? void 0 : magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + } + setTimeout(() => { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.Action.ResetPosition", false); + } catch (e) { + } + }, 500); + } + } + }); + app.ui.settings.addSetting({ + id: "🔍MagnifyGlass.Action.ResetAll", + name: "⚠️ Magnify Glass: Reset ALL Settings", + type: "boolean", + defaultValue: false, + tooltip: "Toggle to reset ALL settings to defaults.", + onChange: (value) => { + if (value) { + if (confirm("Reset ALL Magnify Glass settings?")) { + app.ui.settings.setSettingValue("🔍MagnifyGlass.ZoomFactor", 300); + app.ui.settings.setSettingValue("🔍MagnifyGlass.GlassSize", 250); + app.ui.settings.setSettingValue("🔍MagnifyGlass.GlassShape", "Rounded Square"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.GlassPosition", "Top-Right"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.TextureFiltering", "Linear"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.BorderWidth", 2); + app.ui.settings.setSettingValue("🔍MagnifyGlass.BorderColor", "#ffffff"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.BorderEnabled", true); + app.ui.settings.setSettingValue("🔍MagnifyGlass.FollowCursor", false); + app.ui.settings.setSettingValue("🔍MagnifyGlass.AlwaysActiveMode", true); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ActivationKey", "x"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ResetKey", "o"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", "h"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.AltRequired", false); + app.ui.settings.setSettingValue("🔍MagnifyGlass.OffsetStep", 10); + app.ui.settings.setSettingValue("🔍MagnifyGlass.DebugMode", false); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelEnabled", true); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelPosition", "Bottom"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelWidth", 300); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelMaxHeight", 300); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelOpacity", 95); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelTextColor", "#6b7280"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", "#3b82f6"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelAnimations", false); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ShowHoveringControls", true); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ControlsPosition", "bottom"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.ToggleHotkey", "i"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", "g"); + app.ui.settings.setSettingValue("🔍MagnifyGlass.PinPanelHotkey", "u"); + if (magnifyGlass == null ? void 0 : magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + } + } + setTimeout(() => { + try { + app.ui.settings.setSettingValue("🔍MagnifyGlass.Action.ResetAll", false); + } catch (e) { + } + }, 500); + } + } + }); } export { registerGlassSettings diff --git a/web/shared/settings/glassSettings.js.map b/web/shared/settings/glassSettings.js.map index 43752fa..facd2c4 100644 --- a/web/shared/settings/glassSettings.js.map +++ b/web/shared/settings/glassSettings.js.map @@ -1 +1 @@ -{"version":3,"file":"glassSettings.js","sources":["../../../src/shared/settings/glassSettings.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Glass Settings Registration\n * \n * Registers all magnify glass settings with ComfyUI.\n */\n\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\nimport {\n GLASS_POSITIONS,\n GLASS_SHAPES,\n ACTIVATION_KEYS,\n RESET_KEYS,\n TOGGLE_FOLLOW_KEYS\n} from '../constants';\nimport { DEFAULT_GLASS_SETTINGS } from './defaults';\n\n/**\n * Register all magnify glass settings with ComfyUI.\n * @param magnifyGlass - The MagnifyGlass instance\n */\nexport function registerGlassSettings(magnifyGlass: any): void {\n const settings = DEFAULT_GLASS_SETTINGS;\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ZoomFactor\",\n name: \"🔍 Magnify Glass: Zoom Factor (%)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.ZoomFactor\"],\n min: 100,\n max: 1000,\n step: 25,\n tooltip: \"Magnification level as a percentage (e.g., 300 = 3x zoom, 150 = 1.5x zoom).\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.zoomFactor = typeof value === 'number' ? value / 100 : parseFloat(value) / 100;\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassSize\",\n name: \"🔍 Magnify Glass: Size (px)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.GlassSize\"],\n min: 50,\n max: 100,\n step: 10,\n tooltip: \"Diameter of the magnifying glass circle in pixels.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassSize = parseInt(String(value), 10);\n magnifyGlass.applyUiChanges();\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderWidth\",\n name: \"🔍 Magnify Glass: Border Width (px)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.BorderWidth\"],\n min: 0,\n max: 10,\n step: 0.1,\n tooltip: \"Width of the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.borderWidth = parseFloat(String(value));\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderColor\",\n name: \"🎨 Magnify Glass: Border Color\",\n type: \"color\",\n defaultValue: settings[\"🔍MagnifyGlass.BorderColor\"],\n tooltip: \"Color of the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n const strValue = String(value);\n const normalizedColor = strValue && !strValue.startsWith('#') ? `#${strValue}` : strValue;\n magnifyGlass.config.borderColor = normalizedColor;\n if (strValue !== normalizedColor) {\n try {\n app.ui.settings.setSettingValue(\"🔍MagnifyGlass.BorderColor\", normalizedColor);\n } catch (e) {\n console.warn('Failed to save normalized border color:', e);\n }\n }\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ActivationKey\",\n name: \"⌨️ Magnify Glass: Activation Key\",\n type: \"combo\",\n options: ACTIVATION_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ActivationKey\"],\n tooltip: \"The key to activate the magnifier.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.activationKey = String(value).toLowerCase();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.AltRequired\",\n name: \"⌨️ Magnify Glass: Require Alt/Option Key\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.AltRequired\"],\n tooltip: \"If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.altRequired = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.FollowCursor\",\n name: \"🖱️ Magnify Glass: Follow Cursor Position\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.FollowCursor\"],\n tooltip: \"If Yes, the magnifier window moves with the cursor.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.followCursor = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.OffsetStep\",\n name: \"⌨️ Magnify Glass: Offset Adjust Step\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.OffsetStep\"],\n min: 1,\n max: 50,\n step: 1,\n tooltip: \"How many graph units the view shifts when pressing arrow keys.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.offsetStep = parseInt(String(value), 10);\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ResetKey\",\n name: \"⌨️ Magnify Glass: Reset Offset Key\",\n type: \"combo\",\n options: RESET_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ResetKey\"],\n tooltip: \"The key to reset the magnify glass offset to default.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.resetKey = String(value).toLowerCase();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.DebugMode\",\n name: \"🐞 Magnify Glass: Debug Mode\",\n type: \"combo\",\n options: [{ value: true, text: \"Enabled\" }, { value: false, text: \"Disabled\" }],\n defaultValue: settings[\"🔍MagnifyGlass.DebugMode\"],\n tooltip: \"Show detailed logging and the debug visualization overlay.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.debugMode = !!value;\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassPosition\",\n name: \"🖱️ Magnify Glass: Glass Position\",\n type: \"combo\",\n options: GLASS_POSITIONS.map(p => ({ value: p, text: p === \"Bottom\" ? \"Bottom (Default)\" : p })),\n defaultValue: settings[\"🔍MagnifyGlass.GlassPosition\"],\n tooltip: \"Position of the magnifying glass relative to the cursor.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassPosition = String(value);\n if (magnifyGlass.state.active && !magnifyGlass.config.followCursor) {\n const { x, y } = magnifyGlass.lastKnownMousePosition;\n magnifyGlass.ui.positionGlass(x, y);\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassShape\",\n name: \"🖼️ Magnify Glass: Shape\",\n type: \"combo\",\n options: GLASS_SHAPES.map(s => ({ value: s, text: s })),\n defaultValue: settings[\"🔍MagnifyGlass.GlassShape\"],\n tooltip: \"Shape of the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassShape = String(value);\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderEnabled\",\n name: \"🖼️ Magnify Glass: Show Border\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.BorderEnabled\"],\n tooltip: \"Enable or disable the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.borderEnabled = !!value;\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.TextureFiltering\",\n name: \"🖼️ Magnify Glass: Texture Filtering\",\n type: \"combo\",\n options: [\n { value: \"Linear\", text: \"Linear (Smooth)\" },\n { value: \"Nearest\", text: \"Nearest (Pixelated)\" }\n ],\n defaultValue: settings[\"🔍MagnifyGlass.TextureFiltering\"],\n tooltip: \"Controls how the magnified image is scaled.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config && magnifyGlass.renderer) {\n magnifyGlass.config.textureFiltering = String(value);\n magnifyGlass.renderer.updateTextureFiltering(value);\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.AlwaysActiveMode\",\n name: \"🔒 Magnify Glass: Always Active Mode\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.AlwaysActiveMode\"],\n tooltip: \"If Yes, activating the magnifier keeps it on until activated again.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.alwaysActiveMode = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ToggleFollowCursorKey\",\n name: \"🔑 Magnify Glass: Toggle Follow Key\",\n type: \"combo\",\n options: TOGGLE_FOLLOW_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ToggleFollowCursorKey\"],\n tooltip: \"The key to toggle the 'Follow Cursor' behavior.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.toggleFollowCursorKey = String(value).toLowerCase();\n }\n }\n });\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"glassSettings.js","sources":["../../../src/shared/settings/glassSettings.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Glass Settings Registration\n * \n * Registers all magnify glass settings with ComfyUI.\n */\n\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\nimport {\n GLASS_POSITIONS,\n GLASS_SHAPES,\n ACTIVATION_KEYS,\n RESET_KEYS,\n TOGGLE_FOLLOW_KEYS\n} from '../constants';\nimport { DEFAULT_GLASS_SETTINGS } from './defaults';\n\n/**\n * Register all magnify glass settings with ComfyUI.\n * @param magnifyGlass - The MagnifyGlass instance\n */\nexport function registerGlassSettings(magnifyGlass: any): void {\n const settings = DEFAULT_GLASS_SETTINGS;\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ZoomFactor\",\n name: \"🔍 Magnify Glass: Zoom Factor (%)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.ZoomFactor\"],\n min: 100,\n max: 1000,\n step: 25,\n tooltip: \"Magnification level as a percentage (e.g., 300 = 3x zoom, 150 = 1.5x zoom).\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.zoomFactor = typeof value === 'number' ? value / 100 : parseFloat(value) / 100;\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassSize\",\n name: \"🔍 Magnify Glass: Size (px)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.GlassSize\"],\n min: 50,\n max: 100,\n step: 10,\n tooltip: \"Diameter of the magnifying glass circle in pixels.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassSize = parseInt(String(value), 10);\n magnifyGlass.applyUiChanges();\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderWidth\",\n name: \"🔍 Magnify Glass: Border Width (px)\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.BorderWidth\"],\n min: 0,\n max: 10,\n step: 0.1,\n tooltip: \"Width of the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.borderWidth = parseFloat(String(value));\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderColor\",\n name: \"🎨 Magnify Glass: Border Color\",\n type: \"color\",\n defaultValue: settings[\"🔍MagnifyGlass.BorderColor\"],\n tooltip: \"Color of the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n const strValue = String(value);\n const normalizedColor = strValue && !strValue.startsWith('#') ? `#${strValue}` : strValue;\n magnifyGlass.config.borderColor = normalizedColor;\n if (strValue !== normalizedColor) {\n try {\n app.ui.settings.setSettingValue(\"🔍MagnifyGlass.BorderColor\", normalizedColor);\n } catch (e) {\n console.warn('Failed to save normalized border color:', e);\n }\n }\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ActivationKey\",\n name: \"⌨️ Magnify Glass: Activation Key\",\n type: \"combo\",\n options: ACTIVATION_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ActivationKey\"],\n tooltip: \"The key to activate the magnifier.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.activationKey = String(value).toLowerCase();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.AltRequired\",\n name: \"⌨️ Magnify Glass: Require Alt/Option Key\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.AltRequired\"],\n tooltip: \"If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.altRequired = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.FollowCursor\",\n name: \"🖱️ Magnify Glass: Follow Cursor Position\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.FollowCursor\"],\n tooltip: \"If Yes, the magnifier window moves with the cursor.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.followCursor = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.OffsetStep\",\n name: \"⌨️ Magnify Glass: Offset Adjust Step\",\n type: \"slider\",\n defaultValue: settings[\"🔍MagnifyGlass.OffsetStep\"],\n min: 1,\n max: 50,\n step: 1,\n tooltip: \"How many graph units the view shifts when pressing arrow keys.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.offsetStep = parseInt(String(value), 10);\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ResetKey\",\n name: \"⌨️ Magnify Glass: Reset Offset Key\",\n type: \"combo\",\n options: RESET_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ResetKey\"],\n tooltip: \"The key to reset the magnify glass offset to default.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.resetKey = String(value).toLowerCase();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.DebugMode\",\n name: \"🐞 Magnify Glass: Debug Mode\",\n type: \"combo\",\n options: [{ value: true, text: \"Enabled\" }, { value: false, text: \"Disabled\" }],\n defaultValue: settings[\"🔍MagnifyGlass.DebugMode\"],\n tooltip: \"Show detailed logging and the debug visualization overlay.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.debugMode = !!value;\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassPosition\",\n name: \"🖱️ Magnify Glass: Glass Position\",\n type: \"combo\",\n options: GLASS_POSITIONS.map(p => ({ value: p, text: p === \"Bottom\" ? \"Bottom (Default)\" : p })),\n defaultValue: settings[\"🔍MagnifyGlass.GlassPosition\"],\n tooltip: \"Position of the magnifying glass relative to the cursor.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassPosition = String(value);\n if (magnifyGlass.state.active && !magnifyGlass.config.followCursor) {\n const { x, y } = magnifyGlass.lastKnownMousePosition;\n magnifyGlass.ui.positionGlass(x, y);\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.GlassShape\",\n name: \"🖼️ Magnify Glass: Shape\",\n type: \"combo\",\n options: GLASS_SHAPES.map(s => ({ value: s, text: s })),\n defaultValue: settings[\"🔍MagnifyGlass.GlassShape\"],\n tooltip: \"Shape of the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.glassShape = String(value);\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.BorderEnabled\",\n name: \"🖼️ Magnify Glass: Show Border\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.BorderEnabled\"],\n tooltip: \"Enable or disable the border around the magnifying glass.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.borderEnabled = !!value;\n magnifyGlass.applyUiChanges();\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.TextureFiltering\",\n name: \"🖼️ Magnify Glass: Texture Filtering\",\n type: \"combo\",\n options: [\n { value: \"Linear\", text: \"Linear (Smooth)\" },\n { value: \"Nearest\", text: \"Nearest (Pixelated)\" }\n ],\n defaultValue: settings[\"🔍MagnifyGlass.TextureFiltering\"],\n tooltip: \"Controls how the magnified image is scaled.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config && magnifyGlass.renderer) {\n magnifyGlass.config.textureFiltering = String(value);\n magnifyGlass.renderer.updateTextureFiltering(value);\n if (magnifyGlass.state.active) {\n magnifyGlass.updateMagnifiedView();\n }\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.AlwaysActiveMode\",\n name: \"🔒 Magnify Glass: Always Active Mode\",\n type: \"combo\",\n options: [{ value: true, text: \"Yes\" }, { value: false, text: \"No\" }],\n defaultValue: settings[\"🔍MagnifyGlass.AlwaysActiveMode\"],\n tooltip: \"If Yes, activating the magnifier keeps it on until activated again.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.alwaysActiveMode = !!value;\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.ToggleFollowCursorKey\",\n name: \"🔑 Magnify Glass: Toggle Follow Key\",\n type: \"combo\",\n options: TOGGLE_FOLLOW_KEYS.map(k => ({ value: k, text: k })),\n defaultValue: settings[\"🔍MagnifyGlass.ToggleFollowCursorKey\"],\n tooltip: \"The key to toggle the 'Follow Cursor' behavior.\",\n onChange: (value: any) => {\n if (magnifyGlass?.config) {\n magnifyGlass.config.toggleFollowCursorKey = String(value).toLowerCase();\n }\n }\n });\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.Action.ResetPosition\",\n name: \"🔄 Magnify Glass: Reset Position\",\n type: \"boolean\",\n defaultValue: false,\n tooltip: \"Toggle to reset the glass position and disable follow cursor.\",\n onChange: (value: any) => {\n if (value) {\n if (magnifyGlass?.resetOffsets) {\n magnifyGlass.resetOffsets();\n }\n // Reset the toggle back to false\n setTimeout(() => {\n try {\n app.ui.settings.setSettingValue(\"🔍MagnifyGlass.Action.ResetPosition\", false);\n } catch (e) { /* ignore */ }\n }, 500);\n }\n }\n });\n\n app.ui.settings.addSetting({\n id: \"🔍MagnifyGlass.Action.ResetAll\",\n name: \"⚠️ Magnify Glass: Reset ALL Settings\",\n type: \"boolean\",\n defaultValue: false,\n tooltip: \"Toggle to reset ALL settings to defaults.\",\n onChange: (value: any) => {\n if (value) {\n if (confirm(\"Reset ALL Magnify Glass settings?\")) {\n // Glass Defaults\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ZoomFactor', 300);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassSize', 250);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderWidth', 2);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.BorderEnabled', true);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.FollowCursor', false);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ActivationKey', 'x');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ResetKey', 'o');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.AltRequired', false);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.OffsetStep', 10);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.DebugMode', false);\n\n // Panel Defaults\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 300);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ShowHoveringControls', true);\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g');\n app.ui.settings.setSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u');\n\n // Also force position reset\n if (magnifyGlass?.resetOffsets) {\n magnifyGlass.resetOffsets();\n }\n }\n\n // Reset the toggle back to false\n setTimeout(() => {\n try {\n app.ui.settings.setSettingValue(\"🔍MagnifyGlass.Action.ResetAll\", false);\n } catch (e) { /* ignore */ }\n }, 500);\n }\n }\n });\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/sidebar.css b/web/sidebar.css new file mode 100644 index 0000000..6f458a3 --- /dev/null +++ b/web/sidebar.css @@ -0,0 +1,352 @@ +/** + * ComfyUI MagnifyGlass - Sidebar CSS + * + * Styles for the sidebar settings and inspector panels. + */ + +.magnify-sidebar { + display: flex; + flex-direction: column; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-size: 13px; + color: var(--fg-color, #ddd); + background: transparent; + padding: 0; +} + +.magnify-sidebar-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + border-bottom: 1px solid var(--border-color, #333); + background: rgba(0, 0, 0, 0.2); +} + +.magnify-sidebar-header svg { + width: 20px; + height: 20px; + fill: currentColor; +} + +.magnify-sidebar-header h2 { + margin: 0; + font-size: 14px; + font-weight: 600; +} + +.magnify-sidebar-content { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +/* Section styling */ +.magnify-sidebar-section { + margin-bottom: 4px; +} + +.magnify-sidebar-section-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + cursor: pointer; + background: rgba(255, 255, 255, 0.03); + border-bottom: 1px solid var(--border-color, #333); + transition: background 0.15s; +} + +.magnify-sidebar-section-header:hover { + background: rgba(255, 255, 255, 0.06); +} + +.magnify-sidebar-section-header svg { + width: 12px; + height: 12px; + transition: transform 0.2s; +} + +.magnify-sidebar-section-header.collapsed svg { + transform: rotate(-90deg); +} + +.magnify-sidebar-section-header span { + font-weight: 500; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.magnify-sidebar-section-body { + padding: 12px 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.magnify-sidebar-section-body.collapsed { + display: none; +} + +/* Control row */ +.magnify-control-row { + display: flex; + flex-direction: column; + gap: 4px; +} + +.magnify-control-row label { + font-size: 11px; + color: var(--descrip-text, #999); + font-weight: 500; +} + +.magnify-control-row .control-value { + font-size: 11px; + color: var(--fg-color, #ddd); + margin-left: auto; +} + +.magnify-control-label-row { + display: flex; + justify-content: space-between; + align-items: center; +} + +/* Slider styling */ +.magnify-slider { + width: 100%; + height: 6px; + border-radius: 3px; + background: rgba(255, 255, 255, 0.1); + appearance: none; + cursor: pointer; +} + +.magnify-slider::-webkit-slider-thumb { + appearance: none; + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--p-primary-color, #3b82f6); + cursor: pointer; + transition: transform 0.1s; +} + +.magnify-slider::-webkit-slider-thumb:hover { + transform: scale(1.15); +} + +.magnify-slider::-moz-range-thumb { + width: 14px; + height: 14px; + border-radius: 50%; + background: var(--p-primary-color, #3b82f6); + border: none; + cursor: pointer; +} + +/* Toggle styling */ +.magnify-toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; +} + +.magnify-toggle-row label { + font-size: 12px; + color: var(--fg-color, #ddd); +} + +.magnify-toggle { + position: relative; + width: 36px; + height: 20px; + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; + cursor: pointer; + transition: background 0.2s; +} + +.magnify-toggle.active { + background: var(--p-primary-color, #3b82f6); +} + +.magnify-toggle::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background: white; + border-radius: 50%; + transition: transform 0.2s; +} + +.magnify-toggle.active::after { + transform: translateX(16px); +} + +/* Select styling */ +.magnify-select { + width: 100%; + padding: 6px 10px; + border: 1px solid var(--border-color, #444); + border-radius: 4px; + background: rgba(0, 0, 0, 0.3); + color: var(--fg-color, #ddd); + font-size: 12px; + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M6 8L2 4h8z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; +} + +.magnify-select:focus { + outline: none; + border-color: var(--p-primary-color, #3b82f6); +} + +/* Inspector styles */ +.magnify-inspector-section { + padding: 12px 16px; +} + +.magnify-inspector-group { + margin-bottom: 16px; +} + +.magnify-inspector-group:last-child { + margin-bottom: 0; +} + +.magnify-inspector-group-title { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--descrip-text, #888); + margin-bottom: 8px; + font-weight: 600; +} + +.magnify-inspector-row { + display: flex; + justify-content: space-between; + padding: 4px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.magnify-inspector-row:last-child { + border-bottom: none; +} + +.magnify-inspector-label { + color: var(--descrip-text, #999); + font-size: 12px; +} + +.magnify-inspector-value { + color: var(--fg-color, #ddd); + font-size: 12px; + font-family: monospace; + text-align: right; + max-width: 60%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.magnify-inspector-empty { + color: var(--descrip-text, #666); + font-style: italic; + font-size: 12px; + text-align: center; + padding: 20px 0; +} + +/* Node badge */ +.magnify-node-badge { + display: inline-block; + padding: 2px 8px; + background: var(--p-primary-color, #3b82f6); + border-radius: 4px; + font-size: 11px; + font-weight: 500; + margin-bottom: 8px; +} + +/* Button styling */ +.magnify-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 12px; + border: none; + border-radius: 4px; + background: var(--p-primary-color, #3b82f6); + color: white; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, transform 0.1s; +} + +.magnify-btn:hover { + background: var(--p-primary-hover-color, #2563eb); +} + +.magnify-btn:active { + transform: scale(0.98); +} + +.magnify-btn-secondary { + background: rgba(255, 255, 255, 0.1); +} + +.magnify-btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); +} + +/* Color picker styling */ +.magnify-color-row { + flex-direction: row; + align-items: center; + justify-content: space-between; +} + +.magnify-color-wrapper { + display: flex; + align-items: center; + gap: 8px; +} + +.magnify-color-input { + width: 28px; + height: 28px; + padding: 0; + border: 1px solid var(--border-color, #444); + border-radius: 4px; + cursor: pointer; + background: transparent; +} + +.magnify-color-input::-webkit-color-swatch-wrapper { + padding: 2px; +} + +.magnify-color-input::-webkit-color-swatch { + border-radius: 2px; + border: none; +} + +.magnify-color-preview { + font-size: 11px; + font-family: monospace; + color: var(--descrip-text, #999); +} \ No newline at end of file diff --git a/web/sidebar/SidebarInspector.js b/web/sidebar/SidebarInspector.js new file mode 100644 index 0000000..540a5ec --- /dev/null +++ b/web/sidebar/SidebarInspector.js @@ -0,0 +1,164 @@ +import { app } from "/scripts/app.js"; +const Icons = { + chevronDown: `` +}; +let inspectorBody = null; +let updateInterval = null; +function formatNumber(value, decimals = 0) { + return value.toFixed(decimals); +} +function getHoveredNode() { + try { + const graph = app.graph; + if (!graph || !graph.canvas) return null; + const canvas = graph.canvas; + const mousePos = canvas.graph_mouse; + if (!mousePos) return null; + const nodes = graph._nodes; + for (let i = nodes.length - 1; i >= 0; i--) { + const node = nodes[i]; + if (node.pos && node.size) { + const [x, y] = node.pos; + const [w, h] = node.size; + if (mousePos[0] >= x && mousePos[0] <= x + w && mousePos[1] >= y && mousePos[1] <= y + h) { + return node; + } + } + } + return null; + } catch { + return null; + } +} +function getCanvasInfo() { + var _a; + try { + const graph = app.graph; + if (!graph || !graph.canvas) { + return { scale: 1, cursor: { x: 0, y: 0 } }; + } + const canvas = graph.canvas; + const scale = ((_a = canvas.ds) == null ? void 0 : _a.scale) || 1; + const mousePos = canvas.graph_mouse || [0, 0]; + return { + scale: scale * 100, + cursor: { x: mousePos[0] || 0, y: mousePos[1] || 0 } + }; + } catch { + return { scale: 1, cursor: { x: 0, y: 0 } }; + } +} +function createRow(label, value) { + const row = document.createElement("div"); + row.className = "magnify-inspector-row"; + const labelEl = document.createElement("span"); + labelEl.className = "magnify-inspector-label"; + labelEl.textContent = label; + const valueEl = document.createElement("span"); + valueEl.className = "magnify-inspector-value"; + valueEl.textContent = value; + valueEl.title = value; + row.appendChild(labelEl); + row.appendChild(valueEl); + return row; +} +function createGroup(title) { + const group = document.createElement("div"); + group.className = "magnify-inspector-group"; + const titleEl = document.createElement("div"); + titleEl.className = "magnify-inspector-group-title"; + titleEl.textContent = title; + group.appendChild(titleEl); + return group; +} +function updateInspector() { + var _a, _b; + if (!inspectorBody) return; + const canvasInfo = getCanvasInfo(); + const hoveredNode = getHoveredNode(); + inspectorBody.innerHTML = ""; + const canvasGroup = createGroup("Canvas"); + canvasGroup.appendChild(createRow("Cursor", `(${formatNumber(canvasInfo.cursor.x)}, ${formatNumber(canvasInfo.cursor.y)})`)); + canvasGroup.appendChild(createRow("Scale", `${formatNumber(canvasInfo.scale, 1)}%`)); + inspectorBody.appendChild(canvasGroup); + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass) { + const glassGroup = createGroup("Magnify Glass"); + glassGroup.appendChild(createRow("Zoom", `${formatNumber((((_a = magnifyGlass.config) == null ? void 0 : _a.zoomFactor) || 1) * 100)}%`)); + glassGroup.appendChild(createRow("Active", ((_b = magnifyGlass.state) == null ? void 0 : _b.active) ? "Yes" : "No")); + inspectorBody.appendChild(glassGroup); + } + if (hoveredNode) { + const nodeGroup = createGroup("Hovered Node"); + const badge = document.createElement("div"); + badge.className = "magnify-node-badge"; + badge.textContent = hoveredNode.type || "Unknown"; + nodeGroup.appendChild(badge); + nodeGroup.appendChild(createRow("Title", hoveredNode.title || "Untitled")); + nodeGroup.appendChild(createRow("ID", String(hoveredNode.id))); + if (hoveredNode.pos) { + nodeGroup.appendChild(createRow("Position", `(${formatNumber(hoveredNode.pos[0])}, ${formatNumber(hoveredNode.pos[1])})`)); + } + if (hoveredNode.size) { + nodeGroup.appendChild(createRow("Size", `${formatNumber(hoveredNode.size[0])} × ${formatNumber(hoveredNode.size[1])}`)); + } + if (hoveredNode.widgets && hoveredNode.widgets.length > 0) { + const widgetGroup = createGroup("Parameters"); + const widgetsToShow = hoveredNode.widgets.slice(0, 6); + for (const widget of widgetsToShow) { + if (widget.name && widget.value !== void 0) { + let value = String(widget.value); + if (value.length > 30) value = value.substring(0, 27) + "..."; + widgetGroup.appendChild(createRow(widget.name, value)); + } + } + if (hoveredNode.widgets.length > 6) { + const moreEl = document.createElement("div"); + moreEl.style.cssText = "font-size: 11px; color: #666; padding: 4px 0;"; + moreEl.textContent = `+${hoveredNode.widgets.length - 6} more...`; + widgetGroup.appendChild(moreEl); + } + inspectorBody.appendChild(widgetGroup); + } + inspectorBody.appendChild(nodeGroup); + } else { + const empty = document.createElement("div"); + empty.className = "magnify-inspector-empty"; + empty.textContent = "Hover over a node to inspect"; + inspectorBody.appendChild(empty); + } +} +function renderInspectorPanel(container) { + const section = document.createElement("div"); + section.className = "magnify-sidebar-section"; + const header = document.createElement("div"); + header.className = "magnify-sidebar-section-header"; + header.innerHTML = `${Icons.chevronDown}Inspector`; + const body = document.createElement("div"); + body.className = "magnify-sidebar-section-body magnify-inspector-section"; + inspectorBody = body; + header.addEventListener("click", () => { + header.classList.toggle("collapsed"); + body.classList.toggle("collapsed"); + }); + section.appendChild(header); + section.appendChild(body); + container.appendChild(section); + updateInspector(); + if (updateInterval) { + clearInterval(updateInterval); + } + updateInterval = window.setInterval(updateInspector, 100); +} +function cleanupInspector() { + if (updateInterval) { + clearInterval(updateInterval); + updateInterval = null; + } + inspectorBody = null; +} +export { + cleanupInspector, + renderInspectorPanel +}; +//# sourceMappingURL=SidebarInspector.js.map diff --git a/web/sidebar/SidebarInspector.js.map b/web/sidebar/SidebarInspector.js.map new file mode 100644 index 0000000..303e6e1 --- /dev/null +++ b/web/sidebar/SidebarInspector.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SidebarInspector.js","sources":["../../src/sidebar/SidebarInspector.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Sidebar Inspector Panel\n * \n * Real-time inspector view for the sidebar.\n */\n\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n/**\n * Icons for the inspector panel\n */\nconst Icons = {\n chevronDown: ``,\n};\n\n/**\n * Inspector state\n */\ninterface InspectorState {\n cursor: { x: number; y: number };\n canvasScale: number;\n hoveredNode: any | null;\n}\n\nlet inspectorState: InspectorState = {\n cursor: { x: 0, y: 0 },\n canvasScale: 1,\n hoveredNode: null\n};\n\nlet inspectorBody: HTMLElement | null = null;\nlet updateInterval: number | null = null;\n\n/**\n * Format a number for display\n */\nfunction formatNumber(value: number, decimals: number = 0): string {\n return value.toFixed(decimals);\n}\n\n/**\n * Get the currently hovered node from the graph\n */\nfunction getHoveredNode(): any | null {\n try {\n const graph = app.graph;\n if (!graph || !graph.canvas) return null;\n\n const canvas = graph.canvas;\n const mousePos = canvas.graph_mouse;\n\n if (!mousePos) return null;\n\n // Find node at mouse position\n const nodes = graph._nodes;\n for (let i = nodes.length - 1; i >= 0; i--) {\n const node = nodes[i];\n if (node.pos && node.size) {\n const [x, y] = node.pos;\n const [w, h] = node.size;\n if (mousePos[0] >= x && mousePos[0] <= x + w &&\n mousePos[1] >= y && mousePos[1] <= y + h) {\n return node;\n }\n }\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get canvas information\n */\nfunction getCanvasInfo(): { scale: number; cursor: { x: number; y: number } } {\n try {\n const graph = app.graph;\n if (!graph || !graph.canvas) {\n return { scale: 1, cursor: { x: 0, y: 0 } };\n }\n\n const canvas = graph.canvas;\n const scale = canvas.ds?.scale || 1;\n const mousePos = canvas.graph_mouse || [0, 0];\n\n return {\n scale: scale * 100,\n cursor: { x: mousePos[0] || 0, y: mousePos[1] || 0 }\n };\n } catch {\n return { scale: 1, cursor: { x: 0, y: 0 } };\n }\n}\n\n/**\n * Create an inspector row\n */\nfunction createRow(label: string, value: string): HTMLElement {\n const row = document.createElement('div');\n row.className = 'magnify-inspector-row';\n\n const labelEl = document.createElement('span');\n labelEl.className = 'magnify-inspector-label';\n labelEl.textContent = label;\n\n const valueEl = document.createElement('span');\n valueEl.className = 'magnify-inspector-value';\n valueEl.textContent = value;\n valueEl.title = value;\n\n row.appendChild(labelEl);\n row.appendChild(valueEl);\n\n return row;\n}\n\n/**\n * Create a group with title\n */\nfunction createGroup(title: string): HTMLElement {\n const group = document.createElement('div');\n group.className = 'magnify-inspector-group';\n\n const titleEl = document.createElement('div');\n titleEl.className = 'magnify-inspector-group-title';\n titleEl.textContent = title;\n\n group.appendChild(titleEl);\n\n return group;\n}\n\n/**\n * Update the inspector display\n */\nfunction updateInspector(): void {\n if (!inspectorBody) return;\n\n const canvasInfo = getCanvasInfo();\n const hoveredNode = getHoveredNode();\n\n // Clear body\n inspectorBody.innerHTML = '';\n\n // Canvas info group\n const canvasGroup = createGroup('Canvas');\n canvasGroup.appendChild(createRow('Cursor', `(${formatNumber(canvasInfo.cursor.x)}, ${formatNumber(canvasInfo.cursor.y)})`));\n canvasGroup.appendChild(createRow('Scale', `${formatNumber(canvasInfo.scale, 1)}%`));\n inspectorBody.appendChild(canvasGroup);\n\n // MagnifyGlass info\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (magnifyGlass) {\n const glassGroup = createGroup('Magnify Glass');\n glassGroup.appendChild(createRow('Zoom', `${formatNumber((magnifyGlass.config?.zoomFactor || 1) * 100)}%`));\n glassGroup.appendChild(createRow('Active', magnifyGlass.state?.active ? 'Yes' : 'No'));\n inspectorBody.appendChild(glassGroup);\n }\n\n // Node info group\n if (hoveredNode) {\n const nodeGroup = createGroup('Hovered Node');\n\n // Node badge\n const badge = document.createElement('div');\n badge.className = 'magnify-node-badge';\n badge.textContent = hoveredNode.type || 'Unknown';\n nodeGroup.appendChild(badge);\n\n nodeGroup.appendChild(createRow('Title', hoveredNode.title || 'Untitled'));\n nodeGroup.appendChild(createRow('ID', String(hoveredNode.id)));\n\n // Position\n if (hoveredNode.pos) {\n nodeGroup.appendChild(createRow('Position', `(${formatNumber(hoveredNode.pos[0])}, ${formatNumber(hoveredNode.pos[1])})`));\n }\n\n // Size\n if (hoveredNode.size) {\n nodeGroup.appendChild(createRow('Size', `${formatNumber(hoveredNode.size[0])} × ${formatNumber(hoveredNode.size[1])}`));\n }\n\n // Widgets (first few)\n if (hoveredNode.widgets && hoveredNode.widgets.length > 0) {\n const widgetGroup = createGroup('Parameters');\n const widgetsToShow = hoveredNode.widgets.slice(0, 6);\n for (const widget of widgetsToShow) {\n if (widget.name && widget.value !== undefined) {\n let value = String(widget.value);\n if (value.length > 30) value = value.substring(0, 27) + '...';\n widgetGroup.appendChild(createRow(widget.name, value));\n }\n }\n if (hoveredNode.widgets.length > 6) {\n const moreEl = document.createElement('div');\n moreEl.style.cssText = 'font-size: 11px; color: #666; padding: 4px 0;';\n moreEl.textContent = `+${hoveredNode.widgets.length - 6} more...`;\n widgetGroup.appendChild(moreEl);\n }\n inspectorBody.appendChild(widgetGroup);\n }\n\n inspectorBody.appendChild(nodeGroup);\n } else {\n // Empty state\n const empty = document.createElement('div');\n empty.className = 'magnify-inspector-empty';\n empty.textContent = 'Hover over a node to inspect';\n inspectorBody.appendChild(empty);\n }\n}\n\n/**\n * Render the inspector panel into the container\n */\nexport function renderInspectorPanel(container: HTMLElement): void {\n const section = document.createElement('div');\n section.className = 'magnify-sidebar-section';\n\n // Section header\n const header = document.createElement('div');\n header.className = 'magnify-sidebar-section-header';\n header.innerHTML = `${Icons.chevronDown}Inspector`;\n\n // Section body\n const body = document.createElement('div');\n body.className = 'magnify-sidebar-section-body magnify-inspector-section';\n inspectorBody = body;\n\n // Toggle collapse\n header.addEventListener('click', () => {\n header.classList.toggle('collapsed');\n body.classList.toggle('collapsed');\n });\n\n section.appendChild(header);\n section.appendChild(body);\n container.appendChild(section);\n\n // Initial update\n updateInspector();\n\n // Start update interval\n if (updateInterval) {\n clearInterval(updateInterval);\n }\n updateInterval = window.setInterval(updateInspector, 100);\n}\n\n/**\n * Cleanup the inspector (call when sidebar is destroyed)\n */\nexport function cleanupInspector(): void {\n if (updateInterval) {\n clearInterval(updateInterval);\n updateInterval = null;\n }\n inspectorBody = null;\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/sidebar/SidebarSettings.js b/web/sidebar/SidebarSettings.js new file mode 100644 index 0000000..6efed41 --- /dev/null +++ b/web/sidebar/SidebarSettings.js @@ -0,0 +1,552 @@ +import { app } from "/scripts/app.js"; +import { GLASS_SHAPES, GLASS_POSITIONS, ACTIVATION_KEYS, RESET_KEYS, TOGGLE_FOLLOW_KEYS, PANEL_POSITIONS } from "../shared/constants.js"; +const Icons = { + chevronDown: ``, + reset: `` +}; +function createSlider(label, value, min, max, step, unit, onChange, tooltip, onInput) { + const row = document.createElement("div"); + row.className = "magnify-control-row"; + if (tooltip) row.title = tooltip; + const labelRow = document.createElement("div"); + labelRow.className = "magnify-control-label-row"; + const labelEl = document.createElement("label"); + labelEl.textContent = label; + const valueEl = document.createElement("span"); + valueEl.className = "control-value"; + valueEl.textContent = `${value}${unit}`; + labelRow.appendChild(labelEl); + labelRow.appendChild(valueEl); + const slider = document.createElement("input"); + slider.type = "range"; + slider.className = "magnify-slider"; + slider.min = String(min); + slider.max = String(max); + slider.step = String(step); + slider.value = String(value); + slider.addEventListener("mousedown", (e) => e.stopPropagation()); + slider.addEventListener("touchstart", (e) => e.stopPropagation()); + slider.addEventListener("pointerdown", (e) => e.stopPropagation()); + slider.addEventListener("input", (e) => { + e.stopPropagation(); + const newValue = parseFloat(slider.value); + valueEl.textContent = `${newValue}${unit}`; + if (onInput) onInput(newValue); + }); + slider.addEventListener("change", (e) => { + e.stopPropagation(); + const newValue = parseFloat(slider.value); + onChange(newValue); + }); + row.appendChild(labelRow); + row.appendChild(slider); + return row; +} +function createToggle(label, checked, onChange, tooltip) { + const row = document.createElement("div"); + row.className = "magnify-toggle-row"; + if (tooltip) row.title = tooltip; + const labelEl = document.createElement("label"); + labelEl.textContent = label; + const toggle = document.createElement("div"); + toggle.className = `magnify-toggle${checked ? " active" : ""}`; + toggle.addEventListener("click", () => { + const isActive = toggle.classList.toggle("active"); + onChange(isActive); + }); + row.appendChild(labelEl); + row.appendChild(toggle); + return row; +} +function createSelect(label, value, options, onChange, tooltip) { + const row = document.createElement("div"); + row.className = "magnify-control-row"; + if (tooltip) row.title = tooltip; + const labelEl = document.createElement("label"); + labelEl.textContent = label; + const select = document.createElement("select"); + select.className = "magnify-select"; + options.forEach((opt) => { + const option = document.createElement("option"); + option.value = opt; + option.textContent = opt; + if (opt === value) option.selected = true; + select.appendChild(option); + }); + select.addEventListener("change", () => onChange(select.value)); + row.appendChild(labelEl); + row.appendChild(select); + return row; +} +function createColorPicker(label, value, onChange, tooltip, onInput) { + const row = document.createElement("div"); + row.className = "magnify-control-row magnify-color-row"; + if (tooltip) row.title = tooltip; + const labelEl = document.createElement("label"); + labelEl.textContent = label; + const colorWrapper = document.createElement("div"); + colorWrapper.className = "magnify-color-wrapper"; + const colorInput = document.createElement("input"); + colorInput.type = "color"; + colorInput.className = "magnify-color-input"; + colorInput.value = value; + const colorPreview = document.createElement("span"); + colorPreview.className = "magnify-color-preview"; + colorPreview.textContent = value; + colorInput.addEventListener("input", () => { + colorPreview.textContent = colorInput.value; + if (onInput) onInput(colorInput.value); + }); + colorInput.addEventListener("change", () => { + onChange(colorInput.value); + }); + colorWrapper.appendChild(colorInput); + colorWrapper.appendChild(colorPreview); + row.appendChild(labelEl); + row.appendChild(colorWrapper); + return row; +} +function getSettingValue(id, defaultValue) { + try { + const value = app.ui.settings.getSettingValue(id); + return value !== void 0 ? value : defaultValue; + } catch { + return defaultValue; + } +} +function setSettingValue(id, value) { + try { + app.ui.settings.setSettingValue(id, value); + } catch (e) { + console.warn(`Failed to set setting ${id}:`, e); + } +} +const SECTION_STATE_KEY = "magnifyglass-sidebar-sections"; +function getSectionStates() { + try { + const saved = localStorage.getItem(SECTION_STATE_KEY); + return saved ? JSON.parse(saved) : {}; + } catch { + return {}; + } +} +function saveSectionState(title, collapsed) { + try { + const states = getSectionStates(); + states[title] = collapsed; + localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(states)); + } catch { + } +} +function createSection(title, defaultCollapsed = false) { + const section = document.createElement("div"); + section.className = "magnify-sidebar-section"; + const savedStates = getSectionStates(); + const collapsed = savedStates[title] !== void 0 ? savedStates[title] : defaultCollapsed; + const header = document.createElement("div"); + header.className = `magnify-sidebar-section-header${collapsed ? " collapsed" : ""}`; + header.innerHTML = `${Icons.chevronDown}${title}`; + const body = document.createElement("div"); + body.className = `magnify-sidebar-section-body${collapsed ? " collapsed" : ""}`; + header.addEventListener("click", () => { + const isCollapsed = header.classList.toggle("collapsed"); + body.classList.toggle("collapsed"); + saveSectionState(title, isCollapsed); + }); + section.appendChild(header); + section.appendChild(body); + return { section, body }; +} +function renderSettingsPanel(container) { + const activationKey = getSettingValue("🔍MagnifyGlass.ActivationKey", "x").toUpperCase(); + const resetKey = getSettingValue("🔍MagnifyGlass.ResetKey", "o").toUpperCase(); + const toggleFollowKey = getSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", "h").toUpperCase(); + const glassSection = createSection("Glass Settings"); + glassSection.body.appendChild(createSlider( + "Zoom Factor", + getSettingValue("🔍MagnifyGlass.ZoomFactor", 300), + 100, + 1e3, + 25, + "%", + (value) => setSettingValue("🔍MagnifyGlass.ZoomFactor", value), + "Magnification level of the glass (100-1000%)", + (value) => { + const mg = window.comfyUIMagnifyGlass; + if (mg == null ? void 0 : mg.config) { + mg.config.zoomFactor = value / 100; + if (mg.state.active) mg.updateMagnifiedView(); + } + } + )); + glassSection.body.appendChild(createSlider( + "Glass Size", + getSettingValue("🔍MagnifyGlass.GlassSize", 250), + 50, + 500, + 10, + "px", + (value) => setSettingValue("🔍MagnifyGlass.GlassSize", value), + "Size of the magnifying glass in pixels", + (value) => { + const mg = window.comfyUIMagnifyGlass; + if (mg == null ? void 0 : mg.config) { + mg.config.glassSize = value; + mg.applyUiChanges(); + if (mg.state.active) mg.updateMagnifiedView(); + } + } + )); + glassSection.body.appendChild(createSelect( + "Shape", + getSettingValue("🔍MagnifyGlass.GlassShape", "Rounded Square"), + GLASS_SHAPES, + (value) => setSettingValue("🔍MagnifyGlass.GlassShape", value), + "Shape of the magnifying glass" + )); + glassSection.body.appendChild(createSelect( + "Position", + getSettingValue("🔍MagnifyGlass.GlassPosition", "Top-Right"), + GLASS_POSITIONS, + (value) => setSettingValue("🔍MagnifyGlass.GlassPosition", value), + `Default position of the glass. Press ${resetKey} to reset.` + )); + glassSection.body.appendChild(createSelect( + "Filtering", + getSettingValue("🔍MagnifyGlass.TextureFiltering", "Linear"), + ["Linear", "Nearest"], + (value) => setSettingValue("🔍MagnifyGlass.TextureFiltering", value), + "Texture filtering: Linear (smooth) or Nearest (pixelated)" + )); + glassSection.body.appendChild(createSlider( + "Border Width", + getSettingValue("🔍MagnifyGlass.BorderWidth", 2), + 0, + 10, + 0.5, + "px", + (value) => setSettingValue("🔍MagnifyGlass.BorderWidth", value), + "Width of the glass border in pixels", + (value) => { + const mg = window.comfyUIMagnifyGlass; + if (mg == null ? void 0 : mg.config) { + mg.config.borderWidth = value; + mg.applyUiChanges(); + } + } + )); + glassSection.body.appendChild(createColorPicker( + "Border Color", + getSettingValue("🔍MagnifyGlass.BorderColor", "#ffffff"), + (value) => setSettingValue("🔍MagnifyGlass.BorderColor", value), + "Color of the glass border", + (value) => { + const mg = window.comfyUIMagnifyGlass; + if (mg == null ? void 0 : mg.config) { + mg.config.borderColor = value; + mg.applyUiChanges(); + } + } + )); + glassSection.body.appendChild(createToggle( + "Show Border", + getSettingValue("🔍MagnifyGlass.BorderEnabled", true), + (checked) => setSettingValue("🔍MagnifyGlass.BorderEnabled", checked), + "Show or hide the glass border" + )); + glassSection.body.appendChild(createToggle( + "Follow Cursor", + getSettingValue("🔍MagnifyGlass.FollowCursor", false), + (checked) => setSettingValue("🔍MagnifyGlass.FollowCursor", checked), + `Glass follows the cursor. Hotkey: ${toggleFollowKey}` + )); + glassSection.body.appendChild(createToggle( + "Always Active Mode", + getSettingValue("🔍MagnifyGlass.AlwaysActiveMode", true), + (checked) => setSettingValue("🔍MagnifyGlass.AlwaysActiveMode", checked), + `When enabled, glass stays visible. Toggle with ${activationKey}` + )); + container.appendChild(glassSection.section); + const hotkeySection = createSection("Hotkeys", true); + hotkeySection.body.appendChild(createSelect( + "Activation Key", + getSettingValue("🔍MagnifyGlass.ActivationKey", "x"), + ACTIVATION_KEYS, + (value) => setSettingValue("🔍MagnifyGlass.ActivationKey", value), + "Key to toggle or hold to activate the magnifying glass" + )); + hotkeySection.body.appendChild(createSelect( + "Reset Key", + getSettingValue("🔍MagnifyGlass.ResetKey", "o"), + RESET_KEYS, + (value) => setSettingValue("🔍MagnifyGlass.ResetKey", value), + "Key to reset glass position and disable follow cursor" + )); + hotkeySection.body.appendChild(createSelect( + "Toggle Follow Key", + getSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", "h"), + TOGGLE_FOLLOW_KEYS, + (value) => setSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", value), + "Key to toggle follow cursor mode" + )); + hotkeySection.body.appendChild(createToggle( + "Require Alt Key", + getSettingValue("🔍MagnifyGlass.AltRequired", false), + (checked) => setSettingValue("🔍MagnifyGlass.AltRequired", checked), + "Require Alt key to be held with activation key" + )); + hotkeySection.body.appendChild(createSlider( + "Offset Step", + getSettingValue("🔍MagnifyGlass.OffsetStep", 10), + 1, + 50, + 1, + "px", + (value) => setSettingValue("🔍MagnifyGlass.OffsetStep", value), + "Amount to move glass per arrow key press (pixels)" + )); + container.appendChild(hotkeySection.section); + const panelSection = createSection("Info Panel", true); + const toggleHotkey = getSettingValue("🔍MagnifyGlass.ToggleHotkey", "i").toUpperCase(); + getSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", "g").toUpperCase(); + getSettingValue("🔍MagnifyGlass.PinPanelHotkey", "u").toUpperCase(); + panelSection.body.appendChild(createToggle( + "Enable Panel", + getSettingValue("🔍MagnifyGlass.InfoPanelEnabled", true), + (checked) => setSettingValue("🔍MagnifyGlass.InfoPanelEnabled", checked), + `Show info panel with node details. Toggle: ${toggleHotkey}` + )); + panelSection.body.appendChild(createSelect( + "Position", + getSettingValue("🔍MagnifyGlass.InfoPanelPosition", "Bottom"), + PANEL_POSITIONS, + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelPosition", value), + "Position of the info panel relative to the glass" + )); + panelSection.body.appendChild(createSlider( + "Width", + getSettingValue("🔍MagnifyGlass.InfoPanelWidth", 300), + 200, + 600, + 20, + "px", + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelWidth", value), + "Width of the info panel", + (value) => { + var _a, _b; + const infoPanel = window.infoPanelManager; + if ((_b = (_a = infoPanel == null ? void 0 : infoPanel.stateManager) == null ? void 0 : _a.state) == null ? void 0 : _b.settings) { + infoPanel.stateManager.state.settings["🔍MagnifyGlass.InfoPanelWidth"] = value; + infoPanel.uiManager.applyStyles(); + if (infoPanel.magnifyGlass.state.active) { + infoPanel.uiManager.show(); + infoPanel.positionManager.positionPanel(); + } + } + } + )); + panelSection.body.appendChild(createSlider( + "Max Height", + getSettingValue("🔍MagnifyGlass.InfoPanelMaxHeight", 300), + 200, + 1500, + 50, + "px", + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelMaxHeight", value), + "Maximum height of the info panel", + (value) => { + var _a, _b; + console.log("[MaxHeight] Handler called with value:", value); + const infoPanel = window.infoPanelManager; + console.log("[MaxHeight] infoPanelManager:", infoPanel ? "found" : "NOT FOUND"); + if ((_b = (_a = infoPanel == null ? void 0 : infoPanel.stateManager) == null ? void 0 : _a.state) == null ? void 0 : _b.settings) { + console.log("[MaxHeight] Updating setting and calling applyStyles"); + infoPanel.stateManager.state.settings["🔍MagnifyGlass.InfoPanelMaxHeight"] = value; + infoPanel.uiManager.applyStyles(); + if (infoPanel.magnifyGlass.state.active) { + console.log("[MaxHeight] Glass is active, calling show() and positionPanel()"); + infoPanel.uiManager.show(); + infoPanel.positionManager.positionPanel(); + } else { + console.log("[MaxHeight] Glass is NOT active, skipping show()"); + } + } else { + console.warn("[MaxHeight] Check failed - stateManager/state/settings missing"); + } + } + )); + panelSection.body.appendChild(createSlider( + "Opacity", + getSettingValue("🔍MagnifyGlass.InfoPanelOpacity", 95), + 10, + 100, + 5, + "%", + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelOpacity", value), + "Background opacity of the info panel", + (value) => { + var _a, _b; + const infoPanel = window.infoPanelManager; + if ((_b = (_a = infoPanel == null ? void 0 : infoPanel.stateManager) == null ? void 0 : _a.state) == null ? void 0 : _b.settings) { + infoPanel.stateManager.state.settings["🔍MagnifyGlass.InfoPanelOpacity"] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + panelSection.body.appendChild(createColorPicker( + "Text Color", + getSettingValue("🔍MagnifyGlass.InfoPanelTextColor", "#6b7280"), + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelTextColor", value), + "Text color in the info panel", + (value) => { + var _a, _b; + const infoPanel = window.infoPanelManager; + if ((_b = (_a = infoPanel == null ? void 0 : infoPanel.stateManager) == null ? void 0 : _a.state) == null ? void 0 : _b.settings) { + infoPanel.stateManager.state.settings["🔍MagnifyGlass.InfoPanelTextColor"] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + panelSection.body.appendChild(createColorPicker( + "Accent Color", + getSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", "#3b82f6"), + (value) => setSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", value), + "Accent color for highlights", + (value) => { + var _a, _b; + const infoPanel = window.infoPanelManager; + if ((_b = (_a = infoPanel == null ? void 0 : infoPanel.stateManager) == null ? void 0 : _a.state) == null ? void 0 : _b.settings) { + infoPanel.stateManager.state.settings["🔍MagnifyGlass.InfoPanelAccentColor"] = value; + infoPanel.uiManager.applyStyles(); + } + } + )); + panelSection.body.appendChild(createToggle( + "Animations", + getSettingValue("🔍MagnifyGlass.InfoPanelAnimations", false), + (checked) => setSettingValue("🔍MagnifyGlass.InfoPanelAnimations", checked), + "Enable smooth animations for panel updates" + )); + panelSection.body.appendChild(createToggle( + "Hover Controls", + getSettingValue("🔍MagnifyGlass.ShowHoveringControls", true), + (checked) => setSettingValue("🔍MagnifyGlass.ShowHoveringControls", checked), + "Show floating control buttons near the panel" + )); + panelSection.body.appendChild(createSelect( + "Controls Position", + getSettingValue("🔍MagnifyGlass.ControlsPosition", "bottom"), + ["top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"], + (value) => setSettingValue("🔍MagnifyGlass.ControlsPosition", value), + "Position of floating control buttons" + )); + container.appendChild(panelSection.section); + const panelHotkeySection = createSection("Panel Hotkeys", true); + panelHotkeySection.body.appendChild(createSelect( + "Toggle Key", + getSettingValue("🔍MagnifyGlass.ToggleHotkey", "i"), + ["i", "j", "k", "l", "n", "m"], + (value) => setSettingValue("🔍MagnifyGlass.ToggleHotkey", value), + "Key to toggle info panel visibility" + )); + panelHotkeySection.body.appendChild(createSelect( + "Glass Toggle Key", + getSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", "g"), + ["g", "f", "v", "b"], + (value) => setSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", value), + "Key to toggle glass preview in panel" + )); + panelHotkeySection.body.appendChild(createSelect( + "Pin Key", + getSettingValue("🔍MagnifyGlass.PinPanelHotkey", "u"), + ["u", "p", "y", "t"], + (value) => setSettingValue("🔍MagnifyGlass.PinPanelHotkey", value), + "Key to pin/unpin the info panel position" + )); + container.appendChild(panelHotkeySection.section); + const debugSection = createSection("Debug", true); + debugSection.body.appendChild(createToggle( + "Debug Mode", + getSettingValue("🔍MagnifyGlass.DebugMode", false), + (checked) => setSettingValue("🔍MagnifyGlass.DebugMode", checked), + "Enable debug logging and visual indicators" + )); + container.appendChild(debugSection.section); + const buttonRow = document.createElement("div"); + buttonRow.style.padding = "16px"; + buttonRow.style.borderTop = "1px solid var(--border-color, #333)"; + buttonRow.style.display = "flex"; + buttonRow.style.flexDirection = "column"; + buttonRow.style.gap = "8px"; + const resetBtn = document.createElement("button"); + resetBtn.className = "magnify-btn"; + resetBtn.style.width = "100%"; + resetBtn.innerHTML = `${Icons.reset} Reset Position`; + resetBtn.title = `Reset glass position and disable follow cursor. Hotkey: ${resetKey}`; + resetBtn.addEventListener("click", (e) => { + e.stopPropagation(); + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass && magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + const followToggles = container.querySelectorAll(".magnify-toggle-row"); + followToggles.forEach((row) => { + const label = row.querySelector("label"); + if (label && label.textContent === "Follow Cursor") { + const toggle = row.querySelector(".magnify-toggle"); + if (toggle) toggle.classList.remove("active"); + } + }); + } + }); + buttonRow.appendChild(resetBtn); + const resetAllBtn = document.createElement("button"); + resetAllBtn.className = "magnify-btn magnify-btn-secondary"; + resetAllBtn.style.width = "100%"; + resetAllBtn.innerHTML = `${Icons.reset} Reset All Settings`; + resetAllBtn.title = "Reset all settings to their default values"; + resetAllBtn.addEventListener("click", (e) => { + e.stopPropagation(); + if (!confirm("Reset all MagnifyGlass settings to defaults?")) { + return; + } + setSettingValue("🔍MagnifyGlass.ZoomFactor", 300); + setSettingValue("🔍MagnifyGlass.GlassSize", 250); + setSettingValue("🔍MagnifyGlass.GlassShape", "Rounded Square"); + setSettingValue("🔍MagnifyGlass.GlassPosition", "Top-Right"); + setSettingValue("🔍MagnifyGlass.TextureFiltering", "Linear"); + setSettingValue("🔍MagnifyGlass.BorderWidth", 2); + setSettingValue("🔍MagnifyGlass.BorderColor", "#ffffff"); + setSettingValue("🔍MagnifyGlass.BorderEnabled", true); + setSettingValue("🔍MagnifyGlass.FollowCursor", false); + setSettingValue("🔍MagnifyGlass.AlwaysActiveMode", true); + setSettingValue("🔍MagnifyGlass.ActivationKey", "x"); + setSettingValue("🔍MagnifyGlass.ResetKey", "o"); + setSettingValue("🔍MagnifyGlass.ToggleFollowCursorKey", "h"); + setSettingValue("🔍MagnifyGlass.AltRequired", false); + setSettingValue("🔍MagnifyGlass.OffsetStep", 10); + setSettingValue("🔍MagnifyGlass.DebugMode", false); + setSettingValue("🔍MagnifyGlass.InfoPanelEnabled", true); + setSettingValue("🔍MagnifyGlass.InfoPanelPosition", "Bottom"); + setSettingValue("🔍MagnifyGlass.InfoPanelWidth", 300); + setSettingValue("🔍MagnifyGlass.InfoPanelMaxHeight", 600); + setSettingValue("🔍MagnifyGlass.InfoPanelOpacity", 95); + setSettingValue("🔍MagnifyGlass.InfoPanelTextColor", "#6b7280"); + setSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", "#3b82f6"); + setSettingValue("🔍MagnifyGlass.InfoPanelAnimations", false); + setSettingValue("🔍MagnifyGlass.ShowHoveringControls", true); + setSettingValue("🔍MagnifyGlass.ControlsPosition", "bottom"); + setSettingValue("🔍MagnifyGlass.ToggleHotkey", "i"); + setSettingValue("🔍MagnifyGlass.GlassPreviewToggleHotkey", "g"); + setSettingValue("🔍MagnifyGlass.PinPanelHotkey", "u"); + const magnifyGlass = window.comfyUIMagnifyGlass; + if (magnifyGlass && magnifyGlass.resetOffsets) { + magnifyGlass.resetOffsets(); + } + }); + buttonRow.appendChild(resetAllBtn); + container.appendChild(buttonRow); +} +export { + renderSettingsPanel +}; +//# sourceMappingURL=SidebarSettings.js.map diff --git a/web/sidebar/SidebarSettings.js.map b/web/sidebar/SidebarSettings.js.map new file mode 100644 index 0000000..2a72dd0 --- /dev/null +++ b/web/sidebar/SidebarSettings.js.map @@ -0,0 +1 @@ +{"version":3,"file":"SidebarSettings.js","sources":["../../src/sidebar/SidebarSettings.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Sidebar Settings Panel\n * \n * All settings controls for the sidebar.\n */\n\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\nimport {\n GLASS_POSITIONS,\n GLASS_SHAPES,\n ACTIVATION_KEYS,\n RESET_KEYS,\n TOGGLE_FOLLOW_KEYS,\n PANEL_POSITIONS\n} from '../shared/constants';\n\n/**\n * Icons for the settings panel\n */\nconst Icons = {\n chevronDown: ``,\n reset: ``,\n};\n\n/**\n * Create a slider control with tooltip\n */\n/**\n * Create a slider control with tooltip\n */\nfunction createSlider(\n label: string,\n value: number,\n min: number,\n max: number,\n step: number,\n unit: string,\n onChange: (value: number) => void,\n tooltip?: string,\n onInput?: (value: number) => void\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'magnify-control-row';\n if (tooltip) row.title = tooltip;\n\n const labelRow = document.createElement('div');\n labelRow.className = 'magnify-control-label-row';\n\n const labelEl = document.createElement('label');\n labelEl.textContent = label;\n\n const valueEl = document.createElement('span');\n valueEl.className = 'control-value';\n valueEl.textContent = `${value}${unit}`;\n\n labelRow.appendChild(labelEl);\n labelRow.appendChild(valueEl);\n\n const slider = document.createElement('input');\n slider.type = 'range';\n slider.className = 'magnify-slider';\n slider.min = String(min);\n slider.max = String(max);\n slider.step = String(step);\n slider.value = String(value);\n\n // Prevent ComfyUI from capturing mouse events during drag\n slider.addEventListener('mousedown', (e) => e.stopPropagation());\n slider.addEventListener('touchstart', (e) => e.stopPropagation());\n slider.addEventListener('pointerdown', (e) => e.stopPropagation());\n\n slider.addEventListener('input', (e) => {\n e.stopPropagation();\n const newValue = parseFloat(slider.value);\n valueEl.textContent = `${newValue}${unit}`;\n if (onInput) onInput(newValue);\n });\n\n slider.addEventListener('change', (e) => {\n e.stopPropagation();\n const newValue = parseFloat(slider.value);\n onChange(newValue);\n });\n\n row.appendChild(labelRow);\n row.appendChild(slider);\n\n return row;\n}\n\n/**\n * Create a toggle control with tooltip\n */\nfunction createToggle(\n label: string,\n checked: boolean,\n onChange: (checked: boolean) => void,\n tooltip?: string\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'magnify-toggle-row';\n if (tooltip) row.title = tooltip;\n\n const labelEl = document.createElement('label');\n labelEl.textContent = label;\n\n const toggle = document.createElement('div');\n toggle.className = `magnify-toggle${checked ? ' active' : ''}`;\n\n toggle.addEventListener('click', () => {\n const isActive = toggle.classList.toggle('active');\n onChange(isActive);\n });\n\n row.appendChild(labelEl);\n row.appendChild(toggle);\n\n return row;\n}\n\n/**\n * Create a select dropdown control with tooltip\n */\nfunction createSelect(\n label: string,\n value: string,\n options: readonly string[] | string[],\n onChange: (value: string) => void,\n tooltip?: string\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'magnify-control-row';\n if (tooltip) row.title = tooltip;\n\n const labelEl = document.createElement('label');\n labelEl.textContent = label;\n\n const select = document.createElement('select');\n select.className = 'magnify-select';\n\n options.forEach(opt => {\n const option = document.createElement('option');\n option.value = opt;\n option.textContent = opt;\n if (opt === value) option.selected = true;\n select.appendChild(option);\n });\n\n select.addEventListener('change', () => onChange(select.value));\n\n row.appendChild(labelEl);\n row.appendChild(select);\n\n return row;\n}\n\n/**\n * Create a color picker control with tooltip\n */\n/**\n * Create a color picker control with tooltip\n */\nfunction createColorPicker(\n label: string,\n value: string,\n onChange: (value: string) => void,\n tooltip?: string,\n onInput?: (value: string) => void\n): HTMLElement {\n const row = document.createElement('div');\n row.className = 'magnify-control-row magnify-color-row';\n if (tooltip) row.title = tooltip;\n\n const labelEl = document.createElement('label');\n labelEl.textContent = label;\n\n const colorWrapper = document.createElement('div');\n colorWrapper.className = 'magnify-color-wrapper';\n\n const colorInput = document.createElement('input');\n colorInput.type = 'color';\n colorInput.className = 'magnify-color-input';\n colorInput.value = value;\n\n const colorPreview = document.createElement('span');\n colorPreview.className = 'magnify-color-preview';\n colorPreview.textContent = value;\n\n colorInput.addEventListener('input', () => {\n colorPreview.textContent = colorInput.value;\n if (onInput) onInput(colorInput.value);\n });\n\n colorInput.addEventListener('change', () => {\n onChange(colorInput.value);\n });\n\n colorWrapper.appendChild(colorInput);\n colorWrapper.appendChild(colorPreview);\n\n row.appendChild(labelEl);\n row.appendChild(colorWrapper);\n\n return row;\n}\n\n/**\n * Get current MagnifyGlass settings from app.ui.settings\n */\nfunction getSettingValue(id: string, defaultValue: any): any {\n try {\n const value = app.ui.settings.getSettingValue(id);\n return value !== undefined ? value : defaultValue;\n } catch {\n return defaultValue;\n }\n}\n\n/**\n * Set a MagnifyGlass setting via app.ui.settings\n */\nfunction setSettingValue(id: string, value: any): void {\n try {\n app.ui.settings.setSettingValue(id, value);\n } catch (e) {\n console.warn(`Failed to set setting ${id}:`, e);\n }\n}\n\n/** Storage key for section states */\nconst SECTION_STATE_KEY = 'magnifyglass-sidebar-sections';\n\n/**\n * Get saved section states from localStorage\n */\nfunction getSectionStates(): Record {\n try {\n const saved = localStorage.getItem(SECTION_STATE_KEY);\n return saved ? JSON.parse(saved) : {};\n } catch {\n return {};\n }\n}\n\n/**\n * Save section state to localStorage\n */\nfunction saveSectionState(title: string, collapsed: boolean): void {\n try {\n const states = getSectionStates();\n states[title] = collapsed;\n localStorage.setItem(SECTION_STATE_KEY, JSON.stringify(states));\n } catch {\n // Ignore storage errors\n }\n}\n\n/**\n * Create a collapsible section with persistent state\n */\nfunction createSection(title: string, defaultCollapsed: boolean = false): { section: HTMLElement; body: HTMLElement } {\n const section = document.createElement('div');\n section.className = 'magnify-sidebar-section';\n\n // Check if we have a saved state for this section\n const savedStates = getSectionStates();\n const collapsed = savedStates[title] !== undefined ? savedStates[title] : defaultCollapsed;\n\n const header = document.createElement('div');\n header.className = `magnify-sidebar-section-header${collapsed ? ' collapsed' : ''}`;\n header.innerHTML = `${Icons.chevronDown}${title}`;\n\n const body = document.createElement('div');\n body.className = `magnify-sidebar-section-body${collapsed ? ' collapsed' : ''}`;\n\n header.addEventListener('click', () => {\n const isCollapsed = header.classList.toggle('collapsed');\n body.classList.toggle('collapsed');\n // Save the new state\n saveSectionState(title, isCollapsed);\n });\n\n section.appendChild(header);\n section.appendChild(body);\n\n return { section, body };\n}\n\n/**\n * Render the settings panel into the container\n */\nexport function renderSettingsPanel(container: HTMLElement): void {\n // Get current hotkey values for tooltips\n const activationKey = getSettingValue('🔍MagnifyGlass.ActivationKey', 'x').toUpperCase();\n const resetKey = getSettingValue('🔍MagnifyGlass.ResetKey', 'o').toUpperCase();\n const toggleFollowKey = getSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h').toUpperCase();\n\n // ===== Glass Settings Section =====\n const glassSection = createSection('Glass Settings');\n\n glassSection.body.appendChild(createSlider('Zoom Factor',\n getSettingValue('🔍MagnifyGlass.ZoomFactor', 300), 100, 1000, 25, '%',\n (value) => setSettingValue('🔍MagnifyGlass.ZoomFactor', value),\n 'Magnification level of the glass (100-1000%)',\n (value) => {\n const mg = (window as any).comfyUIMagnifyGlass;\n if (mg?.config) {\n mg.config.zoomFactor = value / 100;\n if (mg.state.active) mg.updateMagnifiedView();\n }\n }\n ));\n\n glassSection.body.appendChild(createSlider('Glass Size',\n getSettingValue('🔍MagnifyGlass.GlassSize', 250), 50, 500, 10, 'px',\n (value) => setSettingValue('🔍MagnifyGlass.GlassSize', value),\n 'Size of the magnifying glass in pixels',\n (value) => {\n const mg = (window as any).comfyUIMagnifyGlass;\n if (mg?.config) {\n mg.config.glassSize = value;\n mg.applyUiChanges();\n if (mg.state.active) mg.updateMagnifiedView();\n }\n }\n ));\n\n glassSection.body.appendChild(createSelect('Shape',\n getSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square'), GLASS_SHAPES,\n (value) => setSettingValue('🔍MagnifyGlass.GlassShape', value),\n 'Shape of the magnifying glass'\n ));\n\n glassSection.body.appendChild(createSelect('Position',\n getSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right'), GLASS_POSITIONS,\n (value) => setSettingValue('🔍MagnifyGlass.GlassPosition', value),\n `Default position of the glass. Press ${resetKey} to reset.`\n ));\n\n glassSection.body.appendChild(createSelect('Filtering',\n getSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear'), ['Linear', 'Nearest'],\n (value) => setSettingValue('🔍MagnifyGlass.TextureFiltering', value),\n 'Texture filtering: Linear (smooth) or Nearest (pixelated)'\n ));\n\n glassSection.body.appendChild(createSlider('Border Width',\n getSettingValue('🔍MagnifyGlass.BorderWidth', 2), 0, 10, 0.5, 'px',\n (value) => setSettingValue('🔍MagnifyGlass.BorderWidth', value),\n 'Width of the glass border in pixels',\n (value) => {\n const mg = (window as any).comfyUIMagnifyGlass;\n if (mg?.config) {\n mg.config.borderWidth = value;\n mg.applyUiChanges();\n }\n }\n ));\n\n glassSection.body.appendChild(createColorPicker('Border Color',\n getSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff'),\n (value) => setSettingValue('🔍MagnifyGlass.BorderColor', value),\n 'Color of the glass border',\n (value) => {\n const mg = (window as any).comfyUIMagnifyGlass;\n if (mg?.config) {\n mg.config.borderColor = value;\n mg.applyUiChanges();\n }\n }\n ));\n\n glassSection.body.appendChild(createToggle('Show Border',\n getSettingValue('🔍MagnifyGlass.BorderEnabled', true),\n (checked) => setSettingValue('🔍MagnifyGlass.BorderEnabled', checked),\n 'Show or hide the glass border'\n ));\n\n glassSection.body.appendChild(createToggle('Follow Cursor',\n getSettingValue('🔍MagnifyGlass.FollowCursor', false),\n (checked) => setSettingValue('🔍MagnifyGlass.FollowCursor', checked),\n `Glass follows the cursor. Hotkey: ${toggleFollowKey}`\n ));\n\n glassSection.body.appendChild(createToggle('Always Active Mode',\n getSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true),\n (checked) => setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', checked),\n `When enabled, glass stays visible. Toggle with ${activationKey}`\n ));\n\n container.appendChild(glassSection.section);\n\n // ===== Hotkeys Section =====\n const hotkeySection = createSection('Hotkeys', true);\n\n hotkeySection.body.appendChild(createSelect('Activation Key',\n getSettingValue('🔍MagnifyGlass.ActivationKey', 'x'), ACTIVATION_KEYS,\n (value) => setSettingValue('🔍MagnifyGlass.ActivationKey', value),\n 'Key to toggle or hold to activate the magnifying glass'\n ));\n\n hotkeySection.body.appendChild(createSelect('Reset Key',\n getSettingValue('🔍MagnifyGlass.ResetKey', 'o'), RESET_KEYS,\n (value) => setSettingValue('🔍MagnifyGlass.ResetKey', value),\n 'Key to reset glass position and disable follow cursor'\n ));\n\n hotkeySection.body.appendChild(createSelect('Toggle Follow Key',\n getSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h'), TOGGLE_FOLLOW_KEYS,\n (value) => setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', value),\n 'Key to toggle follow cursor mode'\n ));\n\n hotkeySection.body.appendChild(createToggle('Require Alt Key',\n getSettingValue('🔍MagnifyGlass.AltRequired', false),\n (checked) => setSettingValue('🔍MagnifyGlass.AltRequired', checked),\n 'Require Alt key to be held with activation key'\n ));\n\n hotkeySection.body.appendChild(createSlider('Offset Step',\n getSettingValue('🔍MagnifyGlass.OffsetStep', 10), 1, 50, 1, 'px',\n (value) => setSettingValue('🔍MagnifyGlass.OffsetStep', value),\n 'Amount to move glass per arrow key press (pixels)'\n ));\n\n container.appendChild(hotkeySection.section);\n\n // ===== Info Panel Section =====\n const panelSection = createSection('Info Panel', true);\n\n const toggleHotkey = getSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i').toUpperCase();\n const glassToggleKey = getSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g').toUpperCase();\n const pinHotkeyVal = getSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u').toUpperCase();\n\n panelSection.body.appendChild(createToggle('Enable Panel',\n getSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true),\n (checked) => setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', checked),\n `Show info panel with node details. Toggle: ${toggleHotkey}`\n ));\n\n panelSection.body.appendChild(createSelect('Position',\n getSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom'), PANEL_POSITIONS,\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelPosition', value),\n 'Position of the info panel relative to the glass'\n ));\n\n panelSection.body.appendChild(createSlider('Width',\n getSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300), 200, 600, 20, 'px',\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelWidth', value),\n 'Width of the info panel',\n (value) => {\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.stateManager?.state?.settings) {\n infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelWidth'] = value;\n infoPanel.uiManager.applyStyles();\n\n // Ensure visible and reflow if active\n if (infoPanel.magnifyGlass.state.active) {\n infoPanel.uiManager.show();\n infoPanel.positionManager.positionPanel();\n }\n }\n }\n ));\n\n panelSection.body.appendChild(createSlider('Max Height',\n getSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 300), 200, 1500, 50, 'px',\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', value),\n 'Maximum height of the info panel',\n (value) => {\n console.log('[MaxHeight] Handler called with value:', value);\n const infoPanel = (window as any).infoPanelManager;\n console.log('[MaxHeight] infoPanelManager:', infoPanel ? 'found' : 'NOT FOUND');\n if (infoPanel?.stateManager?.state?.settings) {\n console.log('[MaxHeight] Updating setting and calling applyStyles');\n infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelMaxHeight'] = value;\n infoPanel.uiManager.applyStyles();\n\n // Ensure visible and reflow if active\n if (infoPanel.magnifyGlass.state.active) {\n console.log('[MaxHeight] Glass is active, calling show() and positionPanel()');\n infoPanel.uiManager.show();\n infoPanel.positionManager.positionPanel();\n } else {\n console.log('[MaxHeight] Glass is NOT active, skipping show()');\n }\n } else {\n console.warn('[MaxHeight] Check failed - stateManager/state/settings missing');\n }\n }\n ));\n\n panelSection.body.appendChild(createSlider('Opacity',\n getSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95), 10, 100, 5, '%',\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', value),\n 'Background opacity of the info panel',\n (value) => {\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.stateManager?.state?.settings) {\n infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelOpacity'] = value;\n infoPanel.uiManager.applyStyles();\n }\n }\n ));\n\n panelSection.body.appendChild(createColorPicker('Text Color',\n getSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280'),\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', value),\n 'Text color in the info panel',\n (value) => {\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.stateManager?.state?.settings) {\n infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelTextColor'] = value;\n infoPanel.uiManager.applyStyles();\n }\n }\n ));\n\n panelSection.body.appendChild(createColorPicker('Accent Color',\n getSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6'),\n (value) => setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', value),\n 'Accent color for highlights',\n (value) => {\n const infoPanel = (window as any).infoPanelManager;\n if (infoPanel?.stateManager?.state?.settings) {\n infoPanel.stateManager.state.settings['🔍MagnifyGlass.InfoPanelAccentColor'] = value;\n infoPanel.uiManager.applyStyles();\n }\n }\n ));\n\n panelSection.body.appendChild(createToggle('Animations',\n getSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false),\n (checked) => setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', checked),\n 'Enable smooth animations for panel updates'\n ));\n\n panelSection.body.appendChild(createToggle('Hover Controls',\n getSettingValue('🔍MagnifyGlass.ShowHoveringControls', true),\n (checked) => setSettingValue('🔍MagnifyGlass.ShowHoveringControls', checked),\n 'Show floating control buttons near the panel'\n ));\n\n panelSection.body.appendChild(createSelect('Controls Position',\n getSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom'),\n ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],\n (value) => setSettingValue('🔍MagnifyGlass.ControlsPosition', value),\n 'Position of floating control buttons'\n ));\n\n container.appendChild(panelSection.section);\n\n // ===== Panel Hotkeys Section =====\n const panelHotkeySection = createSection('Panel Hotkeys', true);\n\n panelHotkeySection.body.appendChild(createSelect('Toggle Key',\n getSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i'), ['i', 'j', 'k', 'l', 'n', 'm'],\n (value) => setSettingValue('🔍MagnifyGlass.ToggleHotkey', value),\n 'Key to toggle info panel visibility'\n ));\n\n panelHotkeySection.body.appendChild(createSelect('Glass Toggle Key',\n getSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g'), ['g', 'f', 'v', 'b'],\n (value) => setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', value),\n 'Key to toggle glass preview in panel'\n ));\n\n panelHotkeySection.body.appendChild(createSelect('Pin Key',\n getSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u'), ['u', 'p', 'y', 't'],\n (value) => setSettingValue('🔍MagnifyGlass.PinPanelHotkey', value),\n 'Key to pin/unpin the info panel position'\n ));\n\n container.appendChild(panelHotkeySection.section);\n\n // ===== Debug Section =====\n const debugSection = createSection('Debug', true);\n\n debugSection.body.appendChild(createToggle('Debug Mode',\n getSettingValue('🔍MagnifyGlass.DebugMode', false),\n (checked) => setSettingValue('🔍MagnifyGlass.DebugMode', checked),\n 'Enable debug logging and visual indicators'\n ));\n\n container.appendChild(debugSection.section);\n\n // ===== Button Row =====\n const buttonRow = document.createElement('div');\n buttonRow.style.padding = '16px';\n buttonRow.style.borderTop = '1px solid var(--border-color, #333)';\n buttonRow.style.display = 'flex';\n buttonRow.style.flexDirection = 'column';\n buttonRow.style.gap = '8px';\n\n // Reset Position Button\n const resetBtn = document.createElement('button');\n resetBtn.className = 'magnify-btn';\n resetBtn.style.width = '100%';\n resetBtn.innerHTML = `${Icons.reset} Reset Position`;\n resetBtn.title = `Reset glass position and disable follow cursor. Hotkey: ${resetKey}`;\n\n resetBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.resetOffsets) {\n magnifyGlass.resetOffsets();\n // Update follow cursor toggle in UI\n const followToggles = container.querySelectorAll('.magnify-toggle-row');\n followToggles.forEach(row => {\n const label = row.querySelector('label');\n if (label && label.textContent === 'Follow Cursor') {\n const toggle = row.querySelector('.magnify-toggle');\n if (toggle) toggle.classList.remove('active');\n }\n });\n }\n });\n\n buttonRow.appendChild(resetBtn);\n\n // Reset All Settings Button\n const resetAllBtn = document.createElement('button');\n resetAllBtn.className = 'magnify-btn magnify-btn-secondary';\n resetAllBtn.style.width = '100%';\n resetAllBtn.innerHTML = `${Icons.reset} Reset All Settings`;\n resetAllBtn.title = 'Reset all settings to their default values';\n\n resetAllBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n\n // Confirm with user\n if (!confirm('Reset all MagnifyGlass settings to defaults?')) {\n return;\n }\n\n // Default glass settings\n setSettingValue('🔍MagnifyGlass.ZoomFactor', 300);\n setSettingValue('🔍MagnifyGlass.GlassSize', 250);\n setSettingValue('🔍MagnifyGlass.GlassShape', 'Rounded Square');\n setSettingValue('🔍MagnifyGlass.GlassPosition', 'Top-Right');\n setSettingValue('🔍MagnifyGlass.TextureFiltering', 'Linear');\n setSettingValue('🔍MagnifyGlass.BorderWidth', 2);\n setSettingValue('🔍MagnifyGlass.BorderColor', '#ffffff');\n setSettingValue('🔍MagnifyGlass.BorderEnabled', true);\n setSettingValue('🔍MagnifyGlass.FollowCursor', false);\n setSettingValue('🔍MagnifyGlass.AlwaysActiveMode', true);\n setSettingValue('🔍MagnifyGlass.ActivationKey', 'x');\n setSettingValue('🔍MagnifyGlass.ResetKey', 'o');\n setSettingValue('🔍MagnifyGlass.ToggleFollowCursorKey', 'h');\n setSettingValue('🔍MagnifyGlass.AltRequired', false);\n setSettingValue('🔍MagnifyGlass.OffsetStep', 10);\n setSettingValue('🔍MagnifyGlass.DebugMode', false);\n\n // Default panel settings\n setSettingValue('🔍MagnifyGlass.InfoPanelEnabled', true);\n setSettingValue('🔍MagnifyGlass.InfoPanelPosition', 'Bottom');\n setSettingValue('🔍MagnifyGlass.InfoPanelWidth', 300);\n setSettingValue('🔍MagnifyGlass.InfoPanelMaxHeight', 600);\n setSettingValue('🔍MagnifyGlass.InfoPanelOpacity', 95);\n setSettingValue('🔍MagnifyGlass.InfoPanelTextColor', '#6b7280');\n setSettingValue('🔍MagnifyGlass.InfoPanelAccentColor', '#3b82f6');\n setSettingValue('🔍MagnifyGlass.InfoPanelAnimations', false);\n setSettingValue('🔍MagnifyGlass.ShowHoveringControls', true);\n setSettingValue('🔍MagnifyGlass.ControlsPosition', 'bottom');\n setSettingValue('🔍MagnifyGlass.ToggleHotkey', 'i');\n setSettingValue('🔍MagnifyGlass.GlassPreviewToggleHotkey', 'g');\n setSettingValue('🔍MagnifyGlass.PinPanelHotkey', 'u');\n\n // Also reset position\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (magnifyGlass && magnifyGlass.resetOffsets) {\n magnifyGlass.resetOffsets();\n }\n });\n\n buttonRow.appendChild(resetAllBtn);\n container.appendChild(buttonRow);\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/web/sidebar/sidebar.js b/web/sidebar/sidebar.js new file mode 100644 index 0000000..3c5cc02 --- /dev/null +++ b/web/sidebar/sidebar.js @@ -0,0 +1,64 @@ +import { app } from "/scripts/app.js"; +import { renderSettingsPanel } from "./SidebarSettings.js"; +const MAGNIFY_ICON = ``; +let sidebarRegistered = false; +function loadSidebarStyles() { + if (!document.getElementById("magnify-sidebar-styles")) { + const link = document.createElement("link"); + link.id = "magnify-sidebar-styles"; + link.rel = "stylesheet"; + link.type = "text/css"; + link.href = "extensions/comfyui-magnifyglass/sidebar.css"; + document.head.appendChild(link); + } +} +function renderSidebar(container) { + loadSidebarStyles(); + container.innerHTML = ""; + const sidebar = document.createElement("div"); + sidebar.className = "magnify-sidebar"; + const header = document.createElement("div"); + header.className = "magnify-sidebar-header"; + header.innerHTML = `${MAGNIFY_ICON}

Magnify G.

`; + sidebar.appendChild(header); + const content = document.createElement("div"); + content.className = "magnify-sidebar-content"; + renderSettingsPanel(content); + sidebar.appendChild(content); + container.appendChild(sidebar); +} +function registerSidebar() { + if (sidebarRegistered) { + return; + } + if (!app.extensionManager) { + console.warn("MagnifyGlass: extensionManager not available, sidebar registration skipped"); + return; + } + try { + app.extensionManager.registerSidebarTab({ + id: "magnifyglass", + icon: "pi pi-search", + title: "Magnify G.", + tooltip: "MagnifyGlass Settings", + type: "custom", + render: (el) => { + renderSidebar(el); + } + }); + sidebarRegistered = true; + console.log("MagnifyGlass: Sidebar registered successfully"); + } catch (e) { + console.warn("MagnifyGlass: Failed to register sidebar:", e); + } +} +function initSidebar() { + setTimeout(() => { + registerSidebar(); + }, 100); +} +export { + initSidebar, + registerSidebar +}; +//# sourceMappingURL=sidebar.js.map diff --git a/web/sidebar/sidebar.js.map b/web/sidebar/sidebar.js.map new file mode 100644 index 0000000..6a044cc --- /dev/null +++ b/web/sidebar/sidebar.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sidebar.js","sources":["../../src/sidebar/sidebar.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Sidebar Entry Point\n * \n * Registers the MagnifyGlass sidebar tab with ComfyUI.\n */\n\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\nimport { renderSettingsPanel } from './SidebarSettings';\n\n/**\n * Magnify glass icon SVG\n */\nconst MAGNIFY_ICON = ``;\n\n/** Track if sidebar has been registered */\nlet sidebarRegistered = false;\n\n/**\n * Load sidebar CSS\n */\nfunction loadSidebarStyles(): void {\n if (!document.getElementById('magnify-sidebar-styles')) {\n const link = document.createElement('link');\n link.id = 'magnify-sidebar-styles';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/sidebar.css';\n document.head.appendChild(link);\n }\n}\n\n/**\n * Render the sidebar content\n */\nfunction renderSidebar(container: HTMLElement): void {\n // Load styles\n loadSidebarStyles();\n\n // Clear existing content to prevent duplicates\n container.innerHTML = '';\n\n // Create main container\n const sidebar = document.createElement('div');\n sidebar.className = 'magnify-sidebar';\n\n // Header\n const header = document.createElement('div');\n header.className = 'magnify-sidebar-header';\n header.innerHTML = `${MAGNIFY_ICON}

Magnify G.

`;\n sidebar.appendChild(header);\n\n // Content area\n const content = document.createElement('div');\n content.className = 'magnify-sidebar-content';\n\n // Render settings panel (contains all settings)\n renderSettingsPanel(content);\n\n sidebar.appendChild(content);\n container.appendChild(sidebar);\n}\n\n/**\n * Register the sidebar tab with ComfyUI\n */\nexport function registerSidebar(): void {\n // Prevent duplicate registration\n if (sidebarRegistered) {\n return;\n }\n\n // Wait for app to be ready\n if (!app.extensionManager) {\n console.warn('MagnifyGlass: extensionManager not available, sidebar registration skipped');\n return;\n }\n\n try {\n app.extensionManager.registerSidebarTab({\n id: 'magnifyglass',\n icon: 'pi pi-search',\n title: 'Magnify G.',\n tooltip: 'MagnifyGlass Settings',\n type: 'custom',\n render: (el: HTMLElement) => {\n renderSidebar(el);\n }\n });\n\n sidebarRegistered = true;\n console.log('MagnifyGlass: Sidebar registered successfully');\n } catch (e) {\n console.warn('MagnifyGlass: Failed to register sidebar:', e);\n }\n}\n\n/**\n * Initialize sidebar (called after app setup)\n */\nexport function initSidebar(): void {\n // Register on next tick to ensure app is ready\n setTimeout(() => {\n registerSidebar();\n }, 100);\n}\n"],"names":[],"mappings":";;AAaA,MAAM,eAAe;AAGrB,IAAI,oBAAoB;AAKxB,SAAS,oBAA0B;AAC/B,MAAI,CAAC,SAAS,eAAe,wBAAwB,GAAG;AACpD,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAK,KAAK;AACV,SAAK,MAAM;AACX,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,aAAS,KAAK,YAAY,IAAI;AAAA,EAClC;AACJ;AAKA,SAAS,cAAc,WAA8B;AAEjD,oBAAA;AAGA,YAAU,YAAY;AAGtB,QAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,UAAQ,YAAY;AAGpB,QAAM,SAAS,SAAS,cAAc,KAAK;AAC3C,SAAO,YAAY;AACnB,SAAO,YAAY,GAAG,YAAY;AAClC,UAAQ,YAAY,MAAM;AAG1B,QAAM,UAAU,SAAS,cAAc,KAAK;AAC5C,UAAQ,YAAY;AAGpB,sBAAoB,OAAO;AAE3B,UAAQ,YAAY,OAAO;AAC3B,YAAU,YAAY,OAAO;AACjC;AAKO,SAAS,kBAAwB;AAEpC,MAAI,mBAAmB;AACnB;AAAA,EACJ;AAGA,MAAI,CAAC,IAAI,kBAAkB;AACvB,YAAQ,KAAK,4EAA4E;AACzF;AAAA,EACJ;AAEA,MAAI;AACA,QAAI,iBAAiB,mBAAmB;AAAA,MACpC,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS;AAAA,MACT,MAAM;AAAA,MACN,QAAQ,CAAC,OAAoB;AACzB,sBAAc,EAAE;AAAA,MACpB;AAAA,IAAA,CACH;AAED,wBAAoB;AACpB,YAAQ,IAAI,+CAA+C;AAAA,EAC/D,SAAS,GAAG;AACR,YAAQ,KAAK,6CAA6C,CAAC;AAAA,EAC/D;AACJ;AAKO,SAAS,cAAoB;AAEhC,aAAW,MAAM;AACb,oBAAA;AAAA,EACJ,GAAG,GAAG;AACV;"} \ No newline at end of file