Add first-run setup and theme synchronization
Introduce first-run setup flow and live chrome theme syncing. - Add first_run_state.cpp/.h to read/write a first_run_state.json under user data and decide whether to show the setup UI. - Wire first-run logic into NebulaController: track first_run_setup_active_, create initial setup tab, defer/bring up chrome browser accordingly, and add CompleteFirstRunSetup() to persist state and finish setup. - Add SendThemeToChromeSurfaces() and handle "theme-update" and "complete-first-run" chrome commands; restrict setup completion to setup frame. - Expose GetFirstRunStatePath() and GetSetupUrl() in UI path helpers and include the state file in the build list (CMakeLists.txt). - Update chrome UI: new CSS variables and styles for tabs/url-bar; chrome.js can apply themes (applyTheme), persist/load theme, and listen for storage updates to apply theme changes live. - Update customization.js, settings.js, and setup.js to normalize/persist themes, send theme updates to the native host (or fallback), and communicate completion via the native bridge when available; include customization.js in setup.html. These changes allow the app to run an interactive first-run setup and keep the separate chrome UI in sync with user-selected themes.
This commit is contained in:
+103
-1
@@ -1,5 +1,24 @@
|
||||
const SEARCH_URL = 'https://www.google.com/search?q=';
|
||||
|
||||
const DEFAULT_THEME = {
|
||||
colors: {
|
||||
bg: '#080a0f',
|
||||
darkBlue: '#0e1119',
|
||||
darkPurple: '#141824',
|
||||
primary: '#7b2eff',
|
||||
accent: '#00c6ff',
|
||||
text: '#e8e8f0',
|
||||
urlBarBg: '#1c2030',
|
||||
urlBarText: '#e0e0e0',
|
||||
urlBarBorder: '#3e4652',
|
||||
tabBg: '#161925',
|
||||
tabText: '#a4a7b3',
|
||||
tabActive: '#1c2030',
|
||||
tabActiveText: '#e0e0e0',
|
||||
tabBorder: '#2b3040'
|
||||
}
|
||||
};
|
||||
|
||||
const state = {
|
||||
id: 1,
|
||||
url: '',
|
||||
@@ -12,6 +31,79 @@ const state = {
|
||||
tabs: []
|
||||
};
|
||||
|
||||
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 value = parseInt(normalized, 16);
|
||||
return {
|
||||
r: (value >> 16) & 255,
|
||||
g: (value >> 8) & 255,
|
||||
b: value & 255
|
||||
};
|
||||
}
|
||||
|
||||
function isDarkColor(hex) {
|
||||
const rgb = hexToRgb(hex);
|
||||
if (!rgb) return true;
|
||||
const luminance = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
|
||||
return luminance < 0.5;
|
||||
}
|
||||
|
||||
function setCssVar(name, value, fallback) {
|
||||
document.documentElement.style.setProperty(name, value || fallback);
|
||||
}
|
||||
|
||||
function normalizeTheme(theme) {
|
||||
const colors = theme?.colors || {};
|
||||
return {
|
||||
...DEFAULT_THEME,
|
||||
...(theme || {}),
|
||||
colors: {
|
||||
...DEFAULT_THEME.colors,
|
||||
...colors
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
const normalized = normalizeTheme(theme);
|
||||
const colors = normalized.colors;
|
||||
|
||||
setCssVar('--bg', colors.bg, DEFAULT_THEME.colors.bg);
|
||||
setCssVar('--surface', colors.darkBlue, DEFAULT_THEME.colors.darkBlue);
|
||||
setCssVar('--surface-raised', colors.darkPurple, DEFAULT_THEME.colors.darkPurple);
|
||||
setCssVar('--text', colors.text, DEFAULT_THEME.colors.text);
|
||||
setCssVar('--muted', colors.tabText, DEFAULT_THEME.colors.tabText);
|
||||
setCssVar('--primary', colors.primary, DEFAULT_THEME.colors.primary);
|
||||
setCssVar('--accent', colors.accent, DEFAULT_THEME.colors.accent);
|
||||
setCssVar('--accent-2', colors.accent, DEFAULT_THEME.colors.accent);
|
||||
setCssVar('--outline', colors.tabBorder, DEFAULT_THEME.colors.tabBorder);
|
||||
setCssVar('--url-bar-bg', colors.urlBarBg, colors.darkBlue);
|
||||
setCssVar('--url-bar-text', colors.urlBarText, colors.text);
|
||||
setCssVar('--url-bar-border', colors.urlBarBorder, colors.primary);
|
||||
setCssVar('--tab-bg', colors.tabBg, colors.darkBlue);
|
||||
setCssVar('--tab-text', colors.tabText, colors.text);
|
||||
setCssVar('--tab-active', colors.tabActive, colors.darkPurple);
|
||||
setCssVar('--tab-active-text', colors.tabActiveText, colors.text);
|
||||
setCssVar('--tab-border', colors.tabBorder, colors.darkBlue);
|
||||
|
||||
document.documentElement.style.colorScheme = isDarkColor(colors.bg) ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function applySavedTheme() {
|
||||
try {
|
||||
const savedTheme = localStorage.getItem('currentTheme');
|
||||
if (savedTheme) applyTheme(JSON.parse(savedTheme));
|
||||
} catch (error) {
|
||||
console.warn('[Chrome] Failed to apply saved theme:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function toNavigationUrl(input) {
|
||||
const value = (input || '').trim();
|
||||
if (!value) return null;
|
||||
@@ -175,9 +267,19 @@ function wireCommands() {
|
||||
});
|
||||
}
|
||||
|
||||
window.NebulaChrome = { applyState, postCommand, toNavigationUrl };
|
||||
window.NebulaChrome = { applyState, applyTheme, postCommand, toNavigationUrl };
|
||||
|
||||
window.addEventListener('storage', event => {
|
||||
if (event.key !== 'currentTheme' || !event.newValue) return;
|
||||
try {
|
||||
applyTheme(JSON.parse(event.newValue));
|
||||
} catch (error) {
|
||||
console.warn('[Chrome] Failed to apply updated theme:', error);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
applySavedTheme();
|
||||
wireCommands();
|
||||
applyState(state);
|
||||
});
|
||||
|
||||
+12
-4
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
class BrowserCustomizer {
|
||||
constructor() {
|
||||
constructor(options = {}) {
|
||||
this.defaultTheme = {
|
||||
name: 'Default',
|
||||
colors: {
|
||||
@@ -286,6 +286,10 @@ class BrowserCustomizer {
|
||||
}
|
||||
};
|
||||
|
||||
if (options.skipInit) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentTheme = this.loadTheme();
|
||||
this.activeThemeName = this.loadActiveThemeName();
|
||||
this.init();
|
||||
@@ -584,9 +588,13 @@ class BrowserCustomizer {
|
||||
// This will be called to apply theme to home.html and other pages
|
||||
this.saveTheme();
|
||||
|
||||
// Send theme update to host (for settings webview)
|
||||
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||
window.electronAPI.sendToHost('theme-update', this.currentTheme);
|
||||
const themePayload = JSON.stringify(this.currentTheme);
|
||||
|
||||
// Send theme update to host so the separate chrome browser can update live.
|
||||
if (window.nebulaNative && typeof window.nebulaNative.postMessage === 'function') {
|
||||
window.nebulaNative.postMessage('theme-update', themePayload);
|
||||
} else if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||
window.electronAPI.sendToHost('theme-update', themePayload);
|
||||
}
|
||||
// Fallback: send via postMessage (for iframe embedding)
|
||||
try {
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ function attachClearHandler(btn) {
|
||||
} finally {
|
||||
const currentTheme = window.browserCustomizer ? window.browserCustomizer.currentTheme : null;
|
||||
if (currentTheme && window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||
window.electronAPI.sendToHost('theme-update', currentTheme);
|
||||
window.electronAPI.sendToHost('theme-update', JSON.stringify(currentTheme));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+70
-19
@@ -12,17 +12,58 @@ const setupState = {
|
||||
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: {
|
||||
default: {
|
||||
name: 'Default',
|
||||
description: 'Classic Nebula theme',
|
||||
colors: { bg: '#121418', primary: '#7B2EFF', accent: '#00C6FF', text: '#E0E0E0' }
|
||||
}
|
||||
}
|
||||
};
|
||||
return { default: getPresetThemes() };
|
||||
},
|
||||
async isDefaultBrowser() {
|
||||
return false;
|
||||
@@ -31,10 +72,17 @@ const nativeApi = window.api || {
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,9 +115,7 @@ async function loadThemes() {
|
||||
console.error('[Setup] Error loading themes:', error);
|
||||
// Fallback to a default theme
|
||||
setupState.themes = {
|
||||
default: {
|
||||
default: { name: 'Default', description: 'Classic Nebula theme', colors: { bg: '#121418', primary: '#7B2EFF', accent: '#00C6FF' } }
|
||||
}
|
||||
default: getPresetThemes()
|
||||
};
|
||||
renderThemeGrid(setupState.themes);
|
||||
}
|
||||
@@ -167,8 +213,9 @@ function hexToRgb(hex) {
|
||||
* Apply theme to the setup page UI and persist selection
|
||||
*/
|
||||
function applyThemeToSetupPage(theme, themeId = null) {
|
||||
if (!theme || !theme.colors) return;
|
||||
const colors = theme.colors;
|
||||
const completeTheme = normalizeTheme(theme);
|
||||
if (!completeTheme || !completeTheme.colors) return;
|
||||
const colors = completeTheme.colors;
|
||||
const root = document.documentElement;
|
||||
|
||||
const setVar = (cssVar, value, fallback) => {
|
||||
@@ -202,15 +249,15 @@ function applyThemeToSetupPage(theme, themeId = null) {
|
||||
setVar('--warning-rgb', `${warningRgb.r}, ${warningRgb.g}, ${warningRgb.b}`);
|
||||
}
|
||||
|
||||
if (theme.gradient) {
|
||||
document.body.style.background = theme.gradient;
|
||||
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(theme));
|
||||
localStorage.setItem('currentTheme', JSON.stringify(completeTheme));
|
||||
if (themeId) localStorage.setItem('activeThemeName', themeId);
|
||||
} catch (err) {
|
||||
console.warn('[Setup] Failed to persist theme:', err);
|
||||
@@ -499,7 +546,9 @@ async function completeSetup() {
|
||||
|
||||
console.log('[Setup] First-time setup completed successfully');
|
||||
|
||||
window.location.href = 'home.html';
|
||||
if (!hasNebulaNativeBridge()) {
|
||||
window.location.href = 'home.html';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Setup] Error completing setup:', error);
|
||||
alert('There was an error saving your preferences. Please try again.');
|
||||
@@ -522,7 +571,9 @@ async function skipSetup() {
|
||||
|
||||
console.log('[Setup] Setup skipped, using defaults');
|
||||
|
||||
window.location.href = 'home.html';
|
||||
if (!hasNebulaNativeBridge()) {
|
||||
window.location.href = 'home.html';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Setup] Error skipping setup:', error);
|
||||
window.location.href = 'home.html';
|
||||
|
||||
Reference in New Issue
Block a user