286 lines
7.9 KiB
JavaScript
286 lines
7.9 KiB
JavaScript
/**
|
|
* NOSTR_LOGIN_LITE Theme Manager
|
|
* Handles theme loading, switching, and CSS custom property management
|
|
*/
|
|
|
|
class NostrThemeManager {
|
|
constructor() {
|
|
this.currentTheme = null;
|
|
this.availableThemes = new Map();
|
|
this.themeCache = new Map();
|
|
this.basePath = './themes/';
|
|
|
|
// Initialize with default theme
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
try {
|
|
// Load available themes index
|
|
await this.loadThemeIndex();
|
|
|
|
// Set default theme if none is set
|
|
if (!this.currentTheme) {
|
|
await this.loadTheme('default');
|
|
}
|
|
|
|
console.log('NostrThemeManager: Initialized with themes:', Array.from(this.availableThemes.keys()));
|
|
} catch (error) {
|
|
console.error('NostrThemeManager: Initialization failed:', error);
|
|
this.fallbackToInlineStyles();
|
|
}
|
|
}
|
|
|
|
async loadThemeIndex() {
|
|
// For now, we'll manually register available themes
|
|
// In production, this could fetch from a themes.json index file
|
|
this.availableThemes.set('default', {
|
|
name: 'Default Monospace',
|
|
path: 'default',
|
|
description: 'Black/white/red monospace theme'
|
|
});
|
|
|
|
this.availableThemes.set('dark', {
|
|
name: 'Dark Monospace',
|
|
path: 'dark',
|
|
description: 'Dark mode with green accents and monospace typography'
|
|
});
|
|
|
|
// Future themes can be registered here or loaded from an index
|
|
// this.availableThemes.set('cyberpunk', { ... });
|
|
}
|
|
|
|
async loadTheme(themeName) {
|
|
try {
|
|
console.log(`NostrThemeManager: Loading theme "${themeName}"`);
|
|
|
|
// Check if theme exists
|
|
if (!this.availableThemes.has(themeName)) {
|
|
throw new Error(`Theme "${themeName}" not found`);
|
|
}
|
|
|
|
// Check cache first
|
|
if (this.themeCache.has(themeName)) {
|
|
const cachedTheme = this.themeCache.get(themeName);
|
|
this.applyTheme(cachedTheme);
|
|
this.currentTheme = themeName;
|
|
return cachedTheme;
|
|
}
|
|
|
|
// Load theme metadata
|
|
const themeInfo = this.availableThemes.get(themeName);
|
|
const metadataUrl = `${this.basePath}${themeInfo.path}/theme.json`;
|
|
|
|
const response = await fetch(metadataUrl);
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to load theme metadata: ${response.statusText}`);
|
|
}
|
|
|
|
const themeData = await response.json();
|
|
|
|
// Validate theme data
|
|
this.validateThemeData(themeData);
|
|
|
|
// Load CSS file
|
|
await this.loadThemeCSS(themeInfo.path);
|
|
|
|
// Cache the theme data
|
|
this.themeCache.set(themeName, themeData);
|
|
|
|
// Apply the theme
|
|
this.applyTheme(themeData);
|
|
this.currentTheme = themeName;
|
|
|
|
console.log(`NostrThemeManager: Successfully loaded theme "${themeName}"`);
|
|
|
|
// Dispatch theme change event
|
|
this.dispatchThemeChangeEvent(themeName, themeData);
|
|
|
|
return themeData;
|
|
|
|
} catch (error) {
|
|
console.error(`NostrThemeManager: Failed to load theme "${themeName}":`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async loadThemeCSS(themePath) {
|
|
const cssUrl = `${this.basePath}${themePath}/theme.css`;
|
|
|
|
// Remove existing theme CSS
|
|
const existingThemeCSS = document.getElementById('nl-theme-css');
|
|
if (existingThemeCSS) {
|
|
existingThemeCSS.remove();
|
|
}
|
|
|
|
// Load new theme CSS
|
|
const link = document.createElement('link');
|
|
link.id = 'nl-theme-css';
|
|
link.rel = 'stylesheet';
|
|
link.type = 'text/css';
|
|
link.href = cssUrl;
|
|
|
|
// Wait for CSS to load
|
|
await new Promise((resolve, reject) => {
|
|
link.onload = resolve;
|
|
link.onerror = () => reject(new Error(`Failed to load CSS from ${cssUrl}`));
|
|
document.head.appendChild(link);
|
|
});
|
|
}
|
|
|
|
applyTheme(themeData) {
|
|
if (!themeData.variables) {
|
|
console.warn('NostrThemeManager: Theme data has no variables to apply');
|
|
return;
|
|
}
|
|
|
|
const root = document.documentElement;
|
|
|
|
// Apply CSS custom properties
|
|
Object.entries(themeData.variables).forEach(([property, value]) => {
|
|
root.style.setProperty(property, value);
|
|
});
|
|
|
|
console.log(`NostrThemeManager: Applied ${Object.keys(themeData.variables).length} CSS variables`);
|
|
}
|
|
|
|
validateThemeData(themeData) {
|
|
const required = ['name', 'version', 'variables'];
|
|
|
|
for (const field of required) {
|
|
if (!themeData[field]) {
|
|
throw new Error(`Theme validation failed: missing required field "${field}"`);
|
|
}
|
|
}
|
|
|
|
if (typeof themeData.variables !== 'object') {
|
|
throw new Error('Theme validation failed: variables must be an object');
|
|
}
|
|
}
|
|
|
|
fallbackToInlineStyles() {
|
|
console.log('NostrThemeManager: Falling back to inline styles');
|
|
|
|
// Apply default theme variables directly
|
|
const defaultVariables = {
|
|
'--nl-primary-color': '#000000',
|
|
'--nl-secondary-color': '#ffffff',
|
|
'--nl-accent-color': '#ff0000',
|
|
'--nl-font-family': '"Courier New", Courier, monospace',
|
|
'--nl-border-radius': '15px',
|
|
'--nl-border-width': '3px',
|
|
'--nl-border-style': 'solid',
|
|
'--nl-padding-button': '12px 16px',
|
|
'--nl-padding-container': '20px 24px',
|
|
'--nl-font-size-base': '14px',
|
|
'--nl-font-size-title': '24px',
|
|
'--nl-font-size-button': '16px',
|
|
'--nl-transition-duration': '0.2s'
|
|
};
|
|
|
|
const root = document.documentElement;
|
|
Object.entries(defaultVariables).forEach(([property, value]) => {
|
|
root.style.setProperty(property, value);
|
|
});
|
|
|
|
this.currentTheme = 'fallback';
|
|
}
|
|
|
|
dispatchThemeChangeEvent(themeName, themeData) {
|
|
if (typeof window !== 'undefined') {
|
|
const event = new CustomEvent('nlThemeChanged', {
|
|
detail: {
|
|
theme: themeName,
|
|
data: themeData,
|
|
timestamp: Date.now()
|
|
}
|
|
});
|
|
window.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
// Public API methods
|
|
getCurrentTheme() {
|
|
return this.currentTheme;
|
|
}
|
|
|
|
getAvailableThemes() {
|
|
return Array.from(this.availableThemes.keys());
|
|
}
|
|
|
|
getThemeInfo(themeName) {
|
|
return this.availableThemes.get(themeName);
|
|
}
|
|
|
|
async switchTheme(themeName) {
|
|
return await this.loadTheme(themeName);
|
|
}
|
|
|
|
getThemeVariable(variableName) {
|
|
if (typeof window === 'undefined') return null;
|
|
|
|
const root = document.documentElement;
|
|
const style = getComputedStyle(root);
|
|
return style.getPropertyValue(variableName);
|
|
}
|
|
|
|
setThemeVariable(variableName, value) {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const root = document.documentElement;
|
|
root.style.setProperty(variableName, value);
|
|
}
|
|
|
|
resetTheme() {
|
|
const root = document.documentElement;
|
|
|
|
// Remove all nl- prefixed custom properties
|
|
const style = getComputedStyle(root);
|
|
for (let i = 0; i < style.length; i++) {
|
|
const property = style[i];
|
|
if (property.startsWith('--nl-')) {
|
|
root.style.removeProperty(property);
|
|
}
|
|
}
|
|
|
|
// Remove theme CSS
|
|
const themeCSS = document.getElementById('nl-theme-css');
|
|
if (themeCSS) {
|
|
themeCSS.remove();
|
|
}
|
|
|
|
this.currentTheme = null;
|
|
}
|
|
|
|
// Theme creation utilities (for developers)
|
|
exportCurrentTheme() {
|
|
const root = document.documentElement;
|
|
const style = getComputedStyle(root);
|
|
const variables = {};
|
|
|
|
for (let i = 0; i < style.length; i++) {
|
|
const property = style[i];
|
|
if (property.startsWith('--nl-')) {
|
|
variables[property] = style.getPropertyValue(property);
|
|
}
|
|
}
|
|
|
|
return {
|
|
name: 'Custom Theme',
|
|
version: '1.0.0',
|
|
author: 'User',
|
|
description: 'Exported theme',
|
|
variables,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
}
|
|
|
|
// Export for use in NOSTR_LOGIN_LITE
|
|
if (typeof window !== 'undefined') {
|
|
window.NostrThemeManager = NostrThemeManager;
|
|
console.log('NostrThemeManager: Class available globally');
|
|
} else {
|
|
// Node.js environment
|
|
module.exports = NostrThemeManager;
|
|
} |