diff --git a/src/info-panel/PositionManager.ts b/src/info-panel/PositionManager.ts
index 05e35af..84b3575 100644
--- a/src/info-panel/PositionManager.ts
+++ b/src/info-panel/PositionManager.ts
@@ -141,5 +141,9 @@ export class PositionManager {
controlsElement.style.left = `${left}px`;
controlsElement.style.top = `${top}px`;
+
+ // Show controls now that they're positioned (initially hidden off-screen)
+ controlsElement.style.display = 'flex';
+ controlsElement.style.visibility = 'visible';
}
}
diff --git a/src/info-panel/UIManager.ts b/src/info-panel/UIManager.ts
index 0fe678a..2ef12c0 100644
--- a/src/info-panel/UIManager.ts
+++ b/src/info-panel/UIManager.ts
@@ -129,6 +129,12 @@ export class UIManager {
// Insert before the panel in the document body, not as a child
document.body.appendChild(this.elements.controls);
+ // Hide and position off-screen initially - will be shown when magnify glass activates and positions them
+ this.elements.controls.style.display = 'none';
+ this.elements.controls.style.visibility = 'hidden';
+ this.elements.controls.style.left = '-9999px';
+ this.elements.controls.style.top = '-9999px';
+
// Add click event delegation for control buttons
this.elements.controls.addEventListener('click', (e: MouseEvent) => {
const target = e.target as HTMLElement;
@@ -288,9 +294,7 @@ export class UIManager {
if (this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelEnabled"]) {
this.elements.panel.style.display = "block";
- if (this.elements.controls) {
- this.elements.controls.style.display = "flex";
- }
+ // Controls are shown by positionFloatingControls() after proper positioning
// Apply user's opacity setting when showing (convert percentage to decimal)
const opacityPercent = this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelOpacity"];
this.elements.panel.style.opacity = (opacityPercent / 100).toString();
diff --git a/src/magnify-glass/WebGLRenderer.ts b/src/magnify-glass/WebGLRenderer.ts
index bead91e..20749bc 100644
--- a/src/magnify-glass/WebGLRenderer.ts
+++ b/src/magnify-glass/WebGLRenderer.ts
@@ -108,7 +108,21 @@ export class WebGLRenderer {
this.gl = this.ui.glassCanvas.getContext("webgl", { preserveDrawingBuffer: true });
if (!this.gl) {
+ // Try webgl2 as fallback
+ this.gl = this.ui.glassCanvas.getContext("webgl2", { preserveDrawingBuffer: true }) as WebGLRenderingContext | null;
+ }
+
+ if (!this.gl) {
+ const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');
+ const browserInstructions = isFirefox
+ ? "Firefox: Go to about:config, search for 'webgl.disabled' and set it to 'false', then restart Firefox."
+ : "Check your browser settings to ensure WebGL is enabled.";
+
console.error("ComfyUI Magnifying Glass ERROR: WebGL not supported or context creation failed.");
+ console.error(`To fix: ${browserInstructions}`);
+
+ // Show user notification
+ this.showWebGLError(browserInstructions);
return;
}
@@ -354,4 +368,69 @@ export class WebGLRenderer {
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
}
}
+
+ /**
+ * Show WebGL error notification to user.
+ * @param instructions - Browser-specific instructions to fix
+ */
+ showWebGLError(instructions: string): void {
+ // Create a toast notification
+ const toast = document.createElement('div');
+ toast.id = 'magnifyglass-webgl-error';
+ toast.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ max-width: 400px;
+ padding: 16px 20px;
+ background: linear-gradient(135deg, #ff4444 0%, #cc3333 100%);
+ color: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ font-family: system-ui, -apple-system, sans-serif;
+ font-size: 14px;
+ line-height: 1.5;
+ z-index: 100000;
+ animation: slideIn 0.3s ease-out;
+ `;
+
+ toast.innerHTML = `
+
+
⚠️
+
+ MagnifyGlass: WebGL Disabled
+ ${instructions}
+
+ Dismiss
+
+
+
+ `;
+
+ // Add animation keyframes
+ if (!document.getElementById('magnifyglass-toast-style')) {
+ const style = document.createElement('style');
+ style.id = 'magnifyglass-toast-style';
+ style.textContent = `
+ @keyframes slideIn {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+ }
+ `;
+ document.head.appendChild(style);
+ }
+
+ document.body.appendChild(toast);
+
+ // Auto-dismiss after 15 seconds
+ setTimeout(() => {
+ if (toast.parentElement) {
+ toast.style.animation = 'slideIn 0.3s ease-out reverse';
+ setTimeout(() => toast.remove(), 300);
+ }
+ }, 15000);
+ }
}
diff --git a/src/magnify_info_panel.ts b/src/magnify_info_panel.ts
index 956d05a..2737b43 100644
--- a/src/magnify_info_panel.ts
+++ b/src/magnify_info_panel.ts
@@ -42,13 +42,7 @@ function initializeInfoPanel() {
// Expose orchestrator globally if needed
window.infoPanelManager = infoPanel as any;
- // Load CSS
- const link = document.createElement("link");
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = "extensions/comfyui-magnifyglass/style.css";
- document.head.appendChild(link);
- console.log("ComfyUI Magnify Info Panel: CSS loaded");
+ // CSS is loaded by UIManager.injectStyles() - no need to load separately
console.log("ComfyUI Magnify Info Panel: Extension initialized");
} catch (e) {
diff --git a/src/types/comfyui.d.ts b/src/types/comfyui.d.ts
index d576663..7cc8959 100644
--- a/src/types/comfyui.d.ts
+++ b/src/types/comfyui.d.ts
@@ -196,6 +196,7 @@ export interface ComfyCanvas {
declare global {
interface Window {
comfyUIMagnifyGlass?: MagnifyGlassInstance;
+ magnifyGlass?: MagnifyGlassInstance; // Alias used by info panel controls
comfyUIMagnifyGlassExtensions?: unknown[];
infoPanelManager?: { uiManager?: { updateTheme: (theme: string) => void } };
}
diff --git a/web/info-panel.css b/web/info-panel.css
index db64636..2307d0d 100644
--- a/web/info-panel.css
+++ b/web/info-panel.css
@@ -171,7 +171,8 @@
.floating-controls {
position: fixed;
- display: flex;
+ 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);
diff --git a/web/info-panel/PositionManager.js b/web/info-panel/PositionManager.js
index 9816245..8baf882 100644
--- a/web/info-panel/PositionManager.js
+++ b/web/info-panel/PositionManager.js
@@ -115,6 +115,8 @@ class PositionManager {
top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));
controlsElement.style.left = `${left}px`;
controlsElement.style.top = `${top}px`;
+ controlsElement.style.display = "flex";
+ controlsElement.style.visibility = "visible";
}
}
export {
diff --git a/web/info-panel/PositionManager.js.map b/web/info-panel/PositionManager.js.map
index 6c5b473..229ddc3 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 const panelWidth = this.panelElement.offsetWidth;\n const panelHeight = this.panelElement.offsetHeight;\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 = settings[\"🔍MagnifyGlass.InfoPanelWidth\"];\n const panelHeight = this.panelElement.offsetHeight; // Use actual height\n\n let left: number;\n let top: number;\n const margin = 15;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);\n top = magnifyGlass.lastKnownMousePosition.y - 20;\n } else {\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n if (glassRect) {\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 {\n left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);\n top = magnifyGlass.lastKnownMousePosition.y - 20;\n }\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 || !this.panelElement) return;\n\n const panelRect = this.panelElement.getBoundingClientRect();\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 = panelRect.left - controlsElement.offsetWidth - margin;\n top = panelRect.top;\n break;\n case \"right\":\n left = panelRect.right + margin;\n top = panelRect.top;\n break;\n case \"top\":\n left = panelRect.left;\n top = panelRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = panelRect.left;\n top = panelRect.bottom + margin;\n break;\n case \"top-right\":\n left = panelRect.right - controlsElement.offsetWidth;\n top = panelRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = panelRect.right + margin;\n top = panelRect.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}\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 const panelWidth = this.panelElement.offsetWidth;\n const panelHeight = this.panelElement.offsetHeight;\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 = settings[\"🔍MagnifyGlass.InfoPanelWidth\"];\n const panelHeight = this.panelElement.offsetHeight; // Use actual height\n\n let left: number;\n let top: number;\n const margin = 15;\n\n if (!this.stateManager.state.isGlassPreviewVisible) {\n left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);\n top = magnifyGlass.lastKnownMousePosition.y - 20;\n } else {\n const glassRect = magnifyGlass.ui.glassDiv?.getBoundingClientRect();\n if (glassRect) {\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 {\n left = magnifyGlass.lastKnownMousePosition.x - (panelWidth / 2);\n top = magnifyGlass.lastKnownMousePosition.y - 20;\n }\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 || !this.panelElement) return;\n\n const panelRect = this.panelElement.getBoundingClientRect();\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 = panelRect.left - controlsElement.offsetWidth - margin;\n top = panelRect.top;\n break;\n case \"right\":\n left = panelRect.right + margin;\n top = panelRect.top;\n break;\n case \"top\":\n left = panelRect.left;\n top = panelRect.top - controlsElement.offsetHeight - margin;\n break;\n case \"bottom\":\n left = panelRect.left;\n top = panelRect.bottom + margin;\n break;\n case \"top-right\":\n left = panelRect.right - controlsElement.offsetWidth;\n top = panelRect.top - controlsElement.offsetHeight - margin;\n break;\n default:\n left = panelRect.right + margin;\n top = panelRect.top;\n }\n\n // Keep controls within viewport\n left = Math.max(10, Math.min(left, window.innerWidth - controlsElement.offsetWidth - 10));\n top = Math.max(10, Math.min(top, window.innerHeight - controlsElement.offsetHeight - 10));\n\n controlsElement.style.left = `${left}px`;\n controlsElement.style.top = `${top}px`;\n\n // Show controls now that they're positioned (initially hidden off-screen)\n controlsElement.style.display = 'flex';\n controlsElement.style.visibility = 'visible';\n }\n}\n"],"names":[],"mappings":";;;AAOO,MAAM,gBAAgB;AAAA,EAIzB,YAAY,cAA4B,cAA2B;AAHnE;AACA;AAGI,SAAK,eAAe;AACpB,SAAK,eAAe;AAAA,EACxB;AAAA,EAEA,gBAAsB;AAClB,QAAI,CAAC,KAAK,aAAc;AAGxB,QAAI,KAAK,aAAa,MAAM,eAAe;AACvC,WAAK,oBAAA;AACL;AAAA,IACJ;AAGA,SAAK,wBAAA;AAAA,EACT;AAAA,EAEA,sBAA4B;AACxB,QAAI,CAAC,KAAK,aAAc;AAExB,QAAI,EAAE,GAAG,EAAA,IAAM,KAAK,aAAa,MAAM;AACvC,UAAM,aAAa,KAAK,aAAa;AACrC,UAAM,cAAc,KAAK,aAAa;AACtC,UAAM,SAAS;AAEf,UAAM,WAAW,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,aAAa,aAAa,MAAM,CAAC;AACtF,UAAM,WAAW,KAAK,IAAI,QAAQ,KAAK,IAAI,GAAG,OAAO,cAAc,cAAc,MAAM,CAAC;AAExF,SAAK,aAAa,MAAM,OAAO,GAAG,QAAQ;AAC1C,SAAK,aAAa,MAAM,MAAM,GAAG,QAAQ;AAAA,EAC7C;AAAA,EAEA,0BAAgC;AArC7B;AAsCC,UAAM,eAAe,OAAO;AAC5B,QAAI,CAAC,gBAAgB,CAAC,KAAK,aAAc;AAEzC,UAAM,WAAW,KAAK,aAAa,MAAM;AACzC,UAAM,aAAa,SAAS,+BAA+B;AAC3D,UAAM,cAAc,KAAK,aAAa;AAEtC,QAAI;AACJ,QAAI;AACJ,UAAM,SAAS;AAEf,QAAI,CAAC,KAAK,aAAa,MAAM,uBAAuB;AAChD,aAAO,aAAa,uBAAuB,IAAK,aAAa;AAC7D,YAAM,aAAa,uBAAuB,IAAI;AAAA,IAClD,OAAO;AACH,YAAM,aAAY,kBAAa,GAAG,aAAhB,mBAA0B;AAC5C,UAAI,WAAW;AACX,cAAM,WAAW,SAAS,kCAAkC;AAC5D,gBAAQ,UAAA;AAAA,UACJ,KAAK;AACD,mBAAO,UAAU,QAAQ;AACzB,kBAAM,UAAU;AAChB;AAAA,UACJ,KAAK;AACD,mBAAO,UAAU,OAAO,aAAa;AACrC,kBAAM,UAAU;AAChB;AAAA,UACJ,KAAK;AACD,mBAAO,UAAU;AACjB,kBAAM,UAAU,MAAM,cAAc;AACpC;AAAA,UACJ,KAAK;AACD,mBAAO,UAAU;AACjB,kBAAM,UAAU,SAAS;AACzB;AAAA,UACJ;AACI,mBAAO,UAAU,QAAQ;AACzB,kBAAM,UAAU;AAChB;AAAA,QAAA;AAAA,MAEZ,OAAO;AACH,eAAO,aAAa,uBAAuB,IAAK,aAAa;AAC7D,cAAM,aAAa,uBAAuB,IAAI;AAAA,MAClD;AAAA,IACJ;AAEA,WAAO,KAAK,IAAI,IAAI,KAAK,IAAI,MAAM,OAAO,aAAa,aAAa,EAAE,CAAC;AACvE,UAAM,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,OAAO,cAAc,cAAc,EAAE,CAAC;AAEvE,SAAK,aAAa,MAAM,OAAO,GAAG,IAAI;AACtC,SAAK,aAAa,MAAM,MAAM,GAAG,GAAG;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,yBAAyB,iBAA2C;AAChE,QAAI,CAAC,mBAAmB,CAAC,KAAK,aAAc;AAE5C,UAAM,YAAY,KAAK,aAAa,sBAAA;AACpC,UAAM,mBAAmB,KAAK,aAAa,MAAM,SAAS,iCAAiC,KAAK;AAChG,UAAM,SAAS;AAEf,QAAI;AACJ,QAAI;AAEJ,YAAQ,kBAAA;AAAA,MACJ,KAAK;AACD,eAAO,UAAU,OAAO,gBAAgB,cAAc;AACtD,cAAM,UAAU;AAChB;AAAA,MACJ,KAAK;AACD,eAAO,UAAU,QAAQ;AACzB,cAAM,UAAU;AAChB;AAAA,MACJ,KAAK;AACD,eAAO,UAAU;AACjB,cAAM,UAAU,MAAM,gBAAgB,eAAe;AACrD;AAAA,MACJ,KAAK;AACD,eAAO,UAAU;AACjB,cAAM,UAAU,SAAS;AACzB;AAAA,MACJ,KAAK;AACD,eAAO,UAAU,QAAQ,gBAAgB;AACzC,cAAM,UAAU,MAAM,gBAAgB,eAAe;AACrD;AAAA,MACJ;AACI,eAAO,UAAU,QAAQ;AACzB,cAAM,UAAU;AAAA,IAAA;AAIxB,WAAO,KAAK,IAAI,IAAI,KAAK,IAAI,MAAM,OAAO,aAAa,gBAAgB,cAAc,EAAE,CAAC;AACxF,UAAM,KAAK,IAAI,IAAI,KAAK,IAAI,KAAK,OAAO,cAAc,gBAAgB,eAAe,EAAE,CAAC;AAExF,oBAAgB,MAAM,OAAO,GAAG,IAAI;AACpC,oBAAgB,MAAM,MAAM,GAAG,GAAG;AAGlC,oBAAgB,MAAM,UAAU;AAChC,oBAAgB,MAAM,aAAa;AAAA,EACvC;AACJ;"}
\ No newline at end of file
diff --git a/web/info-panel/UIManager.js b/web/info-panel/UIManager.js
index 755c07f..fd949f6 100644
--- a/web/info-panel/UIManager.js
+++ b/web/info-panel/UIManager.js
@@ -85,6 +85,10 @@ class UIManager {
${Icons.magnifyGlass}
`;
document.body.appendChild(this.elements.controls);
+ this.elements.controls.style.display = "none";
+ this.elements.controls.style.visibility = "hidden";
+ this.elements.controls.style.left = "-9999px";
+ this.elements.controls.style.top = "-9999px";
this.elements.controls.addEventListener("click", (e) => {
const target = e.target;
const button = target.closest("button[data-action]");
@@ -213,9 +217,6 @@ class UIManager {
if (!this.elements.panel) return;
if (this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelEnabled"]) {
this.elements.panel.style.display = "block";
- if (this.elements.controls) {
- this.elements.controls.style.display = "flex";
- }
const opacityPercent = this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelOpacity"];
this.elements.panel.style.opacity = (opacityPercent / 100).toString();
this.elements.panel.offsetHeight;
diff --git a/web/info-panel/UIManager.js.map b/web/info-panel/UIManager.js.map
index 26da71b..9da97b3 100644
--- a/web/info-panel/UIManager.js.map
+++ b/web/info-panel/UIManager.js.map
@@ -1 +1 @@
-{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\n// import { Z_INDEX } from '../shared/constants'; // Not used in original code? Kept for reference if needed\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\n\ninterface InfoPanelElements {\n panel: HTMLDivElement | null;\n header: HTMLDivElement | null;\n content: HTMLDivElement | null;\n controls: HTMLDivElement | null;\n}\n\n/**\n * UI Manager class.\n * Handles all DOM manipulation and UI creation.\n */\nexport class UIManager {\n stateManager: StateManager;\n elements: InfoPanelElements;\n\n constructor(stateManager: StateManager) {\n this.stateManager = stateManager;\n this.elements = {\n panel: null,\n header: null,\n content: null,\n controls: null\n };\n\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = \"comfyui-magnify-info-panel-pro-v2\";\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n \n \n `;\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 this.elements.controls.innerHTML = `\n ${Icons.unlock} \n ${Icons.pin} \n ${Icons.eye} \n ${Icons.magnifyGlass} \n `;\n\n // Insert before the panel in the document body, not as a child\n document.body.appendChild(this.elements.controls);\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 console.log(`[InfoPanel] 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 this.updateControlStates();\n // Toggle glass visibility on the magnify glass\n if (window.magnifyGlass) {\n if (this.stateManager.state.isGlassPreviewVisible) {\n window.magnifyGlass.ui.show();\n } else {\n window.magnifyGlass.ui.hide();\n }\n }\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\";\n this.updateControlsLayout(controlsPosition);\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n }\n\n if (lockBtn) {\n // Only show/enable lock button when pinned\n lockBtn.style.display = this.stateManager.state.isPanelPinned ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Upin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n visibilityBtn.classList.toggle('active', this.stateManager.state.isPanelVisible);\n visibilityBtn.title = this.stateManager.state.isPanelVisible ? \"Show Panel\" : \"Hide Panel\";\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', this.stateManager.state.isGlassPreviewVisible);\n glassBtn.title = this.stateManager.state.isGlassPreviewVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n }\n }\n\n /**\n * Update controls layout based on position setting.\n * @param position \n */\n updateControlsLayout(position: string): void {\n if (!this.elements.controls) return;\n\n // Remove existing layout classes\n this.elements.controls.classList.remove('horizontal-layout', 'vertical-layout');\n\n // Add appropriate layout class\n if (['top', 'bottom'].includes(position)) {\n this.elements.controls.classList.add('horizontal-layout');\n } else {\n this.elements.controls.classList.add('vertical-layout');\n }\n }\n\n /**\n * Apply current styles to elements.\n */\n applyStyles(): void {\n if (!this.elements.panel) return;\n\n const settings = this.stateManager.state.settings;\n\n // Apply custom text color via CSS variable if provided\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"];\n if (textColor) {\n // Ensure color has # symbol\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n // Apply custom accent color via CSS variable if provided\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (accentColor) {\n // Ensure color has # symbol\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // Apply opacity setting if panel is visible (convert percentage to decimal)\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n }\n\n this.elements.panel.style.cssText = `\n position: absolute;\n width: ${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px;\n max-height: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}px;\n z-index: 99999;\n display: none;\n opacity: 0;\n transform: translateY(-10px);\n transition: ${settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none'};\n pointer-events: auto;\n user-select: none;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}\n ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''}\n `;\n }\n\n /**\n * Show the panel.\n */\n show(): void {\n if (!this.elements.panel) return;\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n this.elements.panel.style.display = \"block\";\n if (this.elements.controls) {\n this.elements.controls.style.display = \"flex\";\n }\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n if (this.elements.controls) {\n this.elements.controls.style.display = \"none\";\n }\n }\n }, this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 300 : 0);\n this.stateManager.state.isPanelVisible = false;\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"]) {\n const inspectorContent = [\n { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` },\n { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` },\n { label: 'Magnifier Zoom', value: `${info.magnifier.zoomFactor}×` }\n ];\n\n sections.push({\n id: 'inspector',\n icon: 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 checkpointInfo = this.getCheckpointInfo(info.hoveredNode);\n if (checkpointInfo) {\n nodeContent.push({ label: 'Model', value: checkpointInfo });\n }\n\n const imageInfo = this.getImageInfo(info.hoveredNode);\n if (imageInfo) {\n if (typeof imageInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imageInfo });\n } else if (typeof imageInfo === 'object' && imageInfo !== null) {\n const imgInfoAny = imageInfo as any;\n nodeContent.push({ label: 'Image Size', value: `${imgInfoAny.width}×${imgInfoAny.height}` });\n if (imgInfoAny.src) {\n nodeContent.push({ label: 'Image Source', value: imgInfoAny.src });\n }\n }\n }\n\n const textBoxContent = this.getTextBoxContent(info.hoveredNode);\n if (textBoxContent) {\n nodeContent.push({ label: 'Text', value: textBoxContent });\n }\n }\n\n const importantParameters = this.getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: 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
\n
\n ${section.content.map((item: any) => {\n const value = this.formatValue(item.value, item.label);\n const valueClass = this.getValueClass(item.value);\n const valueAttributes = this.getValueAttributes(item.value);\n return `\n
\n ${item.label} \n ${value} \n
`;\n }).join('')}\n
\n
\n
\n `).join('');\n }\n\n /**\n * Update section expansion states.\n */\n updateSectionStates(): void {\n if (!this.elements.content) return;\n\n const sections = this.elements.content.querySelectorAll('.info-section');\n sections.forEach(section => {\n const sectionId = (section as HTMLElement).dataset.section;\n if (!sectionId) return;\n\n const header = section.querySelector('.section-header');\n const content = section.querySelector('.section-content');\n\n if (header && content) {\n if (this.stateManager.state.expandedSections.has(sectionId)) {\n header.classList.add('expanded');\n content.classList.add('expanded');\n } else {\n header.classList.remove('expanded');\n content.classList.remove('expanded');\n }\n }\n });\n }\n\n /**\n * Format a value for display.\n * @param value \n * @param label\n * @returns \n */\n formatValue(value: any, label?: string): string {\n if (value === null || value === undefined) return '';\n\n const str = String(value);\n\n // Show full text for prompts, text content, model names, and file paths\n if (label && (\n label.toLowerCase().includes('text') ||\n label.toLowerCase().includes('prompt') ||\n label.toLowerCase().includes('model') ||\n label.toLowerCase().includes('file') ||\n label.toLowerCase().includes('conditioning') ||\n label.toLowerCase().includes('positive') ||\n label.toLowerCase().includes('negative')\n )) {\n return str;\n }\n\n // Show full text for very long values (no truncation)\n return str;\n }\n\n /**\n * Get value class for styling.\n * @param value \n * @returns \n */\n getValueClass(value: any): string {\n if (!value) return '';\n\n const str = String(value);\n let classes: string[] = [];\n\n // Mark text that might benefit from special styling for readability\n if (str.length > 100) {\n classes.push('long-text');\n }\n\n return classes.join(' ');\n }\n\n /**\n * Get value attributes.\n * @param value \n * @returns \n */\n getValueAttributes(value: any): string {\n // Since we show full text now, we don't need title attributes for long text\n // Only add title for very long text that might benefit from tooltips\n if (!value) return '';\n\n const str = String(value);\n if (str.length > 500) { // Only for extremely long text\n return `title=\"${str.replace(/\"/g, '"')}\"`;\n }\n\n return '';\n }\n\n /**\n * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`;\n subtitleElement.style.color = accentColor || '';\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName}`;\n subtitleElement.style.color = accentColor || '';\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type}`;\n subtitleElement.style.color = accentColor || '';\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n // ============== Helper methods for node information extraction ==============\n\n /**\n * Get checkpoint/model info from a node.\n * @param nodeInfo \n * @returns \n */\n getCheckpointInfo(nodeInfo: any): string | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes(\"CheckpointLoader\") ||\n nodeInfo.type.includes(\"LoadCheckpoint\") ||\n nodeInfo.type.includes(\"ModelLoader\") ||\n nodeInfo.type.includes(\"UNETLoader\") ||\n nodeInfo.type.includes(\"VAELoader\") ||\n nodeInfo.type.includes(\"LoraLoader\")\n )) {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes(\"model\") ||\n widget.name.toLowerCase().includes(\"checkpoint\") ||\n widget.name.toLowerCase().includes(\"ckpt\") ||\n widget.name.toLowerCase().includes(\"lora\") ||\n widget.name.toLowerCase().includes(\"vae\") ||\n widget.name.toLowerCase().includes(\"file\")\n )) {\n const value = String(widget.value);\n const filename = value.split(/[\\/\\\\]/).pop();\n return filename || value;\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Get image info from a node.\n * @param nodeInfo \n * @returns \n */\n getImageInfo(nodeInfo: any): string | object | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes(\"SaveImage\") ||\n nodeInfo.type.includes(\"PreviewImage\") ||\n nodeInfo.type.includes(\"VisionOutput\") ||\n nodeInfo.type.includes(\"ImageOutput\") ||\n nodeInfo.type.includes(\"LoadImage\") ||\n nodeInfo.type.includes(\"Display\")\n )) {\n if (nodeInfo.widgets) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes(\"image\") ||\n widget.name.toLowerCase().includes(\"filename\") ||\n widget.name.toLowerCase().includes(\"file\")\n )) {\n return widget.value;\n }\n }\n }\n\n if (nodeInfo.properties && nodeInfo.properties.img) {\n const img = nodeInfo.properties.img;\n return {\n width: img.width || \"unknown\",\n height: img.height || \"unknown\",\n src: img.src ? img.src.split(/[\\/\\\\]/).pop() : \"Preview available\"\n };\n }\n\n if (nodeInfo.outputs) {\n for (const output of nodeInfo.outputs) {\n if (output.links && output.links.length > 0) {\n return \"Image connected to \" + output.links.length + \" node(s)\";\n }\n }\n }\n\n return \"Image node\";\n }\n return null;\n }\n\n /**\n * Get text box content from a node.\n * @param nodeInfo \n * @returns \n */\n getTextBoxContent(nodeInfo: any): string | null {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n if (nodeInfo.type && nodeInfo.type.includes(\"CLIPTextEncode\")) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name === \"text\" && typeof widget.value === 'string') {\n return widget.value;\n }\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.name.toLowerCase().includes(\"prompt\") ||\n widget.name.toLowerCase().includes(\"conditioning\")) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n return widget.value;\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.type === 'text' || widget.type === 'textarea' ||\n widget.type === 'string' || widget.name.toLowerCase().includes('text')) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n\n // Show full text without truncation\n return widget.value;\n }\n }\n }\n return null;\n }\n\n /**\n * Get important node parameters based on node type.\n * @param nodeInfo \n * @returns \n */\n getImportantNodeParameters(nodeInfo: any): any[] {\n const parameters: any[] = [];\n\n // For complex nodes that have many parameters, show ALL widgets\n const nodeType = nodeInfo.type ? nodeInfo.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = nodeInfo.type && (\n nodeType.includes('ksampler') ||\n nodeType.includes('sampler') ||\n nodeType.includes('k_samplers') ||\n nodeType.includes('checkpoint') ||\n nodeType.includes('model') ||\n nodeType.includes('lora') ||\n nodeType.includes('controlnet') ||\n nodeType.includes('advanced') ||\n nodeType.includes('detailer') ||\n nodeType.includes('inpaint') ||\n nodeType.includes('upscale') ||\n nodeType.includes('clip') ||\n nodeType.includes('text') ||\n nodeType.includes('encode')\n ) && !isSaveNode;\n\n if (showAllWidgets) {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n // Show all widgets for complex nodes, but filter out some duplicates\n if (widget.name && widget.name !== '') {\n const widgetName = widget.name.toLowerCase();\n\n // Skip some generic widget names that might be duplicates\n if (widgetName.includes('title') ||\n widgetName === 'node' ||\n widgetName === 'id' ||\n widgetName === 'type' ||\n widgetName === 'mode') {\n continue;\n }\n\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n return parameters;\n }\n\n // Special handling for Save nodes - only show essential parameters\n let importantParams: string[];\n if (isSaveNode) {\n importantParams = [\n 'filename_prefix', 'filename', 'directory', 'path',\n 'format', 'quality', 'extension'\n ];\n } else {\n // For other nodes, use the filtered list but avoid duplicates with specific extractions\n importantParams = [\n 'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',\n 'positive', 'negative', 'width', 'height', 'denoise', 'strength',\n 'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',\n // KSampler specific parameters (for other samplers)\n 'control_after_generate', 'control', 'after', 'generate',\n 'start_at_step', 'end_at_step', 'start', 'end',\n 'return_with_leftover_noise', 'leftover', 'noise_return',\n // Additional common parameters (but avoid duplicates with specific extractors)\n 'model', 'vae', 'clip', 'lora', 'checkpoint',\n 'latent', 'image', 'mask', 'filename', 'directory',\n 'prompt', 'conditioning', 'filename_prefix',\n // New detection vocabulary\n 'resolution', 'num_chunks', 'seconds', 'aspect_ratio',\n 'style_type', 'background', 'n', 'human', 'raw', 'guidance',\n 'skip_preprocessing', 'movement_amplitude', 'animation',\n 'material_type', 'b1', 'b2', 's1', 's2', 'type', 'channel',\n 'sigma', 'rho',\n // Additional detection vocabulary\n 'alpha', 'base_shift', 'shift', 'stretch', 'terminal',\n 'spacing', 'style', 'eta', 'norm_threshold', 'momentum',\n 'hypernetwork_name', 'reuse_threshold', 'verbose', 'layers',\n 'set_cond_area', 'audioui',\n // Camera and 3D parameters\n 'camera_pose', 'fx', 'cx', 'fy', 'cy'\n // Note: 'text' and 'string' removed to avoid duplication with getTextBoxContent\n ];\n }\n\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n const paramName = widget.name.toLowerCase();\n if (importantParams.some(param => paramName.includes(param))) {\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n\n return parameters;\n }\n\n /**\n * Format widget value for display.\n * @param value \n * @returns \n */\n formatWidgetValue(value: any): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"string\") {\n return value; // Show full text without truncation\n }\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? value.toString() : value.toFixed(3);\n }\n if (typeof value === \"boolean\") return value.toString();\n if (Array.isArray(value)) {\n return `Array(${value.length})`;\n }\n if (typeof value === \"object\") {\n return \"Object\";\n }\n return String(value);\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"UIManager.js","sources":["../../src/info-panel/UIManager.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - Info Panel UI Manager (TypeScript)\n * \n * Complete UI Manager extracted from magnify_info_panel.js\n * Handles all DOM manipulation and UI creation for the info panel.\n */\n\n// import { Z_INDEX } from '../shared/constants'; // Not used in original code? Kept for reference if needed\nimport { StateManager } from './StateManager';\nimport { Icons } from '../shared/icons';\n\ninterface InfoPanelElements {\n panel: HTMLDivElement | null;\n header: HTMLDivElement | null;\n content: HTMLDivElement | null;\n controls: HTMLDivElement | null;\n}\n\n/**\n * UI Manager class.\n * Handles all DOM manipulation and UI creation.\n */\nexport class UIManager {\n stateManager: StateManager;\n elements: InfoPanelElements;\n\n constructor(stateManager: StateManager) {\n this.stateManager = stateManager;\n this.elements = {\n panel: null,\n header: null,\n content: null,\n controls: null\n };\n\n this.createPanel();\n this.injectStyles();\n }\n\n /**\n * Create the main panel and its components.\n */\n createPanel(): void {\n // Main panel container\n this.elements.panel = document.createElement(\"div\");\n this.elements.panel.id = \"comfyui-magnify-info-panel-pro-v2\";\n this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;\n\n // Header\n this.elements.header = document.createElement(\"div\");\n this.elements.header.className = \"panel-header\";\n this.elements.header.innerHTML = `\n \n \n `;\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 this.elements.controls.innerHTML = `\n ${Icons.unlock} \n ${Icons.pin} \n ${Icons.eye} \n ${Icons.magnifyGlass} \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 console.log(`[InfoPanel] 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 this.updateControlStates();\n // Toggle glass visibility on the magnify glass\n if (window.magnifyGlass) {\n if (this.stateManager.state.isGlassPreviewVisible) {\n window.magnifyGlass.ui.show();\n } else {\n window.magnifyGlass.ui.hide();\n }\n }\n break;\n }\n });\n\n // Set initial layout based on settings\n const controlsPosition = this.stateManager.state.settings[\"🔍MagnifyGlass.ControlsPosition\"] || \"top-right\";\n this.updateControlsLayout(controlsPosition);\n }\n\n /**\n * Update control button states.\n */\n updateControlStates(): void {\n if (!this.elements.controls) return;\n\n const pinBtn = this.elements.controls.querySelector('[data-action=\"pin\"]') as HTMLButtonElement;\n const lockBtn = this.elements.controls.querySelector('[data-action=\"lock\"]') as HTMLButtonElement;\n const visibilityBtn = this.elements.controls.querySelector('[data-action=\"toggle-panel\"]') as HTMLButtonElement;\n const glassBtn = this.elements.controls.querySelector('[data-action=\"toggle-glass\"]') as HTMLButtonElement;\n\n if (pinBtn) {\n pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);\n pinBtn.title = this.stateManager.state.isPanelPinned ? \"Lock Panel\" : \"Unlock Panel\";\n pinBtn.innerHTML = this.stateManager.state.isPanelPinned ? Icons.lock : Icons.unlock;\n }\n\n if (lockBtn) {\n // Only show/enable lock button when pinned\n lockBtn.style.display = this.stateManager.state.isPanelPinned ? 'flex' : 'none';\n lockBtn.classList.toggle('active', this.stateManager.state.isPanelLocked);\n lockBtn.title = this.stateManager.state.isPanelLocked ? \"Upin Panel Position\" : \"Pin Panel Position\";\n lockBtn.disabled = !this.stateManager.state.isPanelPinned;\n }\n\n if (visibilityBtn) {\n visibilityBtn.classList.toggle('active', this.stateManager.state.isPanelVisible);\n visibilityBtn.title = this.stateManager.state.isPanelVisible ? \"Show Panel\" : \"Hide Panel\";\n }\n\n if (glassBtn) {\n glassBtn.classList.toggle('active', this.stateManager.state.isGlassPreviewVisible);\n glassBtn.title = this.stateManager.state.isGlassPreviewVisible ? \"Hide Glass Preview\" : \"Show Glass Preview\";\n }\n }\n\n /**\n * Update controls layout based on position setting.\n * @param position \n */\n updateControlsLayout(position: string): void {\n if (!this.elements.controls) return;\n\n // Remove existing layout classes\n this.elements.controls.classList.remove('horizontal-layout', 'vertical-layout');\n\n // Add appropriate layout class\n if (['top', 'bottom'].includes(position)) {\n this.elements.controls.classList.add('horizontal-layout');\n } else {\n this.elements.controls.classList.add('vertical-layout');\n }\n }\n\n /**\n * Apply current styles to elements.\n */\n applyStyles(): void {\n if (!this.elements.panel) return;\n\n const settings = this.stateManager.state.settings;\n\n // Apply custom text color via CSS variable if provided\n const textColor = settings[\"🔍MagnifyGlass.InfoPanelTextColor\"];\n if (textColor) {\n // Ensure color has # symbol\n const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;\n this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);\n }\n\n // Apply custom accent color via CSS variable if provided\n const accentColor = settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (accentColor) {\n // Ensure color has # symbol\n const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;\n this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);\n }\n\n // Apply opacity setting if panel is visible (convert percentage to decimal)\n if (this.stateManager.state.isPanelVisible) {\n const opacityPercent = settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n }\n\n this.elements.panel.style.cssText = `\n position: absolute;\n width: ${settings[\"🔍MagnifyGlass.InfoPanelWidth\"]}px;\n max-height: ${settings[\"🔍MagnifyGlass.InfoPanelMaxHeight\"]}px;\n z-index: 99999;\n display: none;\n opacity: 0;\n transform: translateY(-10px);\n transition: ${settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)' : 'none'};\n pointer-events: auto;\n user-select: none;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}\n ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''}\n `;\n }\n\n /**\n * Show the panel.\n */\n show(): void {\n if (!this.elements.panel) return;\n\n if (this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelEnabled\"]) {\n this.elements.panel.style.display = \"block\";\n // Controls are shown by positionFloatingControls() after proper positioning\n // Apply user's opacity setting when showing (convert percentage to decimal)\n const opacityPercent = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelOpacity\"];\n this.elements.panel.style.opacity = (opacityPercent / 100).toString();\n // Trigger reflow\n this.elements.panel.offsetHeight;\n this.elements.panel.classList.add('visible');\n this.stateManager.state.isPanelVisible = true;\n }\n }\n\n /**\n * Hide the panel.\n */\n hide(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.remove('visible');\n setTimeout(() => {\n if (!this.stateManager.state.isPanelVisible && this.elements.panel) {\n this.elements.panel.style.display = \"none\";\n if (this.elements.controls) {\n this.elements.controls.style.display = \"none\";\n }\n }\n }, this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAnimations\"] ? 300 : 0);\n this.stateManager.state.isPanelVisible = false;\n }\n\n /**\n * Update minimized state.\n */\n updateMinimizedState(): void {\n if (!this.elements.panel || !this.elements.header) return;\n\n this.elements.panel.classList.toggle('panel-minimized', this.stateManager.state.isPanelMinimized);\n const minimizeBtn = this.elements.header.querySelector('.minimize-btn');\n if (minimizeBtn) {\n minimizeBtn.textContent = this.stateManager.state.isPanelMinimized ? '+' : '−';\n (minimizeBtn as HTMLElement).title = this.stateManager.state.isPanelMinimized ? 'Expand Panel' : 'Minimize Panel';\n }\n }\n\n /**\n * Update pinned state.\n */\n updatePinnedState(): void {\n if (!this.elements.panel) return;\n\n this.elements.panel.classList.toggle('panel-pinned', this.stateManager.state.isPanelPinned);\n this.elements.panel.classList.toggle('panel-locked', this.stateManager.state.isPanelLocked);\n this.updateControlStates();\n }\n\n /**\n * Update theme class.\n * @param newTheme \n */\n updateTheme(newTheme: string): void {\n if (this.elements.panel) {\n this.elements.panel.className = this.elements.panel.className.replace(/theme-\\w+/, `theme-${newTheme.toLowerCase()}`);\n }\n }\n\n /**\n * Display information in the panel.\n * @param info \n */\n displayInfo(info: any): void {\n const sections = this.buildSections(info);\n this.renderSections(sections);\n this.updateSectionStates();\n this.updateHeaderSubtitle(info);\n }\n\n /**\n * Build section data from info object.\n * @param info \n * @returns \n */\n buildSections(info: any): any[] {\n const sections: any[] = [];\n const settings = this.stateManager.state.settings;\n\n // Inspector section\n if (settings[\"🔍MagnifyGlass.ShowInspectorTab\"]) {\n const inspectorContent = [\n { label: 'Cursor Canvas', value: `(${Math.round(info.cursor.canvas.x)}, ${Math.round(info.cursor.canvas.y)})` },\n { label: 'Canvas Scale', value: `${(info.canvas.scale * 100).toFixed(1)}%` },\n { label: 'Magnifier Zoom', value: `${info.magnifier.zoomFactor}×` }\n ];\n\n sections.push({\n id: 'inspector',\n icon: 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 checkpointInfo = this.getCheckpointInfo(info.hoveredNode);\n if (checkpointInfo) {\n nodeContent.push({ label: 'Model', value: checkpointInfo });\n }\n\n const imageInfo = this.getImageInfo(info.hoveredNode);\n if (imageInfo) {\n if (typeof imageInfo === 'string') {\n nodeContent.push({ label: 'Image', value: imageInfo });\n } else if (typeof imageInfo === 'object' && imageInfo !== null) {\n const imgInfoAny = imageInfo as any;\n nodeContent.push({ label: 'Image Size', value: `${imgInfoAny.width}×${imgInfoAny.height}` });\n if (imgInfoAny.src) {\n nodeContent.push({ label: 'Image Source', value: imgInfoAny.src });\n }\n }\n }\n\n const textBoxContent = this.getTextBoxContent(info.hoveredNode);\n if (textBoxContent) {\n nodeContent.push({ label: 'Text', value: textBoxContent });\n }\n }\n\n const importantParameters = this.getImportantNodeParameters(info.hoveredNode);\n nodeContent.push(...importantParameters);\n\n sections.push({\n id: 'node',\n icon: 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
\n
\n ${section.content.map((item: any) => {\n const value = this.formatValue(item.value, item.label);\n const valueClass = this.getValueClass(item.value);\n const valueAttributes = this.getValueAttributes(item.value);\n return `\n
\n ${item.label} \n ${value} \n
`;\n }).join('')}\n
\n
\n
\n `).join('');\n }\n\n /**\n * Update section expansion states.\n */\n updateSectionStates(): void {\n if (!this.elements.content) return;\n\n const sections = this.elements.content.querySelectorAll('.info-section');\n sections.forEach(section => {\n const sectionId = (section as HTMLElement).dataset.section;\n if (!sectionId) return;\n\n const header = section.querySelector('.section-header');\n const content = section.querySelector('.section-content');\n\n if (header && content) {\n if (this.stateManager.state.expandedSections.has(sectionId)) {\n header.classList.add('expanded');\n content.classList.add('expanded');\n } else {\n header.classList.remove('expanded');\n content.classList.remove('expanded');\n }\n }\n });\n }\n\n /**\n * Format a value for display.\n * @param value \n * @param label\n * @returns \n */\n formatValue(value: any, label?: string): string {\n if (value === null || value === undefined) return '';\n\n const str = String(value);\n\n // Show full text for prompts, text content, model names, and file paths\n if (label && (\n label.toLowerCase().includes('text') ||\n label.toLowerCase().includes('prompt') ||\n label.toLowerCase().includes('model') ||\n label.toLowerCase().includes('file') ||\n label.toLowerCase().includes('conditioning') ||\n label.toLowerCase().includes('positive') ||\n label.toLowerCase().includes('negative')\n )) {\n return str;\n }\n\n // Show full text for very long values (no truncation)\n return str;\n }\n\n /**\n * Get value class for styling.\n * @param value \n * @returns \n */\n getValueClass(value: any): string {\n if (!value) return '';\n\n const str = String(value);\n let classes: string[] = [];\n\n // Mark text that might benefit from special styling for readability\n if (str.length > 100) {\n classes.push('long-text');\n }\n\n return classes.join(' ');\n }\n\n /**\n * Get value attributes.\n * @param value \n * @returns \n */\n getValueAttributes(value: any): string {\n // Since we show full text now, we don't need title attributes for long text\n // Only add title for very long text that might benefit from tooltips\n if (!value) return '';\n\n const str = String(value);\n if (str.length > 500) { // Only for extremely long text\n return `title=\"${str.replace(/\"/g, '"')}\"`;\n }\n\n return '';\n }\n\n /**\n * Update header subtitle.\n * @param info \n */\n updateHeaderSubtitle(info: any): void {\n if (!this.elements.header) return;\n\n const subtitleElement = this.elements.header.querySelector('.header-subtitle') as HTMLElement;\n if (subtitleElement) {\n const accentColor = this.stateManager.state.settings[\"🔍MagnifyGlass.InfoPanelAccentColor\"];\n if (info.hoveredNode) {\n subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`;\n subtitleElement.style.color = accentColor || '';\n } else if (info.media) {\n subtitleElement.textContent = `Media: ${info.media.tagName}`;\n subtitleElement.style.color = accentColor || '';\n } else if (info.connection) {\n subtitleElement.textContent = `Connection: ${info.connection.type}`;\n subtitleElement.style.color = accentColor || '';\n } else {\n subtitleElement.textContent = 'Real-time analysis';\n subtitleElement.style.color = '';\n }\n }\n }\n\n // ============== Helper methods for node information extraction ==============\n\n /**\n * Get checkpoint/model info from a node.\n * @param nodeInfo \n * @returns \n */\n getCheckpointInfo(nodeInfo: any): string | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes(\"CheckpointLoader\") ||\n nodeInfo.type.includes(\"LoadCheckpoint\") ||\n nodeInfo.type.includes(\"ModelLoader\") ||\n nodeInfo.type.includes(\"UNETLoader\") ||\n nodeInfo.type.includes(\"VAELoader\") ||\n nodeInfo.type.includes(\"LoraLoader\")\n )) {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes(\"model\") ||\n widget.name.toLowerCase().includes(\"checkpoint\") ||\n widget.name.toLowerCase().includes(\"ckpt\") ||\n widget.name.toLowerCase().includes(\"lora\") ||\n widget.name.toLowerCase().includes(\"vae\") ||\n widget.name.toLowerCase().includes(\"file\")\n )) {\n const value = String(widget.value);\n const filename = value.split(/[\\/\\\\]/).pop();\n return filename || value;\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Get image info from a node.\n * @param nodeInfo \n * @returns \n */\n getImageInfo(nodeInfo: any): string | object | null {\n if (nodeInfo.type && (\n nodeInfo.type.includes(\"SaveImage\") ||\n nodeInfo.type.includes(\"PreviewImage\") ||\n nodeInfo.type.includes(\"VisionOutput\") ||\n nodeInfo.type.includes(\"ImageOutput\") ||\n nodeInfo.type.includes(\"LoadImage\") ||\n nodeInfo.type.includes(\"Display\")\n )) {\n if (nodeInfo.widgets) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name && (\n widget.name.toLowerCase().includes(\"image\") ||\n widget.name.toLowerCase().includes(\"filename\") ||\n widget.name.toLowerCase().includes(\"file\")\n )) {\n return widget.value;\n }\n }\n }\n\n if (nodeInfo.properties && nodeInfo.properties.img) {\n const img = nodeInfo.properties.img;\n return {\n width: img.width || \"unknown\",\n height: img.height || \"unknown\",\n src: img.src ? img.src.split(/[\\/\\\\]/).pop() : \"Preview available\"\n };\n }\n\n if (nodeInfo.outputs) {\n for (const output of nodeInfo.outputs) {\n if (output.links && output.links.length > 0) {\n return \"Image connected to \" + output.links.length + \" node(s)\";\n }\n }\n }\n\n return \"Image node\";\n }\n return null;\n }\n\n /**\n * Get text box content from a node.\n * @param nodeInfo \n * @returns \n */\n getTextBoxContent(nodeInfo: any): string | null {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n if (nodeInfo.type && nodeInfo.type.includes(\"CLIPTextEncode\")) {\n for (const widget of nodeInfo.widgets) {\n if (widget.name === \"text\" && typeof widget.value === 'string') {\n return widget.value;\n }\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.name.toLowerCase().includes(\"prompt\") ||\n widget.name.toLowerCase().includes(\"conditioning\")) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n return widget.value;\n }\n }\n\n for (const widget of nodeInfo.widgets) {\n if ((widget.type === 'text' || widget.type === 'textarea' ||\n widget.type === 'string' || widget.name.toLowerCase().includes('text')) &&\n typeof widget.value === 'string' && widget.value.length > 0) {\n\n // Show full text without truncation\n return widget.value;\n }\n }\n }\n return null;\n }\n\n /**\n * Get important node parameters based on node type.\n * @param nodeInfo \n * @returns \n */\n getImportantNodeParameters(nodeInfo: any): any[] {\n const parameters: any[] = [];\n\n // For complex nodes that have many parameters, show ALL widgets\n const nodeType = nodeInfo.type ? nodeInfo.type.toLowerCase() : '';\n const isSaveNode = nodeType.includes('save') &&\n !nodeType.includes('checkpoint') &&\n !nodeType.includes('model') &&\n !nodeType.includes('preview');\n\n const showAllWidgets = nodeInfo.type && (\n nodeType.includes('ksampler') ||\n nodeType.includes('sampler') ||\n nodeType.includes('k_samplers') ||\n nodeType.includes('checkpoint') ||\n nodeType.includes('model') ||\n nodeType.includes('lora') ||\n nodeType.includes('controlnet') ||\n nodeType.includes('advanced') ||\n nodeType.includes('detailer') ||\n nodeType.includes('inpaint') ||\n nodeType.includes('upscale') ||\n nodeType.includes('clip') ||\n nodeType.includes('text') ||\n nodeType.includes('encode')\n ) && !isSaveNode;\n\n if (showAllWidgets) {\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n // Show all widgets for complex nodes, but filter out some duplicates\n if (widget.name && widget.name !== '') {\n const widgetName = widget.name.toLowerCase();\n\n // Skip some generic widget names that might be duplicates\n if (widgetName.includes('title') ||\n widgetName === 'node' ||\n widgetName === 'id' ||\n widgetName === 'type' ||\n widgetName === 'mode') {\n continue;\n }\n\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n return parameters;\n }\n\n // Special handling for Save nodes - only show essential parameters\n let importantParams: string[];\n if (isSaveNode) {\n importantParams = [\n 'filename_prefix', 'filename', 'directory', 'path',\n 'format', 'quality', 'extension'\n ];\n } else {\n // For other nodes, use the filtered list but avoid duplicates with specific extractions\n importantParams = [\n 'seed', 'steps', 'cfg', 'scale', 'sampler', 'scheduler',\n 'positive', 'negative', 'width', 'height', 'denoise', 'strength',\n 'noise', 'count', 'batch', 'size', 'phase', 'color', 'intensity',\n // KSampler specific parameters (for other samplers)\n 'control_after_generate', 'control', 'after', 'generate',\n 'start_at_step', 'end_at_step', 'start', 'end',\n 'return_with_leftover_noise', 'leftover', 'noise_return',\n // Additional common parameters (but avoid duplicates with specific extractors)\n 'model', 'vae', 'clip', 'lora', 'checkpoint',\n 'latent', 'image', 'mask', 'filename', 'directory',\n 'prompt', 'conditioning', 'filename_prefix',\n // New detection vocabulary\n 'resolution', 'num_chunks', 'seconds', 'aspect_ratio',\n 'style_type', 'background', 'n', 'human', 'raw', 'guidance',\n 'skip_preprocessing', 'movement_amplitude', 'animation',\n 'material_type', 'b1', 'b2', 's1', 's2', 'type', 'channel',\n 'sigma', 'rho',\n // Additional detection vocabulary\n 'alpha', 'base_shift', 'shift', 'stretch', 'terminal',\n 'spacing', 'style', 'eta', 'norm_threshold', 'momentum',\n 'hypernetwork_name', 'reuse_threshold', 'verbose', 'layers',\n 'set_cond_area', 'audioui',\n // Camera and 3D parameters\n 'camera_pose', 'fx', 'cx', 'fy', 'cy'\n // Note: 'text' and 'string' removed to avoid duplication with getTextBoxContent\n ];\n }\n\n if (nodeInfo.widgets && nodeInfo.widgets.length > 0) {\n for (const widget of nodeInfo.widgets) {\n const paramName = widget.name.toLowerCase();\n if (importantParams.some(param => paramName.includes(param))) {\n parameters.push({\n label: widget.name,\n value: this.formatWidgetValue(widget.value)\n });\n }\n }\n }\n\n return parameters;\n }\n\n /**\n * Format widget value for display.\n * @param value \n * @returns \n */\n formatWidgetValue(value: any): string {\n if (value === null) return \"null\";\n if (value === undefined) return \"undefined\";\n if (typeof value === \"string\") {\n return value; // Show full text without truncation\n }\n if (typeof value === \"number\") {\n return Number.isInteger(value) ? value.toString() : value.toFixed(3);\n }\n if (typeof value === \"boolean\") return value.toString();\n if (Array.isArray(value)) {\n return `Array(${value.length})`;\n }\n if (typeof value === \"object\") {\n return \"Object\";\n }\n return String(value);\n }\n\n /**\n * Inject CSS styles by loading external stylesheet.\n */\n injectStyles(): void {\n if (!document.getElementById('magnify-info-panel-styles-v2')) {\n const link = document.createElement('link');\n link.id = 'magnify-info-panel-styles-v2';\n link.rel = 'stylesheet';\n link.type = 'text/css';\n link.href = 'extensions/comfyui-magnifyglass/info-panel.css';\n document.head.appendChild(link);\n }\n }\n\n cleanup(): void {\n if (this.elements.panel && this.elements.panel.parentNode) {\n this.elements.panel.parentNode.removeChild(this.elements.panel);\n }\n if (this.elements.controls && this.elements.controls.parentNode) {\n this.elements.controls.parentNode.removeChild(this.elements.controls);\n }\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/magnify-glass/WebGLRenderer.js b/web/magnify-glass/WebGLRenderer.js
index cf1df80..70007bc 100644
--- a/web/magnify-glass/WebGLRenderer.js
+++ b/web/magnify-glass/WebGLRenderer.js
@@ -72,7 +72,14 @@ class WebGLRenderer {
if (!this.ui.glassCanvas) return;
this.gl = this.ui.glassCanvas.getContext("webgl", { preserveDrawingBuffer: true });
if (!this.gl) {
+ this.gl = this.ui.glassCanvas.getContext("webgl2", { preserveDrawingBuffer: true });
+ }
+ if (!this.gl) {
+ const isFirefox = navigator.userAgent.toLowerCase().includes("firefox");
+ const browserInstructions = isFirefox ? "Firefox: Go to about:config, search for 'webgl.disabled' and set it to 'false', then restart Firefox." : "Check your browser settings to ensure WebGL is enabled.";
console.error("ComfyUI Magnifying Glass ERROR: WebGL not supported or context creation failed.");
+ console.error(`To fix: ${browserInstructions}`);
+ this.showWebGLError(browserInstructions);
return;
}
this.program = this.createShaderProgram(this.gl, this.vertexShaderSource, this.fragmentShaderSource);
@@ -257,6 +264,63 @@ class WebGLRenderer {
this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
}
}
+ /**
+ * Show WebGL error notification to user.
+ * @param instructions - Browser-specific instructions to fix
+ */
+ showWebGLError(instructions) {
+ const toast = document.createElement("div");
+ toast.id = "magnifyglass-webgl-error";
+ toast.style.cssText = `
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ max-width: 400px;
+ padding: 16px 20px;
+ background: linear-gradient(135deg, #ff4444 0%, #cc3333 100%);
+ color: white;
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0,0,0,0.3);
+ font-family: system-ui, -apple-system, sans-serif;
+ font-size: 14px;
+ line-height: 1.5;
+ z-index: 100000;
+ animation: slideIn 0.3s ease-out;
+ `;
+ toast.innerHTML = `
+
+
⚠️
+
+ MagnifyGlass: WebGL Disabled
+ ${instructions}
+
+ Dismiss
+
+
+
+ `;
+ if (!document.getElementById("magnifyglass-toast-style")) {
+ const style = document.createElement("style");
+ style.id = "magnifyglass-toast-style";
+ style.textContent = `
+ @keyframes slideIn {
+ from { transform: translateX(100%); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+ }
+ `;
+ document.head.appendChild(style);
+ }
+ document.body.appendChild(toast);
+ setTimeout(() => {
+ if (toast.parentElement) {
+ toast.style.animation = "slideIn 0.3s ease-out reverse";
+ setTimeout(() => toast.remove(), 300);
+ }
+ }, 15e3);
+ }
}
export {
WebGLRenderer
diff --git a/web/magnify-glass/WebGLRenderer.js.map b/web/magnify-glass/WebGLRenderer.js.map
index e239872..b6e2ec1 100644
--- a/web/magnify-glass/WebGLRenderer.js.map
+++ b/web/magnify-glass/WebGLRenderer.js.map
@@ -1 +1 @@
-{"version":3,"file":"WebGLRenderer.js","sources":["../../src/magnify-glass/WebGLRenderer.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - WebGLRenderer (TypeScript)\n * \n * Handles WebGL-based rendering of the magnified view.\n */\n\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\nimport type { UiManager } from './UiManager';\n\ninterface UniformLocations {\n sourceTexture: WebGLUniformLocation | null;\n textureOffset: WebGLUniformLocation | null;\n textureRepeat: WebGLUniformLocation | null;\n glassSize: WebGLUniformLocation | null;\n}\n\ninterface AttributeLocations {\n position: number;\n texCoord: number;\n}\n\n/**\n * WebGL Renderer class.\n * Provides efficient WebGL-based magnification rendering.\n */\nexport class WebGLRenderer {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n\n gl: WebGLRenderingContext | null;\n program: WebGLProgram | null;\n texture: WebGLTexture | null;\n positionBuffer: WebGLBuffer | null;\n texCoordBuffer: WebGLBuffer | null;\n uniformLocations: UniformLocations | null;\n attributeLocations: AttributeLocations | null;\n currentFilteringMode: number | null;\n\n // Dirty flag optimization\n lastCanvasVersion: number;\n lastSourceX: number;\n lastSourceY: number;\n lastSourceWidth: number;\n lastSourceHeight: number;\n textureUploadSkipCount: number;\n\n vertexShaderSource: string;\n fragmentShaderSource: string;\n\n constructor(config: ConfigManager, state: MagnifierState, ui: UiManager) {\n this.config = config;\n this.state = state;\n this.ui = ui;\n\n this.gl = null;\n this.program = null;\n this.texture = null;\n this.positionBuffer = null;\n this.texCoordBuffer = null;\n this.uniformLocations = null;\n this.attributeLocations = null;\n this.currentFilteringMode = null;\n\n // Initialize dirty flag tracking\n this.lastCanvasVersion = -1;\n this.lastSourceX = -1;\n this.lastSourceY = -1;\n this.lastSourceWidth = -1;\n this.lastSourceHeight = -1;\n this.textureUploadSkipCount = 0;\n\n this.vertexShaderSource = `\n attribute vec2 a_position;\n attribute vec2 a_texCoord;\n varying vec2 v_texCoord;\n void main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texCoord = a_texCoord;\n }\n `;\n\n this.fragmentShaderSource = `\n precision mediump float;\n varying vec2 v_texCoord;\n uniform sampler2D u_sourceTexture;\n uniform vec2 u_textureOffset;\n uniform vec2 u_textureRepeat;\n uniform float u_glassSize;\n\n void main() {\n vec2 sampleCoord = u_textureOffset + v_texCoord * u_textureRepeat;\n vec4 color = texture2D(u_sourceTexture, sampleCoord);\n gl_FragColor = color;\n }\n `;\n\n this.initialize();\n }\n\n /**\n * Initialize WebGL context and resources.\n */\n initialize(): void {\n // Get WebGL context\n if (!this.ui.glassCanvas) return;\n\n this.gl = this.ui.glassCanvas.getContext(\"webgl\", { preserveDrawingBuffer: true });\n if (!this.gl) {\n console.error(\"ComfyUI Magnifying Glass ERROR: WebGL not supported or context creation failed.\");\n return;\n }\n\n // Create shader program\n this.program = this.createShaderProgram(this.gl, this.vertexShaderSource, this.fragmentShaderSource);\n if (!this.program) return;\n\n // Create position buffer\n this.positionBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n const positions = [-1, -1, 1, -1, -1, 1, 1, 1];\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);\n\n // Create texture coordinate buffer\n this.texCoordBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n const texCoords = [0, 1, 1, 1, 0, 0, 1, 0]; // Flipped Y for texCoords\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(texCoords), this.gl.STATIC_DRAW);\n\n // Create texture\n this.texture = this.gl.createTexture();\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);\n this.updateTextureFiltering(this.config.textureFiltering);\n\n // Get uniform and attribute locations\n this.uniformLocations = {\n sourceTexture: this.gl.getUniformLocation(this.program, \"u_sourceTexture\"),\n textureOffset: this.gl.getUniformLocation(this.program, \"u_textureOffset\"),\n textureRepeat: this.gl.getUniformLocation(this.program, \"u_textureRepeat\"),\n glassSize: this.gl.getUniformLocation(this.program, \"u_glassSize\"),\n };\n\n this.attributeLocations = {\n position: this.gl.getAttribLocation(this.program, \"a_position\"),\n texCoord: this.gl.getAttribLocation(this.program, \"a_texCoord\"),\n };\n }\n\n /**\n * Update texture filtering mode.\n * @param filteringModeString - \"Linear\" or \"Nearest\"\n */\n updateTextureFiltering(filteringModeString: string): void {\n if (!this.gl || !this.texture) return;\n\n let glFilterMode: number;\n if (filteringModeString === \"Nearest\") {\n glFilterMode = this.gl.NEAREST;\n } else {\n glFilterMode = this.gl.LINEAR;\n }\n\n if (this.currentFilteringMode === glFilterMode) return;\n\n this.currentFilteringMode = glFilterMode;\n\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, glFilterMode);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, glFilterMode);\n this.gl.bindTexture(this.gl.TEXTURE_2D, null);\n }\n\n /**\n * Check if renderer is valid and ready.\n * @returns Boolean indicating if WebGL context and program are ready\n */\n isValid(): boolean {\n return this.gl !== null && this.program !== null;\n }\n\n /**\n * Create a WebGL shader program.\n * @param gl - WebGL context\n * @param vsSource - Vertex shader source\n * @param fsSource - Fragment shader source\n * @returns WebGL program or null on failure\n */\n createShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string): WebGLProgram | null {\n const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n if (!vertexShader) return null;\n\n gl.shaderSource(vertexShader, vsSource);\n gl.compileShader(vertexShader);\n if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Vertex shader compilation error:', gl.getShaderInfoLog(vertexShader));\n gl.deleteShader(vertexShader);\n return null;\n }\n\n const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n if (!fragmentShader) {\n gl.deleteShader(vertexShader);\n return null;\n }\n\n gl.shaderSource(fragmentShader, fsSource);\n gl.compileShader(fragmentShader);\n if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Fragment shader compilation error:', gl.getShaderInfoLog(fragmentShader));\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n const program = gl.createProgram();\n if (!program) {\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Shader program linking error:', gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n return program;\n }\n\n /**\n * Render the magnified view.\n * @param sourceCanvas - The source canvas to sample from\n */\n render(sourceCanvas: HTMLCanvasElement): void {\n if (!this.gl || !this.program || !this.texture || !this.uniformLocations || !this.attributeLocations) return;\n\n // Check if we need to update the texture\n // We track: canvas version (via LiteGraph's change counter), source coordinates, and source dimensions\n const app = (window as any).app;\n const currentCanvasVersion = app?.graph?.change_counter ?? -1;\n\n const sourceChanged =\n this.lastSourceX !== this.state.sourceX ||\n this.lastSourceY !== this.state.sourceY ||\n this.lastSourceWidth !== this.state.sourceWidth ||\n this.lastSourceHeight !== this.state.sourceHeight;\n\n const canvasChanged = currentCanvasVersion !== this.lastCanvasVersion;\n\n // Only upload texture if something changed\n const needsTextureUpdate = canvasChanged || sourceChanged || this.lastCanvasVersion === -1;\n\n // Bind texture\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n\n if (needsTextureUpdate) {\n try {\n this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceCanvas);\n\n // Update tracking variables\n this.lastCanvasVersion = currentCanvasVersion;\n this.lastSourceX = this.state.sourceX;\n this.lastSourceY = this.state.sourceY;\n this.lastSourceWidth = this.state.sourceWidth;\n this.lastSourceHeight = this.state.sourceHeight;\n\n // Reset skip counter and log if debug mode\n if (this.config.debugMode && this.textureUploadSkipCount > 0) {\n console.log(`[MagnifyGlass] Texture uploaded after skipping ${this.textureUploadSkipCount} frames`);\n }\n this.textureUploadSkipCount = 0;\n } catch (e) {\n console.error(\"ComfyUI Magnifying Glass ERROR: Error in texImage2D:\", e);\n return;\n }\n } else {\n // Skip texture upload - just increment counter\n this.textureUploadSkipCount++;\n\n // Periodic debug logging\n if (this.config.debugMode && this.textureUploadSkipCount % 60 === 0) {\n console.log(`[MagnifyGlass] Texture upload skipped (${this.textureUploadSkipCount} frames cached)`);\n }\n }\n\n // Calculate normalized texture coordinates (UV space: 0-1)\n const uvX = this.state.sourceX / sourceCanvas.width;\n const uvY = this.state.sourceY / sourceCanvas.height;\n const uvWidth = this.state.sourceWidth / sourceCanvas.width;\n const uvHeight = this.state.sourceHeight / sourceCanvas.height;\n\n // Set up rendering\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n this.gl.clearColor(0.0, 0.0, 0.0, 0.0);\n this.gl.clear(this.gl.COLOR_BUFFER_BIT);\n\n this.gl.useProgram(this.program);\n\n // Set uniforms\n this.gl.activeTexture(this.gl.TEXTURE0);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.uniform1i(this.uniformLocations.sourceTexture, 0);\n this.gl.uniform2f(this.uniformLocations.textureOffset, uvX, uvY);\n this.gl.uniform2f(this.uniformLocations.textureRepeat, uvWidth, uvHeight);\n this.gl.uniform1f(this.uniformLocations.glassSize, this.config.glassSize);\n\n // Set attributes\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.position, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.position);\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.texCoord, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.texCoord);\n\n // Draw\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);\n }\n\n /**\n * Check for WebGL errors.\n * @param label - Debug label\n * @returns True if error occurred\n */\n checkWebGLError(label: string): boolean {\n if (!this.gl) return false;\n\n const err = this.gl.getError();\n if (err !== this.gl.NO_ERROR) {\n console.error(`ComfyUI Magnifying Glass ERROR: WebGL Error (${label}):`, err);\n return true;\n }\n return false;\n }\n\n /**\n * Update viewport when glass size changes.\n */\n updateViewport(): void {\n if (this.gl) {\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n }\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"WebGLRenderer.js","sources":["../../src/magnify-glass/WebGLRenderer.ts"],"sourcesContent":["/**\n * ComfyUI MagnifyGlass - WebGLRenderer (TypeScript)\n * \n * Handles WebGL-based rendering of the magnified view.\n */\n\nimport type { ConfigManager } from './ConfigManager';\nimport type { MagnifierState } from './MagnifierState';\nimport type { UiManager } from './UiManager';\n\ninterface UniformLocations {\n sourceTexture: WebGLUniformLocation | null;\n textureOffset: WebGLUniformLocation | null;\n textureRepeat: WebGLUniformLocation | null;\n glassSize: WebGLUniformLocation | null;\n}\n\ninterface AttributeLocations {\n position: number;\n texCoord: number;\n}\n\n/**\n * WebGL Renderer class.\n * Provides efficient WebGL-based magnification rendering.\n */\nexport class WebGLRenderer {\n config: ConfigManager;\n state: MagnifierState;\n ui: UiManager;\n\n gl: WebGLRenderingContext | null;\n program: WebGLProgram | null;\n texture: WebGLTexture | null;\n positionBuffer: WebGLBuffer | null;\n texCoordBuffer: WebGLBuffer | null;\n uniformLocations: UniformLocations | null;\n attributeLocations: AttributeLocations | null;\n currentFilteringMode: number | null;\n\n // Dirty flag optimization\n lastCanvasVersion: number;\n lastSourceX: number;\n lastSourceY: number;\n lastSourceWidth: number;\n lastSourceHeight: number;\n textureUploadSkipCount: number;\n\n vertexShaderSource: string;\n fragmentShaderSource: string;\n\n constructor(config: ConfigManager, state: MagnifierState, ui: UiManager) {\n this.config = config;\n this.state = state;\n this.ui = ui;\n\n this.gl = null;\n this.program = null;\n this.texture = null;\n this.positionBuffer = null;\n this.texCoordBuffer = null;\n this.uniformLocations = null;\n this.attributeLocations = null;\n this.currentFilteringMode = null;\n\n // Initialize dirty flag tracking\n this.lastCanvasVersion = -1;\n this.lastSourceX = -1;\n this.lastSourceY = -1;\n this.lastSourceWidth = -1;\n this.lastSourceHeight = -1;\n this.textureUploadSkipCount = 0;\n\n this.vertexShaderSource = `\n attribute vec2 a_position;\n attribute vec2 a_texCoord;\n varying vec2 v_texCoord;\n void main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_texCoord = a_texCoord;\n }\n `;\n\n this.fragmentShaderSource = `\n precision mediump float;\n varying vec2 v_texCoord;\n uniform sampler2D u_sourceTexture;\n uniform vec2 u_textureOffset;\n uniform vec2 u_textureRepeat;\n uniform float u_glassSize;\n\n void main() {\n vec2 sampleCoord = u_textureOffset + v_texCoord * u_textureRepeat;\n vec4 color = texture2D(u_sourceTexture, sampleCoord);\n gl_FragColor = color;\n }\n `;\n\n this.initialize();\n }\n\n /**\n * Initialize WebGL context and resources.\n */\n initialize(): void {\n // Get WebGL context\n if (!this.ui.glassCanvas) return;\n\n this.gl = this.ui.glassCanvas.getContext(\"webgl\", { preserveDrawingBuffer: true });\n if (!this.gl) {\n // Try webgl2 as fallback\n this.gl = this.ui.glassCanvas.getContext(\"webgl2\", { preserveDrawingBuffer: true }) as WebGLRenderingContext | null;\n }\n\n if (!this.gl) {\n const isFirefox = navigator.userAgent.toLowerCase().includes('firefox');\n const browserInstructions = isFirefox\n ? \"Firefox: Go to about:config, search for 'webgl.disabled' and set it to 'false', then restart Firefox.\"\n : \"Check your browser settings to ensure WebGL is enabled.\";\n\n console.error(\"ComfyUI Magnifying Glass ERROR: WebGL not supported or context creation failed.\");\n console.error(`To fix: ${browserInstructions}`);\n\n // Show user notification\n this.showWebGLError(browserInstructions);\n return;\n }\n\n // Create shader program\n this.program = this.createShaderProgram(this.gl, this.vertexShaderSource, this.fragmentShaderSource);\n if (!this.program) return;\n\n // Create position buffer\n this.positionBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n const positions = [-1, -1, 1, -1, -1, 1, 1, 1];\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);\n\n // Create texture coordinate buffer\n this.texCoordBuffer = this.gl.createBuffer();\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n const texCoords = [0, 1, 1, 1, 0, 0, 1, 0]; // Flipped Y for texCoords\n this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(texCoords), this.gl.STATIC_DRAW);\n\n // Create texture\n this.texture = this.gl.createTexture();\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);\n this.updateTextureFiltering(this.config.textureFiltering);\n\n // Get uniform and attribute locations\n this.uniformLocations = {\n sourceTexture: this.gl.getUniformLocation(this.program, \"u_sourceTexture\"),\n textureOffset: this.gl.getUniformLocation(this.program, \"u_textureOffset\"),\n textureRepeat: this.gl.getUniformLocation(this.program, \"u_textureRepeat\"),\n glassSize: this.gl.getUniformLocation(this.program, \"u_glassSize\"),\n };\n\n this.attributeLocations = {\n position: this.gl.getAttribLocation(this.program, \"a_position\"),\n texCoord: this.gl.getAttribLocation(this.program, \"a_texCoord\"),\n };\n }\n\n /**\n * Update texture filtering mode.\n * @param filteringModeString - \"Linear\" or \"Nearest\"\n */\n updateTextureFiltering(filteringModeString: string): void {\n if (!this.gl || !this.texture) return;\n\n let glFilterMode: number;\n if (filteringModeString === \"Nearest\") {\n glFilterMode = this.gl.NEAREST;\n } else {\n glFilterMode = this.gl.LINEAR;\n }\n\n if (this.currentFilteringMode === glFilterMode) return;\n\n this.currentFilteringMode = glFilterMode;\n\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, glFilterMode);\n this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, glFilterMode);\n this.gl.bindTexture(this.gl.TEXTURE_2D, null);\n }\n\n /**\n * Check if renderer is valid and ready.\n * @returns Boolean indicating if WebGL context and program are ready\n */\n isValid(): boolean {\n return this.gl !== null && this.program !== null;\n }\n\n /**\n * Create a WebGL shader program.\n * @param gl - WebGL context\n * @param vsSource - Vertex shader source\n * @param fsSource - Fragment shader source\n * @returns WebGL program or null on failure\n */\n createShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string): WebGLProgram | null {\n const vertexShader = gl.createShader(gl.VERTEX_SHADER);\n if (!vertexShader) return null;\n\n gl.shaderSource(vertexShader, vsSource);\n gl.compileShader(vertexShader);\n if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Vertex shader compilation error:', gl.getShaderInfoLog(vertexShader));\n gl.deleteShader(vertexShader);\n return null;\n }\n\n const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);\n if (!fragmentShader) {\n gl.deleteShader(vertexShader);\n return null;\n }\n\n gl.shaderSource(fragmentShader, fsSource);\n gl.compileShader(fragmentShader);\n if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Fragment shader compilation error:', gl.getShaderInfoLog(fragmentShader));\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n const program = gl.createProgram();\n if (!program) {\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n gl.attachShader(program, vertexShader);\n gl.attachShader(program, fragmentShader);\n gl.linkProgram(program);\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n console.error('ComfyUI Magnifying Glass ERROR: Shader program linking error:', gl.getProgramInfoLog(program));\n gl.deleteProgram(program);\n gl.deleteShader(vertexShader);\n gl.deleteShader(fragmentShader);\n return null;\n }\n\n return program;\n }\n\n /**\n * Render the magnified view.\n * @param sourceCanvas - The source canvas to sample from\n */\n render(sourceCanvas: HTMLCanvasElement): void {\n if (!this.gl || !this.program || !this.texture || !this.uniformLocations || !this.attributeLocations) return;\n\n // Check if we need to update the texture\n // We track: canvas version (via LiteGraph's change counter), source coordinates, and source dimensions\n const app = (window as any).app;\n const currentCanvasVersion = app?.graph?.change_counter ?? -1;\n\n const sourceChanged =\n this.lastSourceX !== this.state.sourceX ||\n this.lastSourceY !== this.state.sourceY ||\n this.lastSourceWidth !== this.state.sourceWidth ||\n this.lastSourceHeight !== this.state.sourceHeight;\n\n const canvasChanged = currentCanvasVersion !== this.lastCanvasVersion;\n\n // Only upload texture if something changed\n const needsTextureUpdate = canvasChanged || sourceChanged || this.lastCanvasVersion === -1;\n\n // Bind texture\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n\n if (needsTextureUpdate) {\n try {\n this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceCanvas);\n\n // Update tracking variables\n this.lastCanvasVersion = currentCanvasVersion;\n this.lastSourceX = this.state.sourceX;\n this.lastSourceY = this.state.sourceY;\n this.lastSourceWidth = this.state.sourceWidth;\n this.lastSourceHeight = this.state.sourceHeight;\n\n // Reset skip counter and log if debug mode\n if (this.config.debugMode && this.textureUploadSkipCount > 0) {\n console.log(`[MagnifyGlass] Texture uploaded after skipping ${this.textureUploadSkipCount} frames`);\n }\n this.textureUploadSkipCount = 0;\n } catch (e) {\n console.error(\"ComfyUI Magnifying Glass ERROR: Error in texImage2D:\", e);\n return;\n }\n } else {\n // Skip texture upload - just increment counter\n this.textureUploadSkipCount++;\n\n // Periodic debug logging\n if (this.config.debugMode && this.textureUploadSkipCount % 60 === 0) {\n console.log(`[MagnifyGlass] Texture upload skipped (${this.textureUploadSkipCount} frames cached)`);\n }\n }\n\n // Calculate normalized texture coordinates (UV space: 0-1)\n const uvX = this.state.sourceX / sourceCanvas.width;\n const uvY = this.state.sourceY / sourceCanvas.height;\n const uvWidth = this.state.sourceWidth / sourceCanvas.width;\n const uvHeight = this.state.sourceHeight / sourceCanvas.height;\n\n // Set up rendering\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n this.gl.clearColor(0.0, 0.0, 0.0, 0.0);\n this.gl.clear(this.gl.COLOR_BUFFER_BIT);\n\n this.gl.useProgram(this.program);\n\n // Set uniforms\n this.gl.activeTexture(this.gl.TEXTURE0);\n this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);\n this.gl.uniform1i(this.uniformLocations.sourceTexture, 0);\n this.gl.uniform2f(this.uniformLocations.textureOffset, uvX, uvY);\n this.gl.uniform2f(this.uniformLocations.textureRepeat, uvWidth, uvHeight);\n this.gl.uniform1f(this.uniformLocations.glassSize, this.config.glassSize);\n\n // Set attributes\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.position, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.position);\n\n this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);\n this.gl.vertexAttribPointer(this.attributeLocations.texCoord, 2, this.gl.FLOAT, false, 0, 0);\n this.gl.enableVertexAttribArray(this.attributeLocations.texCoord);\n\n // Draw\n this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);\n }\n\n /**\n * Check for WebGL errors.\n * @param label - Debug label\n * @returns True if error occurred\n */\n checkWebGLError(label: string): boolean {\n if (!this.gl) return false;\n\n const err = this.gl.getError();\n if (err !== this.gl.NO_ERROR) {\n console.error(`ComfyUI Magnifying Glass ERROR: WebGL Error (${label}):`, err);\n return true;\n }\n return false;\n }\n\n /**\n * Update viewport when glass size changes.\n */\n updateViewport(): void {\n if (this.gl) {\n this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);\n }\n }\n\n /**\n * Show WebGL error notification to user.\n * @param instructions - Browser-specific instructions to fix\n */\n showWebGLError(instructions: string): void {\n // Create a toast notification\n const toast = document.createElement('div');\n toast.id = 'magnifyglass-webgl-error';\n toast.style.cssText = `\n position: fixed;\n bottom: 20px;\n right: 20px;\n max-width: 400px;\n padding: 16px 20px;\n background: linear-gradient(135deg, #ff4444 0%, #cc3333 100%);\n color: white;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.3);\n font-family: system-ui, -apple-system, sans-serif;\n font-size: 14px;\n line-height: 1.5;\n z-index: 100000;\n animation: slideIn 0.3s ease-out;\n `;\n\n toast.innerHTML = `\n \n
⚠️ \n
\n MagnifyGlass: WebGL Disabled \n ${instructions} \n \n Dismiss\n \n
\n
\n `;\n\n // Add animation keyframes\n if (!document.getElementById('magnifyglass-toast-style')) {\n const style = document.createElement('style');\n style.id = 'magnifyglass-toast-style';\n style.textContent = `\n @keyframes slideIn {\n from { transform: translateX(100%); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n }\n `;\n document.head.appendChild(style);\n }\n\n document.body.appendChild(toast);\n\n // Auto-dismiss after 15 seconds\n setTimeout(() => {\n if (toast.parentElement) {\n toast.style.animation = 'slideIn 0.3s ease-out reverse';\n setTimeout(() => toast.remove(), 300);\n }\n }, 15000);\n }\n}\n"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/web/magnify_info_panel.js b/web/magnify_info_panel.js
index 5560b7c..67bcedf 100644
--- a/web/magnify_info_panel.js
+++ b/web/magnify_info_panel.js
@@ -22,12 +22,6 @@ function initializeInfoPanel() {
try {
const infoPanel = new InfoPanel(magnifyGlass);
window.infoPanelManager = infoPanel;
- const link = document.createElement("link");
- link.rel = "stylesheet";
- link.type = "text/css";
- link.href = "extensions/comfyui-magnifyglass/style.css";
- document.head.appendChild(link);
- console.log("ComfyUI Magnify Info Panel: CSS loaded");
console.log("ComfyUI Magnify Info Panel: Extension initialized");
} catch (e) {
console.error("ComfyUI Magnify Info Panel: Error during initialization:", e);
diff --git a/web/magnify_info_panel.js.map b/web/magnify_info_panel.js.map
index dd6434a..f6b3f1f 100644
--- a/web/magnify_info_panel.js.map
+++ b/web/magnify_info_panel.js.map
@@ -1 +1 @@
-{"version":3,"file":"magnify_info_panel.js","sources":["../src/magnify_info_panel.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Info Panel - Main Entry Point\n * \n * Initializes the Info Panel extension.\n */\n\nimport { InfoPanel } from './info-panel';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// We rely on the global 'app' object provided by ComfyUI\n// No need to import it as it's global, but TypeScript needs to know about it.\n// We handled this in types/comfyui.d.ts\n\napp.registerExtension({\n name: \"comfyui.magnify.info.panel\",\n init() {\n // Wait for magnify glass to be available\n const checkDependencies = () => {\n if (window.comfyUIMagnifyGlass) {\n initializeInfoPanel();\n } else {\n setTimeout(checkDependencies, 100);\n }\n };\n\n checkDependencies();\n }\n});\n\nfunction initializeInfoPanel() {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass) {\n console.error(\"ComfyUI Magnify Info Panel: Failed to find MagnifyGlass instance.\");\n return;\n }\n\n try {\n // Initialize the info panel\n const infoPanel = new InfoPanel(magnifyGlass);\n\n // Expose orchestrator globally if needed\n window.infoPanelManager = infoPanel as any;\n\n // Load CSS\n const link = document.createElement(\"link\");\n link.rel = \"stylesheet\";\n link.type = \"text/css\";\n link.href = \"extensions/comfyui-magnifyglass/style.css\";\n document.head.appendChild(link);\n console.log(\"ComfyUI Magnify Info Panel: CSS loaded\");\n\n console.log(\"ComfyUI Magnify Info Panel: Extension initialized\");\n } catch (e) {\n console.error(\"ComfyUI Magnify Info Panel: Error during initialization:\", e);\n }\n}\n\n"],"names":[],"mappings":";;AAcA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,oBAAoB,MAAM;AAC5B,UAAI,OAAO,qBAAqB;AAC5B,4BAAA;AAAA,MACJ,OAAO;AACH,mBAAW,mBAAmB,GAAG;AAAA,MACrC;AAAA,IACJ;AAEA,sBAAA;AAAA,EACJ;AACJ,CAAC;AAED,SAAS,sBAAsB;AAC3B,QAAM,eAAe,OAAO;AAC5B,MAAI,CAAC,cAAc;AACf,YAAQ,MAAM,mEAAmE;AACjF;AAAA,EACJ;AAEA,MAAI;AAEA,UAAM,YAAY,IAAI,UAAU,YAAY;AAG5C,WAAO,mBAAmB;AAG1B,UAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,SAAK,MAAM;AACX,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,aAAS,KAAK,YAAY,IAAI;AAC9B,YAAQ,IAAI,wCAAwC;AAEpD,YAAQ,IAAI,mDAAmD;AAAA,EACnE,SAAS,GAAG;AACR,YAAQ,MAAM,4DAA4D,CAAC;AAAA,EAC/E;AACJ;"}
\ No newline at end of file
+{"version":3,"file":"magnify_info_panel.js","sources":["../src/magnify_info_panel.ts"],"sourcesContent":["/**\n * ComfyUI Magnify Info Panel - Main Entry Point\n * \n * Initializes the Info Panel extension.\n */\n\nimport { InfoPanel } from './info-panel';\n// @ts-ignore\nimport { app } from \"/scripts/app.js\";\n\n// We rely on the global 'app' object provided by ComfyUI\n// No need to import it as it's global, but TypeScript needs to know about it.\n// We handled this in types/comfyui.d.ts\n\napp.registerExtension({\n name: \"comfyui.magnify.info.panel\",\n init() {\n // Wait for magnify glass to be available\n const checkDependencies = () => {\n if (window.comfyUIMagnifyGlass) {\n initializeInfoPanel();\n } else {\n setTimeout(checkDependencies, 100);\n }\n };\n\n checkDependencies();\n }\n});\n\nfunction initializeInfoPanel() {\n const magnifyGlass = window.comfyUIMagnifyGlass;\n if (!magnifyGlass) {\n console.error(\"ComfyUI Magnify Info Panel: Failed to find MagnifyGlass instance.\");\n return;\n }\n\n try {\n // Initialize the info panel\n const infoPanel = new InfoPanel(magnifyGlass);\n\n // Expose orchestrator globally if needed\n window.infoPanelManager = infoPanel as any;\n\n // CSS is loaded by UIManager.injectStyles() - no need to load separately\n\n console.log(\"ComfyUI Magnify Info Panel: Extension initialized\");\n } catch (e) {\n console.error(\"ComfyUI Magnify Info Panel: Error during initialization:\", e);\n }\n}\n\n"],"names":[],"mappings":";;AAcA,IAAI,kBAAkB;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAEH,UAAM,oBAAoB,MAAM;AAC5B,UAAI,OAAO,qBAAqB;AAC5B,4BAAA;AAAA,MACJ,OAAO;AACH,mBAAW,mBAAmB,GAAG;AAAA,MACrC;AAAA,IACJ;AAEA,sBAAA;AAAA,EACJ;AACJ,CAAC;AAED,SAAS,sBAAsB;AAC3B,QAAM,eAAe,OAAO;AAC5B,MAAI,CAAC,cAAc;AACf,YAAQ,MAAM,mEAAmE;AACjF;AAAA,EACJ;AAEA,MAAI;AAEA,UAAM,YAAY,IAAI,UAAU,YAAY;AAG5C,WAAO,mBAAmB;AAI1B,YAAQ,IAAI,mDAAmD;AAAA,EACnE,SAAS,GAAG;AACR,YAAQ,MAAM,4DAA4D,CAAC;AAAA,EAC/E;AACJ;"}
\ No newline at end of file