diff --git a/README.md b/README.md
index 644e05b..c8d8145 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# ComfyUI-MagnifyGlass
-
+



@@ -14,6 +14,28 @@ ComfyUI-MagnifyGlass adds an intuitive magnifying glass to your ComfyUI canvas,

+## 🔥 Recent Improvements
+
+**Latest Session Updates:**
+
+- **🎯 Smart Input Detection**: Hotkeys now intelligently detect when you're typing in input fields (similar to ComfyUI's native 'F' key behavior) and won't activate, eliminating the need for Alt key requirements
+- **🔓 Enhanced Panel Controls**:
+ - Pin button now properly toggles between unlocked (🔓) and locked (🔒) states
+ - 'U' key unlocks panel to mouse location without requiring Alt
+ - 'G' key intelligently respects manual pin/lock decisions and only auto-unlocks when appropriate
+- **🎨 Visual Improvements**:
+ - Updated default border colors to gray (#6b7280) for better UI integration
+ - Added customizable info panel text and accent colors with smart defaults for light/dark modes
+ - Automatic theme synchronization with main ComfyUI frontend for dark and light modes
+- **⚙️ Enhanced Settings**:
+ - Percentage-based controls for opacity (10-100%) and zoom factor (100-1000%) for finer control
+ - Fixed color picker persistence issues
+- **🔄 Improved Reset Functionality**: Reset key ('O') now properly resets both magnify glass and inspector panel to default top-right positions
+- **📱 UI Polish**:
+ - Inspector panel always appears above other UI elements with proper z-index layering
+ - Fixed blank settings dropdowns and improved tooltip descriptions
+ - Updated button tooltips to reflect current hotkey behavior
+
## 🚀 Latest Updates
**6/12/25 (v1.3.0)**
diff --git a/pyproject.toml b/pyproject.toml
index e440258..9fc17a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "comfyui-magnifyglass"
description = "ComfyUI-MagnifyGlass: A powerful & customizable magnifying glass for ComfyUI. Zoom into canvas details with smooth controls, configurable activation, custom styles (shape, size, border) & WebGL performance."
-version = "1.4.0"
+version = "1.4.1"
license = {file = "LICENSE"}
[project.urls]
diff --git a/web/magnify_glass.js b/web/magnify_glass.js
index 2d0026d..165582e 100644
--- a/web/magnify_glass.js
+++ b/web/magnify_glass.js
@@ -4,7 +4,7 @@ import { app } from "../../../scripts/app.js";
* ComfyUI Magnifying Glass
*
* This script adds a magnifying glass feature to ComfyUI.
- * Hold Alt+X to activate the magnifying glass and see a zoomed view of the canvas.
+ * Press X to activate the magnifying glass and see a zoomed view of the canvas.
*/
app.registerExtension({
@@ -12,13 +12,13 @@ app.registerExtension({
async setup() {
// Default settings configuration
const DEFAULT_SETTINGS = {
- "🔍MagnifyGlass.ZoomFactor": 3,
+ "🔍MagnifyGlass.ZoomFactor": 300,
"🔍MagnifyGlass.GlassSize": 300,
- "🔍MagnifyGlass.BorderColor": "#ffffff",
- "🔍MagnifyGlass.BorderWidth": 2,
+ "🔍MagnifyGlass.BorderColor": "#6b7280",
+ "🔍MagnifyGlass.BorderWidth": 1,
"🔍MagnifyGlass.ActivationKey": "x",
"🔍MagnifyGlass.AltRequired": false,
- "🔍MagnifyGlass.FollowCursor": true,
+ "🔍MagnifyGlass.FollowCursor": false,
"🔍MagnifyGlass.DebugMode": false,
"🔍MagnifyGlass.OffsetStep": 5,
"🔍MagnifyGlass.GlassPosition": "Top-Right",
@@ -26,7 +26,7 @@ app.registerExtension({
"🔍MagnifyGlass.GlassShape": "Rounded Square",
"🔍MagnifyGlass.BorderEnabled": true,
"🔍MagnifyGlass.TextureFiltering": "Linear",
- "🔍MagnifyGlass.AlwaysActiveMode": false,
+ "🔍MagnifyGlass.AlwaysActiveMode": true,
"🔍MagnifyGlass.ToggleFollowCursorKey": "h",
};
@@ -94,7 +94,7 @@ app.registerExtension({
// Attach event handlers
this.eventHandler.attachListeners();
- this.debugger.log(`Initialized (WebGL). Hold ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate. Arrow keys to adjust offset, R to reset.`);
+ this.debugger.log(`Initialized (WebGL) with Smart Input Detection. Press ${this.config.altRequired ? 'Alt+' : ''}${this.config.activationKey.toUpperCase()} to activate. Hotkeys disabled while typing in inputs.`);
}
findLiteGraphCanvas() {
@@ -345,7 +345,7 @@ app.registerExtension({
// Method to update config from settings (can be called from callbacks)
updateConfigFromSettings() {
- this.config.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"]);
+ this.config.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"]) / 100;
this.config.glassSize = getSettingValue("🔍MagnifyGlass.GlassSize", DEFAULT_SETTINGS["🔍MagnifyGlass.GlassSize"]);
this.config.borderColor = getSettingValue("🔍MagnifyGlass.BorderColor", DEFAULT_SETTINGS["🔍MagnifyGlass.BorderColor"]);
this.config.borderWidth = getSettingValue("🔍MagnifyGlass.BorderWidth", DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"]);
@@ -415,7 +415,40 @@ app.registerExtension({
if (this.state.active) {
this.updateMagnifiedView(); // Update if active
}
- this.debugger.log("Offsets reset to 0, 0");
+
+ // Reset magnify glass position to top-right corner
+ if (this.ui.glassDiv) {
+ const glassSize = this.config.glassSize;
+ const padding = 20; // Same padding as initial positioning
+ this.ui.glassDiv.style.left = `${window.innerWidth - glassSize - padding}px`;
+ this.ui.glassDiv.style.top = `${padding}px`;
+ // Reset the activation flag so next time it uses default positioning
+ this.state.wasActivatedBefore = false;
+ }
+
+ // Reset inspector panel position to default
+ if (window.comfyUIMagnifyGlassExtensions && window.comfyUIMagnifyGlassExtensions.length > 0) {
+ window.comfyUIMagnifyGlassExtensions.forEach(extension => {
+ if (extension && extension.stateManager) {
+ // Reset panel to default position
+ extension.stateManager.state.isPanelPinned = false;
+ extension.stateManager.state.isPanelLocked = false;
+ extension.stateManager.state.isAutoPinned = false;
+ extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };
+ extension.stateManager.state.lastPinnedPosition = null;
+
+ // Update panel position and controls
+ if (extension.positionManager) {
+ extension.positionManager.positionPanel();
+ }
+ if (extension.uiManager && extension.uiManager.updateControlStates) {
+ extension.uiManager.updateControlStates();
+ }
+ }
+ });
+ }
+
+ this.debugger.log("Reset: Magnify glass and inspector panel positions restored to top-right defaults");
}
}
@@ -423,7 +456,7 @@ app.registerExtension({
class ConfigManager {
constructor() {
// Initialize properties that will be set by reading settings
- this.zoomFactor = DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"];
+ this.zoomFactor = DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"] / 100;
this.glassSize = DEFAULT_SETTINGS["🔍MagnifyGlass.GlassSize"];
this.borderColor = DEFAULT_SETTINGS["🔍MagnifyGlass.BorderColor"];
this.borderWidth = DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"];
@@ -447,7 +480,7 @@ app.registerExtension({
// Call this after settings are registered and potentially read by MagnifyGlass
loadSettings() {
- this.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", this.zoomFactor);
+ this.zoomFactor = getSettingValue("🔍MagnifyGlass.ZoomFactor", this.zoomFactor * 100) / 100;
this.glassSize = getSettingValue("🔍MagnifyGlass.GlassSize", this.glassSize);
this.borderColor = getSettingValue("🔍MagnifyGlass.BorderColor", this.borderColor);
this.borderWidth = getSettingValue("🔍MagnifyGlass.BorderWidth", this.borderWidth);
@@ -543,7 +576,7 @@ app.registerExtension({
border: ${this.config.borderEnabled ? `${this.config.borderWidth}px solid ${this.config.borderColor}` : 'none'};
overflow: hidden;
pointer-events: none;
- z-index: 9999;
+ z-index: 98999;
display: none;
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
background-color: rgba(255,255,255,0.1);
@@ -589,7 +622,7 @@ app.registerExtension({
right: 10px;
background: rgba(0,0,0,0.7);
border: 1px solid #fff;
- z-index: 10000;
+ z-index: 99000;
pointer-events: none;
color: white;
font-family: monospace;
@@ -931,10 +964,43 @@ app.registerExtension({
document.addEventListener("mousemove", this.handleMouseMove.bind(this));
}
+ // Helper function to check if user is typing in an input field
+ isUserTyping() {
+ const activeElement = document.activeElement;
+ if (!activeElement) return false;
+
+ // Check if the active element is an input, textarea, or contenteditable
+ const tagName = activeElement.tagName.toLowerCase();
+ if (tagName === 'input' || tagName === 'textarea') {
+ return true;
+ }
+
+ // Check for contenteditable elements
+ if (activeElement.contentEditable === 'true') {
+ return true;
+ }
+
+ // Check if it's inside a form or has input-like classes
+ if (activeElement.closest('form') ||
+ activeElement.classList.contains('cm-editor') || // CodeMirror editor
+ activeElement.classList.contains('monaco-editor') || // Monaco editor
+ activeElement.closest('.cm-editor') ||
+ activeElement.closest('.monaco-editor')) {
+ return true;
+ }
+
+ return false;
+ }
+
handleKeyDown(e) {
const config = this.magnifyGlass.config;
const state = this.magnifyGlass.state;
+ // Don't handle hotkeys if user is typing in an input field (Smart Input Detection)
+ if (this.isUserTyping()) {
+ return;
+ }
+
// Magnifier activation
if (e.key.toLowerCase() === config.activationKey &&
(!config.altRequired || e.altKey)) {
@@ -982,10 +1048,43 @@ app.registerExtension({
e.preventDefault();
} else if (e.key.toLowerCase() === config.resetKey.toLowerCase() &&
(!config.altRequired || e.altKey)) { // Use configured reset key & check Alt
- // Reset offsets to zero
+ // Reset magnify glass offsets to zero
config.offsetX = 0;
config.offsetY = 0;
offsetChanged = true;
+
+ // Reset magnify glass position to top-right corner
+ if (this.magnifyGlass.ui.glassDiv) {
+ const glassSize = config.glassSize;
+ const padding = 20; // Same padding as initial positioning
+ this.magnifyGlass.ui.glassDiv.style.left = `${window.innerWidth - glassSize - padding}px`;
+ this.magnifyGlass.ui.glassDiv.style.top = `${padding}px`;
+ // Reset the activation flag so next time it uses default positioning
+ this.magnifyGlass.state.wasActivatedBefore = false;
+ }
+
+ // Reset inspector panel position to default
+ if (window.comfyUIMagnifyGlassExtensions && window.comfyUIMagnifyGlassExtensions.length > 0) {
+ window.comfyUIMagnifyGlassExtensions.forEach(extension => {
+ if (extension && extension.stateManager) {
+ // Reset panel to default position
+ extension.stateManager.state.isPanelPinned = false;
+ extension.stateManager.state.isPanelLocked = false;
+ extension.stateManager.state.pinnedPosition = { x: 0, y: 0 };
+ extension.stateManager.state.lastPinnedPosition = null;
+
+ // Update panel position and controls
+ if (extension.positionManager) {
+ extension.positionManager.positionPanel();
+ }
+ if (extension.uiManager && extension.uiManager.updateControlStates) {
+ extension.uiManager.updateControlStates();
+ }
+ }
+ });
+ }
+
+ this.magnifyGlass.debugger.log("Reset: Magnify glass and inspector panel positions restored to top-right defaults");
e.preventDefault();
}
@@ -1276,16 +1375,17 @@ app.registerExtension({
// Add settings to the ComfyUI settings dialog
app.ui.settings.addSetting({
id: "🔍MagnifyGlass.ZoomFactor",
- name: "🔍 Magnify Glass: Zoom Factor",
+ name: "🔍 Magnify Glass: Zoom Factor (%)",
type: "slider",
defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ZoomFactor"],
- min: 1.0,
- max: 10.0,
- step: 0.1,
- tooltip: "Magnification level (e.g., 2.5 means 2.5x zoom).",
+ min: 100,
+ max: 1000,
+ step: 25,
+ tooltip: "Magnification level as a percentage (e.g., 300 = 3x zoom, 150 = 1.5x zoom).",
onChange: (value) => {
if (magnifyGlass && magnifyGlass.config) {
- magnifyGlass.config.zoomFactor = parseFloat(value);
+ // Convert percentage to decimal (e.g., 300 -> 3.0)
+ magnifyGlass.config.zoomFactor = value / 100;
if (magnifyGlass.state.active) {
magnifyGlass.updateMagnifiedView();
}
@@ -1320,11 +1420,11 @@ app.registerExtension({
defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.BorderWidth"],
min: 0,
max: 10,
- step: 1,
- tooltip: "Width of the border around the magnifying glass.",
+ step: 0.1,
+ tooltip: "Width of the border around the magnifying glass (supports decimal values like 0.5, 1.5, etc.).",
onChange: (value) => {
if (magnifyGlass && magnifyGlass.config) {
- magnifyGlass.config.borderWidth = parseInt(value, 10);
+ magnifyGlass.config.borderWidth = parseFloat(value);
magnifyGlass.applyUiChanges(); // Update border style
}
}
@@ -1338,7 +1438,19 @@ app.registerExtension({
tooltip: "Color of the border around the magnifying glass.",
onChange: (value) => {
if (magnifyGlass && magnifyGlass.config) {
- magnifyGlass.config.borderColor = value;
+ // Ensure color value includes # symbol for valid CSS
+ const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value;
+ magnifyGlass.config.borderColor = normalizedColor;
+
+ // Only save back if we actually normalized the color (to prevent recursion)
+ if (value !== normalizedColor) {
+ try {
+ app.ui.settings.setSettingValue("🔍MagnifyGlass.BorderColor", normalizedColor);
+ } catch (e) {
+ console.warn('Failed to save normalized border color:', e);
+ }
+ }
+
magnifyGlass.applyUiChanges(); // Update border style
}
}
@@ -1365,7 +1477,7 @@ app.registerExtension({
type: "combo",
options: [ { value: true, text: "Yes" }, { value: false, text: "No" } ],
defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.AltRequired"],
- tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset.",
+ tooltip: "If Yes, Alt (Windows/Linux) or Option (Mac) must be held for activation and for reset. Not needed when 'Smart Input Detection' prevents hotkeys during typing.",
onChange: (value) => {
if (magnifyGlass && magnifyGlass.config) {
magnifyGlass.config.altRequired = value;
@@ -1410,7 +1522,7 @@ app.registerExtension({
type: "combo",
options: ["r", "o", "p", "k", "l"], // Example keys, can be expanded
defaultValue: DEFAULT_SETTINGS["🔍MagnifyGlass.ResetKey"],
- tooltip: "The key (case-insensitive) to press to reset the view offset while the magnifier is active. Works with Alt/Option if 'Require Alt for Reset' is Yes.",
+ tooltip: "The key (case-insensitive) to press to reset the magnify glass offset and inspector panel position to their default locations while the magnifier is active. Works with Alt/Option if 'Require Alt for Reset' is Yes.",
onChange: (value) => {
if (magnifyGlass && magnifyGlass.config) {
magnifyGlass.config.resetKey = value.toLowerCase();
diff --git a/web/magnify_info_panel.js b/web/magnify_info_panel.js
index 85bc2eb..c388522 100644
--- a/web/magnify_info_panel.js
+++ b/web/magnify_info_panel.js
@@ -42,18 +42,19 @@ app.registerExtension({
// Default settings configuration
const DEFAULT_SETTINGS = {
"🔍MagnifyGlass.InfoPanelEnabled": true,
- "🔍MagnifyGlass.InfoPanelPosition": "Left",
+ "🔍MagnifyGlass.InfoPanelPosition": "Bottom",
"🔍MagnifyGlass.InfoPanelWidth": 320,
- "🔍MagnifyGlass.InfoPanelOpacity": 0.95,
+ "🔍MagnifyGlass.InfoPanelOpacity": 100,
"🔍MagnifyGlass.InfoPanelMaxHeight": 500,
- "🔍MagnifyGlass.InfoPanelTheme": "Dark",
- "🔍MagnifyGlass.InfoPanelAnimations": true,
+ "🔍MagnifyGlass.InfoPanelAnimations": false,
"🔍MagnifyGlass.ShowInspectorTab": false,
"🔍MagnifyGlass.ToggleHotkey": "i",
"🔍MagnifyGlass.GlassPreviewToggleHotkey": "g",
- "🔍MagnifyGlass.PinPanelHotkey": "p",
+ "🔍MagnifyGlass.PinPanelHotkey": "u",
"🔍MagnifyGlass.ShowHoveringControls": true,
- "🔍MagnifyGlass.ControlsPosition": "bottom-centered",
+ "🔍MagnifyGlass.ControlsPosition": "bottom",
+ "🔍MagnifyGlass.InfoPanelTextColor": "#6b7280",
+ "🔍MagnifyGlass.InfoPanelAccentColor": "#3b82f6",
};
const getSettingValue = (key, defaultValue) => {
@@ -78,6 +79,7 @@ app.registerExtension({
isPanelMinimized: false,
isPanelPinned: false,
isPanelLocked: false, // New lock state
+ isAutoPinned: false, // Track if panel was auto-pinned by glass hide
pinnedPosition: { x: 0, y: 0 },
lastPinnedPosition: null, // Remember last pinned location
@@ -103,16 +105,265 @@ app.registerExtension({
currentInfo: {},
// Settings cache
- settings: {}
+ settings: {},
+
+ // Auto-detected theme
+ currentTheme: 'dark' // Will be auto-detected
};
+ this.initThemeDetection();
this.loadSettings();
}
+ initThemeDetection() {
+ // Immediate forced detection
+ console.log('ComfyUI Magnify Glass: Starting theme detection...');
+
+ // Detect ComfyUI's current theme
+ this.detectCurrentTheme();
+
+ // Force an immediate check in case we missed the initial state
+ setTimeout(() => {
+ console.log('ComfyUI Magnify Glass: Delayed theme check...');
+ if (this.detectCurrentTheme()) {
+ this.notifyThemeChange();
+ }
+ }, 1000);
+
+ // Set up mutation observer to watch for theme changes
+ this.setupThemeObserver();
+
+ // Listen for system theme changes as fallback
+ if (window.matchMedia) {
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
+ prefersDark.addListener(() => this.detectCurrentTheme());
+ }
+
+ // Add click listener to theme toggle buttons for immediate detection
+ this.setupThemeButtonListeners();
+ }
+
+ detectCurrentTheme() {
+ let detectedTheme = 'dark'; // Default fallback
+
+ const body = document.body;
+ const html = document.documentElement;
+ const vueApp = document.querySelector('#vue-app');
+ const sidebar = document.querySelector('.comfy-menu, .sidebar, .menu, [class*="sidebar"], [class*="menu"]');
+
+ // Get all possible theme-related elements
+ const allElements = [body, html, vueApp, sidebar].filter(Boolean);
+
+ // Debug: Log current classes and attributes with more detail
+ const debugInfo = {
+ bodyClasses: Array.from(body.classList),
+ htmlClasses: Array.from(html.classList),
+ bodyDataTheme: body.getAttribute('data-theme'),
+ htmlDataTheme: html.getAttribute('data-theme'),
+ vueAppClasses: vueApp ? Array.from(vueApp.classList) : null,
+ sidebarClasses: sidebar ? Array.from(sidebar.classList) : null,
+ bodyBgColor: window.getComputedStyle(body).backgroundColor,
+ htmlBgColor: window.getComputedStyle(html).backgroundColor,
+ allComputedVars: {}
+ };
+
+ // Check CSS variables
+ const rootStyles = window.getComputedStyle(html);
+ const bodyStyles = window.getComputedStyle(body);
+
+ // Get all CSS custom properties
+ for (let i = 0; i < rootStyles.length; i++) {
+ const prop = rootStyles[i];
+ if (prop.startsWith('--')) {
+ debugInfo.allComputedVars[prop] = rootStyles.getPropertyValue(prop).trim();
+ }
+ }
+
+ console.log('ComfyUI Magnify Glass: Comprehensive theme detection debug:', debugInfo);
+
+ // Method 1: Look for specific light theme indicators in CSS variables
+ const primarySurface = rootStyles.getPropertyValue('--p-primary-color') ||
+ rootStyles.getPropertyValue('--primary-color') ||
+ bodyStyles.getPropertyValue('--p-primary-color') ||
+ bodyStyles.getPropertyValue('--primary-color');
+
+ const surfaceGround = rootStyles.getPropertyValue('--p-surface-ground') ||
+ rootStyles.getPropertyValue('--surface-ground') ||
+ bodyStyles.getPropertyValue('--p-surface-ground') ||
+ bodyStyles.getPropertyValue('--surface-ground');
+
+ const textColor = rootStyles.getPropertyValue('--p-text-color') ||
+ rootStyles.getPropertyValue('--text-color') ||
+ bodyStyles.getPropertyValue('--p-text-color') ||
+ bodyStyles.getPropertyValue('--text-color');
+
+ console.log('ComfyUI Magnify Glass: CSS Variables:', { primarySurface, surfaceGround, textColor });
+
+ // Method 2: Analyze background colors of multiple elements
+ const backgrounds = allElements.map(el => {
+ const styles = window.getComputedStyle(el);
+ return {
+ element: el.tagName || el.className,
+ backgroundColor: styles.backgroundColor,
+ color: styles.color
+ };
+ });
+
+ console.log('ComfyUI Magnify Glass: Background analysis:', backgrounds);
+
+ // Method 3: Check for light backgrounds
+ for (const bg of backgrounds) {
+ if (bg.backgroundColor && bg.backgroundColor !== 'rgba(0, 0, 0, 0)' && bg.backgroundColor !== 'transparent') {
+ const rgbMatch = bg.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
+ if (rgbMatch) {
+ const [, r, g, b] = rgbMatch.map(Number);
+ const brightness = (r * 299 + g * 587 + b * 114) / 1000;
+ console.log(`ComfyUI Magnify Glass: Element ${bg.element} brightness: ${brightness}`);
+ if (brightness > 180) { // Higher threshold for light detection
+ detectedTheme = 'light';
+ console.log(`ComfyUI Magnify Glass: Light theme detected via background analysis of ${bg.element}`);
+ break;
+ }
+ }
+ }
+ }
+
+ // Method 4: Check for theme toggle buttons state (they might indicate current theme)
+ const themeButtons = document.querySelectorAll('[class*="theme"], button[title*="theme"], button[title*="Theme"], .p-button[title*="light"], .p-button[title*="Light"]');
+ themeButtons.forEach(btn => {
+ console.log('ComfyUI Magnify Glass: Found theme button:', {
+ classes: Array.from(btn.classList),
+ text: btn.textContent,
+ title: btn.title,
+ active: btn.classList.contains('active') || btn.classList.contains('selected')
+ });
+ });
+
+ // Method 5: Force light theme detection if we see light backgrounds
+ if (detectedTheme === 'dark') {
+ // Look for any element with very light background
+ const lightElements = document.querySelectorAll('*');
+ for (let i = 0; i < Math.min(lightElements.length, 50); i++) { // Check first 50 elements
+ const el = lightElements[i];
+ const styles = window.getComputedStyle(el);
+ const bgColor = styles.backgroundColor;
+
+ if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
+ const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
+ if (rgbMatch) {
+ const [, r, g, b] = rgbMatch.map(Number);
+ const brightness = (r * 299 + g * 587 + b * 114) / 1000;
+ if (brightness > 200) { // Very light elements
+ detectedTheme = 'light';
+ console.log(`ComfyUI Magnify Glass: Light theme forced via element ${el.tagName}.${el.className} with brightness ${brightness}`);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Update theme if it changed
+ if (this.state.currentTheme !== detectedTheme) {
+ console.log(`ComfyUI Magnify Glass: Theme changed from ${this.state.currentTheme} to ${detectedTheme}`);
+ this.state.currentTheme = detectedTheme;
+ return true; // Theme changed
+ }
+
+ console.log(`ComfyUI Magnify Glass: Current theme remains: ${detectedTheme}`);
+ return false; // No change
+ }
+
+ setupThemeObserver() {
+ // Watch for changes to body/html classes and attributes
+ const observer = new MutationObserver((mutations) => {
+ let shouldCheck = false;
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'attributes' &&
+ (mutation.attributeName === 'class' ||
+ mutation.attributeName === 'data-theme' ||
+ mutation.attributeName === 'style')) {
+ shouldCheck = true;
+ console.log('ComfyUI Magnify Glass: DOM change detected:', {
+ target: mutation.target.tagName,
+ attribute: mutation.attributeName,
+ oldValue: mutation.oldValue,
+ newValue: mutation.target.getAttribute(mutation.attributeName)
+ });
+ }
+ });
+
+ if (shouldCheck && this.detectCurrentTheme()) {
+ // Theme changed, update UI immediately
+ this.notifyThemeChange();
+ }
+ });
+
+ // Observe changes to body, html, and vue app
+ observer.observe(document.body, { attributes: true, attributeOldValue: true });
+ observer.observe(document.documentElement, { attributes: true, attributeOldValue: true });
+
+ const vueApp = document.querySelector('#vue-app');
+ if (vueApp) {
+ observer.observe(vueApp, { attributes: true, attributeOldValue: true });
+ }
+
+ // Also watch for changes to any element with theme-related classes
+ const themeElements = document.querySelectorAll('[class*="theme"], [class*="dark"], [class*="light"], [data-theme]');
+ themeElements.forEach(el => {
+ observer.observe(el, { attributes: true, attributeOldValue: true });
+ });
+
+ // Periodic fallback check every 2 seconds
+ setInterval(() => {
+ if (this.detectCurrentTheme()) {
+ this.notifyThemeChange();
+ }
+ }, 2000);
+ }
+
+ setupThemeButtonListeners() {
+ // Listen for clicks on theme toggle buttons
+ document.addEventListener('click', (e) => {
+ // Check if clicked element might be a theme toggle
+ const target = e.target;
+ const isThemeButton = target.textContent?.toLowerCase().includes('theme') ||
+ target.textContent?.toLowerCase().includes('light') ||
+ target.textContent?.toLowerCase().includes('dark') ||
+ target.title?.toLowerCase().includes('theme') ||
+ target.className?.toLowerCase().includes('theme');
+
+ if (isThemeButton) {
+ console.log('ComfyUI Magnify Glass: Theme button clicked, triggering detection');
+ setTimeout(() => {
+ if (this.detectCurrentTheme()) {
+ this.notifyThemeChange();
+ }
+ }, 100);
+ }
+ });
+ }
+
+ notifyThemeChange() {
+ // Update settings to reflect new theme
+ this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme;
+
+ // Notify UI components of theme change immediately
+ if (window.infoPanelManager && window.infoPanelManager.uiManager) {
+ window.infoPanelManager.uiManager.updateTheme(this.state.currentTheme);
+ }
+ }
+
loadSettings() {
Object.keys(DEFAULT_SETTINGS).forEach(key => {
- this.state.settings[key] = getSettingValue(key, DEFAULT_SETTINGS[key]);
+ // Skip the theme setting since we auto-detect it
+ if (key !== "🔍MagnifyGlass.InfoPanelTheme") {
+ this.state.settings[key] = getSettingValue(key, DEFAULT_SETTINGS[key]);
+ }
});
+
+ // Set theme to auto-detected value
+ this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme;
}
updateSettings() {
@@ -149,9 +400,10 @@ app.registerExtension({
const wasPinned = this.state.isPanelPinned;
this.state.isPanelPinned = !this.state.isPanelPinned;
- // When unpinning, also unlock
+ // When unpinning, also unlock and clear auto-pin flag
if (!this.state.isPanelPinned) {
this.state.isPanelLocked = false;
+ this.state.isAutoPinned = false; // Clear auto-pin flag when manually unpinning
}
if (this.state.isPanelPinned && this.state.lastPinnedPosition) {
@@ -266,7 +518,7 @@ app.registerExtension({
// Main panel container
this.elements.panel = document.createElement("div");
this.elements.panel.id = "comfyui-magnify-info-panel-pro-v2";
- this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelTheme"].toLowerCase()}`;
+ this.elements.panel.className = `magnify-info-panel theme-${this.stateManager.state.currentTheme}`;
// Header
this.elements.header = document.createElement("div");
@@ -304,10 +556,10 @@ app.registerExtension({
this.elements.controls = document.createElement("div");
this.elements.controls.className = "floating-controls vertical-layout"; // Default to vertical
this.elements.controls.innerHTML = `
-
+
-
-
+
+
`;
// Insert before the panel in the document body, not as a child
@@ -329,6 +581,7 @@ app.registerExtension({
if (pinBtn) {
pinBtn.classList.toggle('active', this.stateManager.state.isPanelPinned);
pinBtn.title = this.stateManager.state.isPanelPinned ? "Lock Panel" : "Unlock Panel";
+ pinBtn.textContent = this.stateManager.state.isPanelPinned ? "🔒" : "🔓";
}
if (lockBtn) {
@@ -367,11 +620,33 @@ app.registerExtension({
applyStyles() {
const settings = this.stateManager.state.settings;
+ // Apply custom text color via CSS variable if provided
+ const textColor = settings["🔍MagnifyGlass.InfoPanelTextColor"];
+ if (textColor) {
+ // Ensure color has # symbol
+ const normalizedTextColor = textColor.startsWith('#') ? textColor : `#${textColor}`;
+ this.elements.panel.style.setProperty('--info-panel-text-color', normalizedTextColor);
+ }
+
+ // Apply custom accent color via CSS variable if provided
+ const accentColor = settings["🔍MagnifyGlass.InfoPanelAccentColor"];
+ if (accentColor) {
+ // Ensure color has # symbol
+ const normalizedAccentColor = accentColor.startsWith('#') ? accentColor : `#${accentColor}`;
+ this.elements.panel.style.setProperty('--info-panel-accent-color', normalizedAccentColor);
+ }
+
+ // Apply opacity setting if panel is visible (convert percentage to decimal)
+ if (this.stateManager.state.isPanelVisible) {
+ const opacityPercent = settings["🔍MagnifyGlass.InfoPanelOpacity"];
+ this.elements.panel.style.opacity = opacityPercent / 100;
+ }
+
this.elements.panel.style.cssText = `
position: absolute;
width: ${settings["🔍MagnifyGlass.InfoPanelWidth"]}px;
max-height: ${settings["🔍MagnifyGlass.InfoPanelMaxHeight"]}px;
- z-index: 10001;
+ z-index: 99999;
display: none;
opacity: 0;
transform: translateY(-10px);
@@ -379,6 +654,8 @@ app.registerExtension({
pointer-events: auto;
user-select: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ ${textColor ? `--info-panel-text-color: ${textColor.startsWith('#') ? textColor : `#${textColor}`};` : ''}
+ ${accentColor ? `--info-panel-accent-color: ${accentColor.startsWith('#') ? accentColor : `#${accentColor}`};` : ''}
`;
}
@@ -388,6 +665,9 @@ app.registerExtension({
if (this.elements.controls) {
this.elements.controls.style.display = "flex";
}
+ // 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;
// Trigger reflow
this.elements.panel.offsetHeight;
this.elements.panel.classList.add('visible');
@@ -424,7 +704,10 @@ app.registerExtension({
}
updateTheme(newTheme) {
- this.elements.panel.className = this.elements.panel.className.replace(/theme-\w+/, `theme-${newTheme.toLowerCase()}`);
+ if (this.elements.panel) {
+ this.elements.panel.className = this.elements.panel.className.replace(/theme-\w+/, `theme-${newTheme.toLowerCase()}`);
+ console.log(`ComfyUI Magnify Glass: Panel theme updated to ${newTheme}`);
+ }
}
displayInfo(info) {
@@ -571,15 +854,16 @@ app.registerExtension({
updateHeaderSubtitle(info) {
const subtitleElement = this.elements.header.querySelector('.header-subtitle');
if (subtitleElement) {
+ const accentColor = this.stateManager.state.settings["🔍MagnifyGlass.InfoPanelAccentColor"];
if (info.hoveredNode) {
subtitleElement.textContent = `Analyzing: ${info.hoveredNode.title}`;
- subtitleElement.style.color = '#74b9ff';
+ subtitleElement.style.color = accentColor;
} else if (info.media) {
subtitleElement.textContent = `Media: ${info.media.tagName}`;
- subtitleElement.style.color = '#a0d468';
+ subtitleElement.style.color = accentColor;
} else if (info.connection) {
subtitleElement.textContent = `Connection: ${info.connection.type}`;
- subtitleElement.style.color = '#fdcb6e';
+ subtitleElement.style.color = accentColor;
} else {
subtitleElement.textContent = 'Real-time analysis';
subtitleElement.style.color = '';
@@ -763,18 +1047,17 @@ app.registerExtension({
.magnify-info-panel.theme-dark {
background: linear-gradient(135deg, rgba(0, 0, 0, 0.95), rgba(10, 10, 15, 0.95));
- color: #e0e0e0;
+ color: var(--info-panel-text-color, #e0e0e0);
border: 1px solid rgba(100, 100, 120, 0.3);
}
.magnify-info-panel.theme-light {
background: linear-gradient(135deg, rgba(250, 250, 255, 0.95), rgba(240, 240, 250, 0.95));
- color: #2a2a2a;
+ color: var(--info-panel-text-color, #2a2a2a);
border: 1px solid rgba(200, 200, 220, 0.4);
}
.magnify-info-panel.visible {
- opacity: 1 !important;
transform: translateY(0) !important;
}
@@ -803,7 +1086,7 @@ app.registerExtension({
cursor: grabbing;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5), 0 0 0 2px rgba(255, 215, 0, 0.7);
transform: scale(1.02) !important;
- z-index: 10010 !important;
+ z-index: 100001 !important;
transition: none !important;
}
@@ -839,7 +1122,7 @@ app.registerExtension({
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
opacity: 1;
- z-index: 10003;
+ z-index: 100000;
backdrop-filter: blur(8px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
@@ -1008,7 +1291,7 @@ app.registerExtension({
.section-header.expanded {
background: rgba(116, 185, 255, 0.1);
- border-left: 2px solid #74b9ff;
+ border-left: 2px solid var(--info-panel-accent-color, #74b9ff);
}
.info-section[data-section="node"] {
@@ -1030,7 +1313,7 @@ app.registerExtension({
}
.info-section[data-section="node"] .section-title {
- color: #74b9ff;
+ color: var(--info-panel-accent-color, #74b9ff);
font-weight: 700;
}
@@ -1064,7 +1347,7 @@ app.registerExtension({
}
.info-section[data-section="media"] .section-title {
- color: #fdcb6e;
+ color: var(--info-panel-accent-color, #fdcb6e);
font-weight: 700;
}
@@ -1098,12 +1381,12 @@ app.registerExtension({
.section-header:hover .expand-icon {
opacity: 1;
- color: #74b9ff;
+ color: var(--info-panel-accent-color, #74b9ff);
}
.section-header.expanded .expand-icon {
transform: rotate(90deg);
- color: #74b9ff;
+ color: var(--info-panel-accent-color, #74b9ff);
}
.section-content {
@@ -1141,7 +1424,7 @@ app.registerExtension({
.info-value {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
- color: #a0d468;
+ color: var(--info-panel-accent-color, #a0d468);
background: rgba(160, 212, 104, 0.1);
padding: 3px 8px;
border-radius: 4px;
@@ -1442,29 +1725,64 @@ app.registerExtension({
});
}
+ // Helper function to check if user is typing in an input field
+ isUserTyping() {
+ const activeElement = document.activeElement;
+ if (!activeElement) return false;
+
+ // Check if the active element is an input, textarea, or contenteditable
+ const tagName = activeElement.tagName.toLowerCase();
+ if (tagName === 'input' || tagName === 'textarea') {
+ return true;
+ }
+
+ // Check for contenteditable elements
+ if (activeElement.contentEditable === 'true') {
+ return true;
+ }
+
+ // Check if it's inside a form or has input-like classes
+ if (activeElement.closest('form') ||
+ activeElement.classList.contains('cm-editor') || // CodeMirror editor
+ activeElement.classList.contains('monaco-editor') || // Monaco editor
+ activeElement.closest('.cm-editor') ||
+ activeElement.closest('.monaco-editor')) {
+ return true;
+ }
+
+ return false;
+ }
+
setupHotkeyEvents() {
document.addEventListener('keydown', (e) => {
+ // Don't handle hotkeys if user is typing in an input field (Smart Input Detection)
+ if (this.isUserTyping()) {
+ return;
+ }
+
// Only handle hotkeys when magnifying glass is active
if (!magnifyGlass.state.active) return;
const settings = this.stateManager.state.settings;
// Toggle info panel
- if (e.key.toLowerCase() === settings["🔍MagnifyGlass.ToggleHotkey"].toLowerCase() && !e.repeat) {
+ if (e.key.toLowerCase() === settings["🔍MagnifyGlass.ToggleHotkey"].toLowerCase() &&
+ (!magnifyGlass.config.altRequired || e.altKey) && !e.repeat) {
e.preventDefault();
e.stopPropagation();
this.handleControlAction('toggle-panel');
}
// Toggle glass preview
- if (e.key.toLowerCase() === settings["🔍MagnifyGlass.GlassPreviewToggleHotkey"].toLowerCase() && !e.repeat) {
+ if (e.key.toLowerCase() === settings["🔍MagnifyGlass.GlassPreviewToggleHotkey"].toLowerCase() &&
+ (!magnifyGlass.config.altRequired || e.altKey) && !e.repeat) {
e.preventDefault();
e.stopPropagation();
this.handleControlAction('toggle-glass');
}
- // Pin at mouse location with Alt + configured key
- if (e.key.toLowerCase() === settings["🔍MagnifyGlass.PinPanelHotkey"].toLowerCase() && e.altKey && !e.repeat) {
+ // Unlock panel from magnify glass and move to current mouse location (no Alt required due to Smart Input Detection)
+ if (e.key.toLowerCase() === settings["🔍MagnifyGlass.PinPanelHotkey"].toLowerCase() && !e.repeat) {
e.preventDefault();
e.stopPropagation();
this.handleControlAction('pin-at-mouse');
@@ -1754,6 +2072,14 @@ app.registerExtension({
if (!isVisible && !this.stateManager.state.isPanelPinned) {
this.captureCurrentPanelPosition(); // Capture current position before pinning
this.stateManager.togglePinning();
+ this.stateManager.state.isAutoPinned = true; // Mark as auto-pinned
+ this.uiManager.updatePinnedState();
+ }
+
+ // When the glass preview is shown again, only auto-unlock if NOT manually locked with 📌 button
+ if (isVisible && this.stateManager.state.isPanelPinned && !this.stateManager.state.isPanelLocked) {
+ this.stateManager.togglePinning(); // Unpin the panel
+ this.stateManager.state.isAutoPinned = false; // Clear auto-pin flag
this.uiManager.updatePinnedState();
}
@@ -2170,10 +2496,8 @@ app.registerExtension({
updateSettings() {
const changes = this.stateManager.updateSettings();
- // React to setting changes
- if (changes["🔍MagnifyGlass.InfoPanelTheme"]) {
- this.uiManager.updateTheme(changes["🔍MagnifyGlass.InfoPanelTheme"].new);
- }
+ // React to setting changes (theme is now auto-detected)
+ // Theme changes are handled automatically by the observer
if (changes["🔍MagnifyGlass.ControlsPosition"]) {
// Update controls layout and position when position setting changes
@@ -2199,12 +2523,15 @@ app.registerExtension({
// Create the main manager instance
const infoPanelManager = new ProfessionalInfoPanelManager(magnifyGlass);
- // Store reference globally for cleanup
+ // Store reference globally for cleanup and theme updates
if (!window.comfyUIMagnifyGlassExtensions) {
window.comfyUIMagnifyGlassExtensions = [];
}
window.comfyUIMagnifyGlassExtensions.push(infoPanelManager);
+ // Store main manager reference for theme updates
+ window.infoPanelManager = infoPanelManager;
+
// Register settings with ComfyUI
Object.keys(DEFAULT_SETTINGS).forEach(settingKey => {
const settingConfig = getSettingConfig(settingKey, DEFAULT_SETTINGS[settingKey]);
@@ -2256,13 +2583,20 @@ app.registerExtension({
},
"🔍MagnifyGlass.InfoPanelOpacity": {
id: key,
- name: "📋 Magnify Glass: Info Panel Opacity",
+ name: "📋 Magnify Glass: Info Panel Opacity (%)",
type: "slider",
defaultValue,
- min: 0.7,
- max: 1.0,
- step: 0.05,
- tooltip: "Opacity of the information panel background."
+ min: 10,
+ max: 100,
+ step: 5,
+ tooltip: "Opacity of the information panel background as a percentage (10 = very transparent, 100 = fully opaque).",
+ onChange: (value) => {
+ // Convert percentage to decimal and apply opacity immediately
+ const opacity = value / 100;
+ if (window.infoPanelManager && window.infoPanelManager.uiManager && window.infoPanelManager.uiManager.elements.panel) {
+ window.infoPanelManager.uiManager.elements.panel.style.opacity = opacity;
+ }
+ }
},
"🔍MagnifyGlass.InfoPanelMaxHeight": {
id: key,
@@ -2274,17 +2608,7 @@ app.registerExtension({
step: 25,
tooltip: "Maximum height of the information panel in pixels."
},
- "🔍MagnifyGlass.InfoPanelTheme": {
- id: key,
- name: "🎨 Magnify Glass: Info Panel Theme",
- type: "combo",
- options: [
- { value: "Dark", text: "Dark" },
- { value: "Light", text: "Light" }
- ],
- defaultValue,
- tooltip: "Color theme for the information panel."
- },
+
"🔍MagnifyGlass.InfoPanelAnimations": {
id: key,
name: "🎬 Magnify Glass: Info Panel Animations",
@@ -2325,11 +2649,11 @@ app.registerExtension({
},
"🔍MagnifyGlass.PinPanelHotkey": {
id: key,
- name: "📌 Magnify Glass: Pin Panel Hotkey",
+ name: "🔓 Magnify Glass: Unlock Panel to Mouse Location",
type: "combo",
- options: ["p", "f", "x", "z", "h"],
+ options: ["u", "p", "f", "x", "z"],
defaultValue,
- tooltip: "Key to use with Alt to pin the info panel at mouse location (e.g., Alt+P)."
+ tooltip: "Key to unlock the info panel from the magnify glass and move it to your current mouse position (e.g., U). Panel remains movable - use the pin button to lock it in place. Alt not required due to Smart Input Detection."
},
"🔍MagnifyGlass.ShowHoveringControls": {
id: key,
@@ -2358,6 +2682,56 @@ app.registerExtension({
],
defaultValue,
tooltip: "Position of the floating control buttons relative to the info panel."
+ },
+ "🔍MagnifyGlass.InfoPanelTextColor": {
+ id: key,
+ name: "🎨 Magnify Glass: Info Panel Text Color",
+ type: "color",
+ defaultValue,
+ tooltip: "Color of the text in the info panel. This affects all text content including titles, values, and labels.",
+ onChange: (value) => {
+ // Ensure color value includes # symbol for valid CSS and persistence
+ const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value;
+
+ // Only save back if we actually normalized the color (to prevent recursion)
+ if (value !== normalizedColor) {
+ try {
+ app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelTextColor", normalizedColor);
+ } catch (e) {
+ console.warn('Failed to save normalized text color:', e);
+ }
+ }
+
+ // Apply the color immediately
+ if (window.infoPanelManager && window.infoPanelManager.uiManager) {
+ window.infoPanelManager.uiManager.applyStyles();
+ }
+ }
+ },
+ "🔍MagnifyGlass.InfoPanelAccentColor": {
+ id: key,
+ name: "🎨 Magnify Glass: Info Panel Accent Color",
+ type: "color",
+ defaultValue,
+ tooltip: "Color used for highlighted elements like values, section titles, and active states in the info panel.",
+ onChange: (value) => {
+ // Ensure color value includes # symbol for valid CSS and persistence
+ const normalizedColor = value && !value.startsWith('#') ? `#${value}` : value;
+
+ // Only save back if we actually normalized the color (to prevent recursion)
+ if (value !== normalizedColor) {
+ try {
+ app.ui.settings.setSettingValue("🔍MagnifyGlass.InfoPanelAccentColor", normalizedColor);
+ } catch (e) {
+ console.warn('Failed to save normalized accent color:', e);
+ }
+ }
+
+ // Apply the color immediately
+ if (window.infoPanelManager && window.infoPanelManager.uiManager) {
+ window.infoPanelManager.uiManager.applyStyles();
+ }
+ }
}
};