415 lines
15 KiB
JavaScript
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
|
|
};
|