Full codebase including API server (32 modules + routes), dashboard frontend, DashCA certificate distribution, installer script, and deployment skills.
308 lines
9.8 KiB
JavaScript
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);
|