Files
dashcaddy/status/js/theme-adapter.js
Sami f61e85d9a7 Initial commit: DashCaddy v1.0
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
2026-03-05 02:26:12 -08:00

308 lines
9.8 KiB
JavaScript

/**
* Theme Adapter
* Ensures tooltips match the current dashboard theme
* Integrates with Driver.js to apply theme-specific styling
*/
(function(window) {
'use strict';
/**
* Theme configuration mapping for Driver.js
* Maps dashboard themes to Driver.js styling
*/
const THEME_CONFIGS = {
dark: {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
light: {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent-strong)',
overlayColor: 'rgba(0, 0, 0, 0.5)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent-strong)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
blue: {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(25, 8, 172, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
nord: {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(46, 52, 64, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
dracula: {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(40, 42, 54, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
'solarized-dark': {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(0, 43, 54, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
},
'solarized-light': {
backgroundColor: 'var(--card-base)',
textColor: 'var(--fg)',
primaryColor: 'var(--accent)',
overlayColor: 'rgba(253, 246, 227, 0.7)',
borderColor: 'var(--border)',
highlightColor: 'var(--accent)',
fontFamily: "'Sami Grotesk', 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif"
}
};
/**
* ThemeAdapter class
* Manages theme integration for the tooltip system
*/
class ThemeAdapter {
constructor() {
this.currentTheme = this.getCurrentTheme();
this.themeChangeCallbacks = [];
this._setupThemeChangeListener();
}
/**
* Get the current theme name from document root class
* @returns {string} Current theme name (e.g., 'dark', 'light', 'blue')
*/
getCurrentTheme() {
const root = document.documentElement;
const classList = Array.from(root.classList);
// Check all known themes (built-in + user) except 'dark' (no class = dark)
const allThemes = (window.THEMES || []).filter(t => t !== 'dark');
const foundTheme = allThemes.find(theme => classList.includes(theme));
return foundTheme || 'dark';
}
/**
* Get Driver.js theme configuration for current theme
* @returns {Object} Theme configuration object
*/
getDriverTheme() {
const themeName = this.getCurrentTheme();
const config = THEME_CONFIGS[themeName] || THEME_CONFIGS.dark;
// Resolve CSS variables to actual values
const resolvedConfig = {};
for (const [key, value] of Object.entries(config)) {
if (typeof value === 'string' && value.startsWith('var(')) {
// Extract CSS variable name
const varName = value.match(/var\((--[^)]+)\)/)?.[1];
if (varName) {
const computedValue = getComputedStyle(document.documentElement)
.getPropertyValue(varName)
.trim();
resolvedConfig[key] = computedValue || value;
} else {
resolvedConfig[key] = value;
}
} else {
resolvedConfig[key] = value;
}
}
return resolvedConfig;
}
/**
* Register a callback for theme changes
* @param {Function} callback - Function to call when theme changes
*/
onThemeChange(callback) {
if (typeof callback === 'function') {
this.themeChangeCallbacks.push(callback);
}
}
/**
* Setup theme change listener using MutationObserver
* @private
*/
_setupThemeChangeListener() {
const root = document.documentElement;
// Create observer to watch for class changes on root element
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const newTheme = this.getCurrentTheme();
if (newTheme !== this.currentTheme) {
const oldTheme = this.currentTheme;
this.currentTheme = newTheme;
this._notifyThemeChange(newTheme, oldTheme);
}
}
});
});
// Start observing
observer.observe(root, {
attributes: true,
attributeFilter: ['class']
});
console.log('[ThemeAdapter] Theme change listener initialized');
}
/**
* Notify all registered callbacks of theme change
* @private
* @param {string} newTheme - New theme name
* @param {string} oldTheme - Old theme name
*/
_notifyThemeChange(newTheme, oldTheme) {
console.log(`[ThemeAdapter] Theme changed: ${oldTheme}${newTheme}`);
this.themeChangeCallbacks.forEach(callback => {
try {
callback(newTheme, oldTheme);
} catch (error) {
console.error('[ThemeAdapter] Error in theme change callback:', error);
}
});
}
/**
* Apply theme to Driver.js instance
* @param {Object} driver - Driver.js instance
*/
applyTheme(driver) {
if (!driver) {
console.warn('[ThemeAdapter] No driver instance provided');
return;
}
const themeConfig = this.getDriverTheme();
// Apply theme configuration to driver
// Note: Driver.js v1.0+ uses CSS variables, so we inject a style element
this._injectDriverStyles(themeConfig);
console.log('[ThemeAdapter] Theme applied to driver:', this.currentTheme);
}
/**
* Inject custom styles for Driver.js based on theme
* @private
* @param {Object} themeConfig - Theme configuration
*/
_injectDriverStyles(themeConfig) {
// Remove existing theme styles
const existingStyle = document.getElementById('driver-theme-styles');
if (existingStyle) {
existingStyle.remove();
}
// Create new style element
const style = document.createElement('style');
style.id = 'driver-theme-styles';
style.textContent = `
.driver-popover {
background: ${themeConfig.backgroundColor} !important;
color: ${themeConfig.textColor} !important;
border: 1px solid ${themeConfig.borderColor} !important;
border-radius: 12px !important;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4) !important;
font-family: ${themeConfig.fontFamily} !important;
}
.driver-popover-title {
color: ${themeConfig.textColor} !important;
font-weight: 600 !important;
font-family: ${themeConfig.fontFamily} !important;
}
.driver-popover-description {
color: ${themeConfig.textColor} !important;
font-family: ${themeConfig.fontFamily} !important;
}
.driver-popover-footer button {
background: ${themeConfig.primaryColor} !important;
color: ${themeConfig.backgroundColor} !important;
border: none !important;
font-family: ${themeConfig.fontFamily} !important;
font-weight: 500 !important;
}
.driver-popover-footer button:hover {
opacity: 0.9 !important;
}
.driver-popover-close-btn {
color: ${themeConfig.textColor} !important;
}
.driver-overlay {
background: ${themeConfig.overlayColor} !important;
}
.driver-highlighted-element {
outline: 2px solid ${themeConfig.highlightColor} !important;
outline-offset: 4px !important;
}
.driver-popover-progress-text {
color: ${themeConfig.textColor} !important;
opacity: 0.7 !important;
font-family: ${themeConfig.fontFamily} !important;
}
`;
document.head.appendChild(style);
}
/**
* Get all available theme names
* @returns {string[]} Array of theme names
*/
getAvailableThemes() {
return Object.keys(THEME_CONFIGS);
}
/**
* Check if a theme is available
* @param {string} themeName - Theme name to check
* @returns {boolean} True if theme is available
*/
isThemeAvailable(themeName) {
return THEME_CONFIGS.hasOwnProperty(themeName);
}
}
// Export to global scope
window.ThemeAdapter = ThemeAdapter;
console.log('[ThemeAdapter] Module loaded');
})(window);