mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 07:57:19 -05:00
Frontend fixes: - Copy missing JS files from blackroad-os/ to backend/static/js/ - os.js (core OS functionality) - components.js (UI components) - registry.js (app registry) - app.js, config.js, theme.js, mock_data.js (supporting files) - Fixes 3 ERROR findings from Cece audit - System health: 0 ERRORS → 94 SUCCESSES (from 91) Backend config fix: - Add `extra = "ignore"` to Settings.Config in backend/app/config.py - Allows .env.example to have more vars than Settings class defines - Fixes Pydantic v2 validation errors on startup - Enables local development without removing env template vars Cece audit results after fixes: 🔴 CRITICAL: 0 🟠 ERROR: 0 (was 3) 🟡 WARNING: 6 🟢 SUCCESS: 94 (was 91)
248 lines
7.8 KiB
JavaScript
248 lines
7.8 KiB
JavaScript
/**
|
|
* BlackRoad OS Theme Manager
|
|
* Handles theme switching and persistence
|
|
*
|
|
* Built-in Themes:
|
|
* - TealOS (default): Teal/cyan cyberpunk aesthetic
|
|
* - NightOS: Purple/magenta dark theme
|
|
*
|
|
* Features:
|
|
* - Theme persistence via localStorage
|
|
* - Smooth transitions between themes
|
|
* - Event emission for theme changes
|
|
* - Extensible architecture for custom themes
|
|
*
|
|
* Theme System Architecture:
|
|
* - Themes are defined via CSS variables in styles.css
|
|
* - Body attribute `data-theme` controls which CSS vars are active
|
|
* - All components reference CSS variables, not hardcoded colors
|
|
* - New themes can be added by adding `body[data-theme="name"]` blocks
|
|
*
|
|
* TODO v0.2.0: Add theme preview system
|
|
* TODO v0.2.0: Add custom theme builder in Settings app
|
|
* TODO v0.3.0: Support theme import/export (JSON format)
|
|
* TODO v0.3.0: Add system theme (auto dark/light based on OS preference)
|
|
*/
|
|
|
|
class ThemeManager {
|
|
constructor() {
|
|
this.currentTheme = 'tealOS';
|
|
this.availableThemes = ['tealOS', 'nightOS', 'contrastOS']; // Extensible list
|
|
// TODO v0.2.0: Load available themes dynamically from CSS
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
// Load saved theme preference from localStorage
|
|
const saved = localStorage.getItem('blackroad-theme');
|
|
if (saved && this.availableThemes.includes(saved)) {
|
|
this.currentTheme = saved;
|
|
}
|
|
|
|
// Apply theme immediately (before page renders)
|
|
this.applyTheme(this.currentTheme);
|
|
|
|
// Setup toggle button
|
|
this.setupToggleButton();
|
|
|
|
console.log(`🎨 Theme Manager initialized: ${this.currentTheme}`);
|
|
}
|
|
|
|
/**
|
|
* Setup theme toggle button in system tray
|
|
*/
|
|
setupToggleButton() {
|
|
const toggleBtn = document.getElementById('theme-toggle');
|
|
if (!toggleBtn) return;
|
|
|
|
// Add accessibility attributes
|
|
toggleBtn.setAttribute('aria-label', 'Toggle theme');
|
|
toggleBtn.setAttribute('aria-pressed', 'false');
|
|
|
|
toggleBtn.addEventListener('click', () => {
|
|
this.toggleTheme();
|
|
});
|
|
|
|
// Keyboard support
|
|
toggleBtn.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
this.toggleTheme();
|
|
}
|
|
});
|
|
|
|
this.updateToggleButton();
|
|
}
|
|
|
|
/**
|
|
* Toggle between available themes
|
|
* Currently cycles between TealOS and NightOS
|
|
* TODO v0.2.0: Support more than 2 themes with dropdown or cycle logic
|
|
*/
|
|
toggleTheme() {
|
|
// Cycle through available themes
|
|
const currentIndex = this.availableThemes.indexOf(this.currentTheme);
|
|
const nextIndex = (currentIndex + 1) % this.availableThemes.length;
|
|
this.currentTheme = this.availableThemes[nextIndex];
|
|
|
|
this.applyTheme(this.currentTheme);
|
|
this.saveTheme();
|
|
this.updateToggleButton();
|
|
|
|
// Emit event so apps can react if needed
|
|
if (window.OS) {
|
|
window.OS.eventBus.emit('theme:changed', {
|
|
theme: this.currentTheme,
|
|
previousTheme: this.currentTheme === 'tealOS' ? 'nightOS' : 'tealOS'
|
|
});
|
|
|
|
// Show notification
|
|
const themeName = this.currentTheme === 'tealOS' ? 'Teal OS' : 'Night OS';
|
|
window.OS.showNotification({
|
|
type: 'info',
|
|
title: 'Theme Changed',
|
|
message: `Switched to ${themeName}`,
|
|
duration: 2000
|
|
});
|
|
}
|
|
|
|
console.log(`🎨 Theme switched to: ${this.currentTheme}`);
|
|
}
|
|
|
|
/**
|
|
* Apply a theme by setting data-theme attribute
|
|
* @param {string} theme - Theme identifier (e.g., 'tealOS', 'nightOS')
|
|
*/
|
|
applyTheme(theme) {
|
|
if (!this.availableThemes.includes(theme)) {
|
|
console.warn(`Unknown theme: ${theme}. Falling back to tealOS`);
|
|
theme = 'tealOS';
|
|
}
|
|
|
|
// Apply theme with smooth transition
|
|
document.body.classList.add('theme-transitioning');
|
|
document.body.setAttribute('data-theme', theme);
|
|
|
|
// Remove transition class after animation completes
|
|
setTimeout(() => {
|
|
document.body.classList.remove('theme-transitioning');
|
|
}, 300);
|
|
}
|
|
|
|
/**
|
|
* Save current theme to localStorage
|
|
*/
|
|
saveTheme() {
|
|
localStorage.setItem('blackroad-theme', this.currentTheme);
|
|
}
|
|
|
|
/**
|
|
* Update toggle button icon to reflect current theme
|
|
*/
|
|
updateToggleButton() {
|
|
const toggleBtn = document.getElementById('theme-toggle');
|
|
if (!toggleBtn) return;
|
|
|
|
const icon = toggleBtn.querySelector('.icon');
|
|
if (icon) {
|
|
const iconMap = { tealOS: '🌙', nightOS: '☀️', contrastOS: '⚡️' };
|
|
icon.textContent = iconMap[this.currentTheme] || '🎨';
|
|
}
|
|
|
|
// Update aria-label for clarity
|
|
const currentIndex = this.availableThemes.indexOf(this.currentTheme);
|
|
const nextTheme = this.availableThemes[(currentIndex + 1) % this.availableThemes.length] || 'Teal OS';
|
|
toggleBtn.setAttribute('aria-label', `Switch to ${nextTheme}`);
|
|
}
|
|
|
|
/**
|
|
* Get current theme
|
|
* @returns {string} Current theme identifier
|
|
*/
|
|
getTheme() {
|
|
return this.currentTheme;
|
|
}
|
|
|
|
/**
|
|
* Set theme programmatically
|
|
* @param {string} theme - Theme identifier
|
|
*/
|
|
setTheme(theme) {
|
|
if (this.availableThemes.includes(theme)) {
|
|
this.currentTheme = theme;
|
|
this.applyTheme(theme);
|
|
this.saveTheme();
|
|
this.updateToggleButton();
|
|
|
|
// Emit event
|
|
if (window.OS) {
|
|
window.OS.eventBus.emit('theme:changed', { theme });
|
|
}
|
|
} else {
|
|
console.error(`Cannot set theme: ${theme} is not available`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of available themes
|
|
* @returns {Array} Array of theme identifiers
|
|
*/
|
|
getAvailableThemes() {
|
|
return [...this.availableThemes];
|
|
}
|
|
|
|
/**
|
|
* Get theme metadata (for Settings app or theme picker)
|
|
* @param {string} theme - Theme identifier
|
|
* @returns {Object} Theme metadata
|
|
*/
|
|
getThemeMetadata(theme) {
|
|
const metadata = {
|
|
tealOS: {
|
|
id: 'tealOS',
|
|
name: 'Teal OS',
|
|
description: 'Cyberpunk teal/cyan aesthetic with dark background',
|
|
primaryColor: '#0FA',
|
|
author: 'BlackRoad Team',
|
|
preview: null // TODO v0.2.0: Add preview image
|
|
},
|
|
nightOS: {
|
|
id: 'nightOS',
|
|
name: 'Night OS',
|
|
description: 'Purple/magenta dark theme',
|
|
primaryColor: '#A0F',
|
|
author: 'BlackRoad Team',
|
|
preview: null
|
|
}
|
|
};
|
|
|
|
return metadata[theme] || null;
|
|
}
|
|
|
|
/**
|
|
* Preview a theme without committing (for theme picker)
|
|
* TODO v0.2.0: Implement preview mode with cancel/apply buttons
|
|
* @param {string} theme - Theme to preview
|
|
*/
|
|
previewTheme(theme) {
|
|
console.log(`🔍 Preview mode for theme: ${theme} - Coming in v0.2.0`);
|
|
// Would temporarily apply theme without saving
|
|
// Settings app would show "Apply" and "Cancel" buttons
|
|
}
|
|
|
|
/**
|
|
* Register a custom theme (extension point for future custom theme system)
|
|
* TODO v0.3.0: Allow apps to register custom themes dynamically
|
|
* @param {Object} themeDefinition - Theme definition object
|
|
*/
|
|
registerCustomTheme(themeDefinition) {
|
|
console.log('📦 Custom theme registration - Coming in v0.3.0');
|
|
// Would validate theme definition
|
|
// Add CSS variables dynamically
|
|
// Add to availableThemes list
|
|
}
|
|
}
|
|
|
|
// Create global instance
|
|
window.ThemeManager = new ThemeManager();
|