Files
AEmotionStudio-ComfyUI-Magn…/web/info-panel/StateManager.js
T

415 lines
15 KiB
JavaScript

var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { getSettingValue, clamp } from "../shared/utils.js";
import { DEFAULT_PANEL_SETTINGS } from "../shared/settings/defaults.js";
import "/scripts/app.js";
class StateManager {
constructor() {
__publicField(this, "state");
this.state = {
// Panel visibility and positioning
isPanelVisible: false,
wasPanelVisibleBeforeHide: false,
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
// Glass visibility
isGlassPreviewVisible: true,
isInfoHeld: false,
// Section expansion
expandedSections: /* @__PURE__ */ new Set(["node"]),
// Interaction states
isPanelHovered: false,
isHoveringNode: false,
// Auto-collapse
autoExpandTimer: null,
lastNodeId: null,
// Update scheduling
updateScheduled: false,
isInitialLoading: false,
// Current data
currentInfo: null,
// Node selection override
selectedNodeId: null,
// Settings cache
settings: {},
// Auto-detected theme
currentTheme: "dark"
// Will be auto-detected
};
this.initThemeDetection();
this.loadSettings();
}
initThemeDetection() {
this.detectCurrentTheme();
setTimeout(() => {
if (this.detectCurrentTheme()) {
this.notifyThemeChange();
}
}, 1e3);
this.setupThemeObserver();
if (window.matchMedia) {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)");
if (prefersDark.addListener) {
prefersDark.addListener(() => this.detectCurrentTheme());
} else if (prefersDark.addEventListener) {
prefersDark.addEventListener("change", () => this.detectCurrentTheme());
}
}
this.setupThemeButtonListeners();
}
detectCurrentTheme() {
let detectedTheme = "dark";
try {
const colorPalette = getSettingValue("Comfy.ColorPalette", "");
if (colorPalette) {
const paletteStr = String(colorPalette).toLowerCase();
if (paletteStr.includes("obsidian") && paletteStr.includes("dark")) {
detectedTheme = "obsidian-dark";
} else if (paletteStr.includes("obsidian")) {
detectedTheme = "obsidian";
} else if (paletteStr.includes("milk") || paletteStr.includes("white")) {
detectedTheme = "milk-white";
} else if (paletteStr.includes("solarized")) {
detectedTheme = "solarized";
} else if (paletteStr.includes("arc")) {
detectedTheme = "arc";
} else if (paletteStr.includes("nord")) {
detectedTheme = "nord";
} else if (paletteStr.includes("github")) {
detectedTheme = "github";
} else if (paletteStr.includes("light")) {
detectedTheme = "light";
} else if (paletteStr.includes("dark")) {
detectedTheme = "dark";
}
}
} catch (e) {
console.warn("ComfyUI Info Panel: Could not read theme from settings, using fallback detection");
}
if (detectedTheme === "dark") {
const body = document.body;
const html = document.documentElement;
const dataTheme = body.getAttribute("data-theme") || html.getAttribute("data-theme");
if (dataTheme) {
detectedTheme = dataTheme.toLowerCase();
}
const classes = (body.className + " " + html.className).toLowerCase();
if (classes.includes("theme-obsidian-dark") || classes.includes("obsidian-dark")) {
detectedTheme = "obsidian-dark";
} else if (classes.includes("theme-obsidian") || classes.includes("obsidian")) {
detectedTheme = "obsidian";
} else if (classes.includes("theme-milk-white") || classes.includes("milk-white") || classes.includes("milk_white")) {
detectedTheme = "milk-white";
} else if (classes.includes("theme-solarized") || classes.includes("solarized")) {
detectedTheme = "solarized";
} else if (classes.includes("theme-arc") || classes.includes("arc-theme")) {
detectedTheme = "arc";
} else if (classes.includes("theme-nord") || classes.includes("nord-theme")) {
detectedTheme = "nord";
} else if (classes.includes("theme-github") || classes.includes("github-theme")) {
detectedTheme = "github";
} else if (classes.includes("theme-light") || classes.includes("light-theme")) {
detectedTheme = "light";
} else if (classes.includes("theme-dracula") || classes.includes("dracula-theme")) {
detectedTheme = "dracula";
} else if (classes.includes("theme-gruvbox") || classes.includes("gruvbox-theme")) {
detectedTheme = "gruvbox";
} else if (classes.includes("theme-monokai") || classes.includes("monokai-theme")) {
detectedTheme = "monokai";
}
}
if (detectedTheme === "dark") {
const bodyStyles = window.getComputedStyle(document.body);
const bgColor = bodyStyles.backgroundColor;
if (bgColor && bgColor !== "rgba(0, 0, 0, 0)" && bgColor !== "transparent") {
const rgbMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
if (rgbMatch) {
const [, r, g, b] = rgbMatch.map(Number);
const brightness = (r * 299 + g * 587 + b * 114) / 1e3;
if (brightness > 180) {
detectedTheme = "light";
}
}
}
}
if (this.state.currentTheme !== detectedTheme) {
this.state.currentTheme = detectedTheme;
return true;
}
return false;
}
setupThemeObserver() {
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;
}
});
if (shouldCheck && this.detectCurrentTheme()) {
this.notifyThemeChange();
}
});
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 });
}
const themeElements = document.querySelectorAll('[class*="theme"], [class*="dark"], [class*="light"], [data-theme]');
themeElements.forEach((el) => {
observer.observe(el, { attributes: true, attributeOldValue: true });
});
setInterval(() => {
if (this.detectCurrentTheme()) {
this.notifyThemeChange();
}
}, 2e3);
}
setupThemeButtonListeners() {
document.addEventListener("click", (e) => {
var _a, _b, _c, _d;
const target = e.target;
const isThemeButton = ((_a = target.textContent) == null ? void 0 : _a.toLowerCase().includes("theme")) || ((_b = target.textContent) == null ? void 0 : _b.toLowerCase().includes("light")) || ((_c = target.textContent) == null ? void 0 : _c.toLowerCase().includes("dark")) || ((_d = target.title) == null ? void 0 : _d.toLowerCase().includes("theme")) || typeof target.className === "string" && target.className.toLowerCase().includes("theme");
if (isThemeButton) {
setTimeout(() => {
if (this.detectCurrentTheme()) {
this.notifyThemeChange();
}
}, 100);
}
});
}
notifyThemeChange() {
this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme;
if (window.infoPanelManager && window.infoPanelManager.uiManager) {
window.infoPanelManager.uiManager.updateTheme(this.state.currentTheme);
}
const magnifyGlass = window.comfyUIMagnifyGlass;
if (magnifyGlass && magnifyGlass.popOutManager) {
magnifyGlass.popOutManager.updateTheme(this.state.currentTheme);
}
}
loadSettings() {
const defaults = DEFAULT_PANEL_SETTINGS;
Object.keys(defaults).forEach((key) => {
if (key !== "🔍MagnifyGlass.InfoPanelTheme") {
this.state.settings[key] = getSettingValue(key, defaults[key]);
}
});
this.state.settings["🔍MagnifyGlass.InfoPanelWidth"] = this.validateNumber(
this.state.settings["🔍MagnifyGlass.InfoPanelWidth"],
200,
600,
defaults["🔍MagnifyGlass.InfoPanelWidth"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelMaxHeight"] = this.validateNumber(
this.state.settings["🔍MagnifyGlass.InfoPanelMaxHeight"],
200,
1500,
defaults["🔍MagnifyGlass.InfoPanelMaxHeight"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelOpacity"] = this.validateNumber(
this.state.settings["🔍MagnifyGlass.InfoPanelOpacity"],
10,
100,
defaults["🔍MagnifyGlass.InfoPanelOpacity"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelFontSize"] = this.validateNumber(
this.state.settings["🔍MagnifyGlass.InfoPanelFontSize"],
8,
24,
defaults["🔍MagnifyGlass.InfoPanelFontSize"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelTextColor"] = this.validateColor(
this.state.settings["🔍MagnifyGlass.InfoPanelTextColor"],
defaults["🔍MagnifyGlass.InfoPanelTextColor"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelAccentColor"] = this.validateColor(
this.state.settings["🔍MagnifyGlass.InfoPanelAccentColor"],
defaults["🔍MagnifyGlass.InfoPanelAccentColor"]
);
const validPanelPositions = ["Bottom", "Top", "Left", "Right"];
this.state.settings["🔍MagnifyGlass.InfoPanelPosition"] = this.validateStringOption(
this.state.settings["🔍MagnifyGlass.InfoPanelPosition"],
validPanelPositions,
defaults["🔍MagnifyGlass.InfoPanelPosition"]
);
const validControlPositions = ["top-left", "top-right", "bottom-left", "bottom-right", "top", "bottom", "left", "right"];
this.state.settings["🔍MagnifyGlass.ControlsPosition"] = this.validateStringOption(
this.state.settings["🔍MagnifyGlass.ControlsPosition"],
validControlPositions,
defaults["🔍MagnifyGlass.ControlsPosition"]
);
const validFonts = [
"System Default",
"Inter",
"Roboto",
"JetBrains Mono",
"Fira Code",
"IBM Plex Sans",
"Space Grotesk",
"Lexend",
"Outfit",
"monospace",
"system-ui"
];
this.state.settings["🔍MagnifyGlass.InfoPanelFontFamily"] = this.validateStringOption(
this.state.settings["🔍MagnifyGlass.InfoPanelFontFamily"],
validFonts,
defaults["🔍MagnifyGlass.InfoPanelFontFamily"]
);
this.state.settings["🔍MagnifyGlass.InfoPanelTheme"] = this.state.currentTheme;
}
/**
* Validate numeric input with bounds checking.
*/
validateNumber(value, min, max, fallback) {
const num = Number(value);
if (isNaN(num)) return fallback;
return clamp(num, min, max);
}
/**
* Validate color string (hex).
*/
validateColor(color, fallback) {
if (!color || typeof color !== "string") return fallback;
if (/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(color)) {
return color;
}
return fallback;
}
/**
* Validate string against an allowlist.
*/
validateStringOption(value, options, fallback) {
if (typeof value !== "string") return fallback;
return options.includes(value) ? value : fallback;
}
updateSettings() {
const oldSettings = { ...this.state.settings };
this.loadSettings();
const changes = {};
Object.keys(this.state.settings).forEach((key) => {
if (oldSettings[key] !== this.state.settings[key]) {
changes[key] = {
old: oldSettings[key],
new: this.state.settings[key]
};
}
});
return changes;
}
togglePanelVisibility() {
this.state.isPanelVisible = !this.state.isPanelVisible;
return this.state.isPanelVisible;
}
toggleGlassPreview() {
this.state.isGlassPreviewVisible = !this.state.isGlassPreviewVisible;
return this.state.isGlassPreviewVisible;
}
togglePinning() {
const wasPinned = this.state.isPanelPinned;
this.state.isPanelPinned = !this.state.isPanelPinned;
if (!this.state.isPanelPinned) {
this.state.isPanelLocked = false;
this.state.isAutoPinned = false;
} else {
this.state.isAutoPinned = false;
}
if (this.state.isPanelPinned && this.state.lastPinnedPosition) {
this.state.pinnedPosition = { ...this.state.lastPinnedPosition };
} else if (!this.state.isPanelPinned && wasPinned) {
this.state.lastPinnedPosition = { ...this.state.pinnedPosition };
}
return this.state.isPanelPinned;
}
toggleLocking() {
if (!this.state.isPanelPinned) {
return false;
}
this.state.isPanelLocked = !this.state.isPanelLocked;
return this.state.isPanelLocked;
}
toggleMinimized() {
this.state.isPanelMinimized = !this.state.isPanelMinimized;
return this.state.isPanelMinimized;
}
toggleHold() {
this.state.isInfoHeld = !this.state.isInfoHeld;
return this.state.isInfoHeld;
}
toggleSection(sectionId) {
if (sectionId === "node") return false;
if (this.state.expandedSections.has(sectionId)) {
this.state.expandedSections.delete(sectionId);
} else {
this.state.expandedSections.add(sectionId);
}
return true;
}
setPinnedPosition(x, y) {
this.state.pinnedPosition = { x, y };
this.state.lastPinnedPosition = { x, y };
}
setCurrentInfo(info) {
this.state.currentInfo = info;
}
/**
* Set the selected node ID for manual override.
* When set, the inspector will display this node instead of the hovered node.
*/
setSelectedNode(id) {
this.state.selectedNodeId = id;
}
/**
* Clear the selected node, returning to hover-based detection.
*/
clearSelectedNode() {
this.state.selectedNodeId = null;
}
scheduleAutoCollapse() {
this.clearAutoExpandTimer();
if (this.state.settings["🔍MagnifyGlass.InfoPanelAnimations"]) {
this.state.autoExpandTimer = setTimeout(() => {
if (!this.state.isPanelHovered) {
this.collapseNodeSections();
}
}, 1500);
}
}
clearAutoExpandTimer() {
if (this.state.autoExpandTimer) {
clearTimeout(this.state.autoExpandTimer);
this.state.autoExpandTimer = null;
}
}
expandNodeSections() {
this.state.expandedSections.add("hoveredNode");
this.state.expandedSections.add("node");
this.state.expandedSections.add("cursor");
this.state.expandedSections.add("canvas");
this.state.expandedSections.add("magnifier");
this.clearAutoExpandTimer();
}
collapseNodeSections() {
this.state.expandedSections.delete("hoveredNode");
this.state.expandedSections.delete("node");
this.state.expandedSections.delete("widget");
}
cleanup() {
this.clearAutoExpandTimer();
}
}
export {
StateManager
};