/** * First-Time Setup Script for Nebula Browser * Handles theme selection, default browser setup, and first-run completion */ // State management const setupState = { currentStep: 1, selectedTheme: 'default', defaultBrowserSet: false, skipped: false, themes: [] }; function hasNebulaNativeBridge() { return !!(window.nebulaNative && typeof window.nebulaNative.postMessage === 'function'); } function getPresetThemes() { if (typeof BrowserCustomizer === 'function') { const customizer = new BrowserCustomizer({ skipInit: true }); return customizer.predefinedThemes || { default: customizer.defaultTheme }; } return { default: { name: 'Default', colors: { bg: '#121418', darkBlue: '#0B1C2B', darkPurple: '#1B1035', primary: '#7B2EFF', accent: '#00C6FF', text: '#E0E0E0', urlBarBg: '#1C2030', urlBarText: '#E0E0E0', urlBarBorder: '#3E4652', tabBg: '#161925', tabText: '#A4A7B3', tabActive: '#1C2030', tabActiveText: '#E0E0E0', tabBorder: '#2B3040' }, layout: 'centered', showLogo: true, customTitle: 'Nebula Browser', gradient: 'linear-gradient(145deg, #121418 0%, #1B1035 100%)' } }; } function normalizeTheme(theme) { const fallback = getPresetThemes().default; return { ...fallback, ...(theme || {}), colors: { ...fallback.colors, ...((theme && theme.colors) || {}) } }; } const nativeApi = window.api || { async getAllThemes() { return { default: getPresetThemes() }; }, async isDefaultBrowser() { return false; }, async setAsDefaultBrowser() { return { success: false, error: 'Default browser setup is handled by the native CEF app.' }; }, async applyTheme(themeId) { const theme = getThemeById(themeId); if (theme) { localStorage.setItem('currentTheme', JSON.stringify(normalizeTheme(theme))); } localStorage.setItem('activeThemeName', themeId); }, async completeFirstRun(data) { localStorage.setItem('nebula-first-run-complete', JSON.stringify(data)); if (hasNebulaNativeBridge()) { window.nebulaNative.postMessage('complete-first-run', JSON.stringify(data)); } } }; // Initialize setup when DOM is ready document.addEventListener('DOMContentLoaded', async () => { console.log('[Setup] Initializing first-time setup...'); // Load available themes await loadThemes(); // Initialize button handlers initializeButtons(); // Check default browser status checkDefaultBrowserStatus(); }); /** * Load available themes from main process */ async function loadThemes() { try { const themes = await nativeApi.getAllThemes(); console.log('[Setup] Loaded themes:', themes); setupState.themes = themes; // Render theme grid renderThemeGrid(themes); } catch (error) { console.error('[Setup] Error loading themes:', error); // Fallback to a default theme setupState.themes = { default: getPresetThemes() }; renderThemeGrid(setupState.themes); } } /** * Render theme selection grid */ function renderThemeGrid(themes) { const themeGrid = document.getElementById('theme-grid'); if (!themeGrid) return; themeGrid.innerHTML = ''; // Convert themes object to array let themeArray = []; if (Array.isArray(themes)) { // Already an array themeArray = themes; } else if (themes.default) { // Has default property, extract themes from it themeArray = Object.entries(themes.default).map(([id, data]) => ({ id, name: data.name || id.charAt(0).toUpperCase() + id.slice(1).replace(/-/g, ' '), description: data.description || 'A beautiful color scheme', colors: data.colors || {} })); } else { // Direct object of themes themeArray = Object.entries(themes).map(([id, data]) => ({ id, name: data.name || id.charAt(0).toUpperCase() + id.slice(1).replace(/-/g, ' '), description: data.description || 'A beautiful color scheme', colors: data.colors || {} })); } console.log('[Setup] Rendering', themeArray.length, 'themes'); // If no themes found, add a default one if (themeArray.length === 0) { themeArray = [{ id: 'default', name: 'Default', description: 'Classic Nebula theme', colors: { bg: '#121418', primary: '#7B2EFF', accent: '#00C6FF', text: '#E0E0E0' } }]; } themeArray.forEach(theme => { const themeCard = createThemeCard(theme); themeGrid.appendChild(themeCard); }); // Select default theme const defaultCard = themeGrid.querySelector('[data-theme-id="default"]'); if (defaultCard) { defaultCard.classList.add('selected'); const defaultTheme = getThemeById('default'); if (defaultTheme) { applyThemeToSetupPage(defaultTheme, 'default'); } } } /** * Get a theme by id from loaded theme sets */ function getThemeById(themeId) { const themes = setupState.themes || {}; if (themes.default && themes.default[themeId]) return themes.default[themeId]; if (themes.user && themes.user[themeId]) return themes.user[themeId]; if (themes.downloaded && themes.downloaded[themeId]) return themes.downloaded[themeId]; return null; } function hexToRgb(hex) { if (!hex || typeof hex !== 'string') return null; let normalized = hex.trim().replace(/^#/, ''); if (normalized.length === 3) { normalized = normalized.split('').map(char => char + char).join(''); } if (!/^[a-fA-F\d]{6}$/.test(normalized)) return null; const intValue = parseInt(normalized, 16); return { r: (intValue >> 16) & 255, g: (intValue >> 8) & 255, b: intValue & 255 }; } /** * Apply theme to the setup page UI and persist selection */ function applyThemeToSetupPage(theme, themeId = null) { const completeTheme = normalizeTheme(theme); if (!completeTheme || !completeTheme.colors) return; const colors = completeTheme.colors; const root = document.documentElement; const setVar = (cssVar, value, fallback) => { const val = value || fallback; if (val) root.style.setProperty(cssVar, val); }; setVar('--bg', colors.bg, '#121418'); setVar('--dark-blue', colors.darkBlue, '#0B1C2B'); setVar('--dark-purple', colors.darkPurple, '#1B1035'); setVar('--primary', colors.primary, '#7B2EFF'); setVar('--accent', colors.accent, '#00C6FF'); setVar('--text', colors.text, '#E0E0E0'); setVar('--success', colors.accent, '#4CAF50'); setVar('--warning', colors.primary, '#FF9800'); const primaryRgb = hexToRgb(colors.primary || '#7B2EFF'); const accentRgb = hexToRgb(colors.accent || '#00C6FF'); const successRgb = hexToRgb(colors.accent || '#4CAF50'); const warningRgb = hexToRgb(colors.primary || '#FF9800'); if (primaryRgb) { setVar('--primary-rgb', `${primaryRgb.r}, ${primaryRgb.g}, ${primaryRgb.b}`); } if (accentRgb) { setVar('--accent-rgb', `${accentRgb.r}, ${accentRgb.g}, ${accentRgb.b}`); } if (successRgb) { setVar('--success-rgb', `${successRgb.r}, ${successRgb.g}, ${successRgb.b}`); } if (warningRgb) { setVar('--warning-rgb', `${warningRgb.r}, ${warningRgb.g}, ${warningRgb.b}`); } if (completeTheme.gradient) { document.body.style.background = completeTheme.gradient; } else if (colors.bg) { document.body.style.background = colors.bg; } // Persist for main UI to pick up on first load try { localStorage.setItem('currentTheme', JSON.stringify(completeTheme)); if (themeId) localStorage.setItem('activeThemeName', themeId); } catch (err) { console.warn('[Setup] Failed to persist theme:', err); } } /** * Create a theme card element */ function createThemeCard(theme) { const card = document.createElement('div'); card.className = 'theme-card'; card.dataset.themeId = theme.id; // Create color preview const preview = document.createElement('div'); preview.className = 'theme-preview'; const colors = theme.colors || {}; // Get color values, trying multiple property naming conventions const getColor = (keys, fallback) => { for (const key of keys) { if (colors[key]) return colors[key]; } return fallback; }; const previewColors = [ getColor(['bg', '--bg', 'background'], '#121418'), getColor(['primary', '--primary'], '#7B2EFF'), getColor(['accent', '--accent'], '#00C6FF'), getColor(['text', '--text'], '#E0E0E0') ]; previewColors.forEach(color => { const colorDiv = document.createElement('div'); colorDiv.className = 'theme-color'; colorDiv.style.backgroundColor = color; preview.appendChild(colorDiv); }); // Create theme info const name = document.createElement('div'); name.className = 'theme-name'; name.textContent = theme.name || theme.id; const description = document.createElement('div'); description.className = 'theme-description'; description.textContent = theme.description || 'A beautiful color scheme'; // Assemble card card.appendChild(preview); card.appendChild(name); card.appendChild(description); // Add click handler card.addEventListener('click', () => selectTheme(theme.id, card)); return card; } /** * Select a theme */ function selectTheme(themeId, cardElement) { // Update state setupState.selectedTheme = themeId; // Update UI document.querySelectorAll('.theme-card').forEach(card => { card.classList.remove('selected'); }); cardElement.classList.add('selected'); const theme = getThemeById(themeId); if (theme) { applyThemeToSetupPage(theme, themeId); } console.log('[Setup] Selected theme:', themeId); } /** * Check if Nebula is the default browser */ async function checkDefaultBrowserStatus() { const statusEl = document.getElementById('default-status'); if (!statusEl) return; statusEl.classList.add('checking'); try { const isDefault = await nativeApi.isDefaultBrowser(); statusEl.classList.remove('checking'); if (isDefault) { statusEl.classList.add('is-default'); statusEl.innerHTML = `
Nebula is already your default browser
`; setupState.defaultBrowserSet = true; // Update button const setDefaultBtn = document.getElementById('btn-set-default'); if (setDefaultBtn) { setDefaultBtn.textContent = '✓ Already Default'; setDefaultBtn.disabled = true; } } else { statusEl.classList.add('not-default'); statusEl.innerHTML = `Nebula is not your default browser
`; } } catch (error) { console.error('[Setup] Error checking default browser status:', error); statusEl.classList.remove('checking'); statusEl.innerHTML = `Unable to check default browser status
`; } } /** * Set Nebula as default browser */ async function setDefaultBrowser() { const btn = document.getElementById('btn-set-default'); const statusEl = document.getElementById('default-status'); if (btn) { btn.disabled = true; btn.innerHTML = ' Setting...'; } try { const result = await nativeApi.setAsDefaultBrowser(); if (result.success) { const isDefault = await window.api.isDefaultBrowser(); if (isDefault) { setupState.defaultBrowserSet = true; if (statusEl) { statusEl.classList.remove('not-default'); statusEl.classList.add('is-default'); statusEl.innerHTML = `Nebula is now your default browser!
`; } if (btn) { btn.innerHTML = ' Set Successfully'; } // Auto-advance after a brief delay setTimeout(() => goToStep(4), 1500); return; } if (statusEl) { statusEl.classList.remove('not-default'); statusEl.innerHTML = `System settings opened. Choose Nebula as your default browser to finish.
`; } if (btn) { btn.disabled = false; btn.innerHTML = ' Check Again'; } if (result.needsUserAction && nativeApi.openDefaultBrowserSettings) { try { await nativeApi.openDefaultBrowserSettings(); } catch {} } return; } throw new Error(result.error || 'Failed to set default browser'); } catch (error) { console.error('[Setup] Error setting default browser:', error); if (statusEl) { statusEl.innerHTML = `Failed to set default browser. You can try again from settings.
`; } if (btn) { btn.disabled = false; btn.innerHTML = ' Try Again'; } } } /** * Navigate to a specific step */ function goToStep(stepNumber) { // Hide current step document.querySelectorAll('.setup-step').forEach(step => { step.classList.remove('active'); }); // Show target step const targetStep = document.querySelector(`.setup-step[data-step="${stepNumber}"]`); if (targetStep) { targetStep.classList.add('active'); } // Update progress bar document.querySelectorAll('.progress-step').forEach((step, index) => { const stepNum = index + 1; if (stepNum < stepNumber) { step.classList.add('completed'); step.classList.remove('active'); } else if (stepNum === stepNumber) { step.classList.add('active'); step.classList.remove('completed'); } else { step.classList.remove('active', 'completed'); } }); setupState.currentStep = stepNumber; // Special handling for completion step if (stepNumber === 4) { renderCompletionSummary(); } console.log('[Setup] Navigated to step:', stepNumber); } /** * Render completion summary */ function renderCompletionSummary() { const summaryEl = document.getElementById('completion-summary'); if (!summaryEl) return; const selectedThemeName = setupState.themes.default?.[setupState.selectedTheme]?.name || setupState.selectedTheme.charAt(0).toUpperCase() + setupState.selectedTheme.slice(1); summaryEl.innerHTML = `