diff --git a/CHANGELOG.md b/CHANGELOG.md index 85208f5..3121dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,6 @@ Format guidelines: - Keep it concise: what is new, what changed/improved, what was fixed. - Avoid exhaustive UI/visual layout breakdowns. --> + +## 25.2.1 +- **Improved:** The first start of the portable version shows a progress loading screen in your system language while the data folder is prepared. Later starts open directly without the splash. diff --git a/electron/main.cjs b/electron/main.cjs index 5538fb8..c0c2a85 100644 --- a/electron/main.cjs +++ b/electron/main.cjs @@ -45,7 +45,56 @@ if (!gotSingleInstanceLock) { let mainWindow; let splashWindow = null; -// Show a lightweight branded window while the main workspace initializes. +// First-run setup marker: created in the user data folder (the portable 'data' folder when portable) +// once the initial data preparation has finished. Later launches skip the splash and setup progress. +const FIRST_RUN_MARKER_NAME = 'first-run-setup-complete.flag'; +let firstRunSetupPending = false; +let revealMainWindowFn = null; + +function isFirstRunSetupNeeded() { + try { + return !fs.existsSync(path.join(app.getPath('userData'), FIRST_RUN_MARKER_NAME)); + } catch { + return false; + } +} + +function markFirstRunSetupComplete() { + try { + fs.writeFileSync(path.join(app.getPath('userData'), FIRST_RUN_MARKER_NAME), new Date().toISOString(), 'utf8'); + } catch (err) { + console.warn('[SOCDOF Electron] Could not write first-run marker:', err); + } +} + +// Push a progress update (0-100 and a step key) to the splash window. Step labels are localized in splash.html. +function sendSplashProgress(percent, stepKey) { + if (!splashWindow || splashWindow.isDestroyed()) return; + const send = () => { + if (splashWindow && !splashWindow.isDestroyed()) { + splashWindow.webContents.send('socdof:splash-progress', { percent, stepKey }); + } + }; + if (splashWindow.webContents.isLoading()) { + splashWindow.webContents.once('did-finish-load', send); + } else { + send(); + } +} + +// Finish the first-run setup: store the marker and let the splash hand over to the main window. +function finishFirstRunSetup() { + if (!firstRunSetupPending) return; + firstRunSetupPending = false; + markFirstRunSetupComplete(); + sendSplashProgress(100, 'ready'); + setTimeout(() => { + if (revealMainWindowFn) revealMainWindowFn(); + setTimeout(closeSplashWindow, 400); + }, 300); +} + +// Show a lightweight branded window while the first-run data preparation runs (progress-aware). function createSplashWindow() { if (splashWindow && !splashWindow.isDestroyed()) return; splashWindow = new BrowserWindow({ @@ -70,6 +119,9 @@ function createSplashWindow() { splashWindow.once('ready-to-show', () => { if (splashWindow && !splashWindow.isDestroyed()) splashWindow.show(); }); + splashWindow.webContents.once('did-finish-load', () => { + sendSplashProgress(5, 'start'); + }); splashWindow.on('closed', () => { splashWindow = null; }); splashWindow.loadFile(path.join(__dirname, 'splash.html')); } @@ -527,6 +579,8 @@ function createWindow() { let mainWindowRevealed = false; const revealMainWindow = () => { if (mainWindowRevealed || !mainWindow || mainWindow.isDestroyed()) return; + // On first run the main window stays hidden until the data preparation has finished + if (firstRunSetupPending) return; mainWindowRevealed = true; closeSplashWindow(); mainWindow.setFullScreen(true); @@ -534,6 +588,8 @@ function createWindow() { mainWindow.focus(); }; + revealMainWindowFn = revealMainWindow; + // Reveal window once first paint is completed and DOM is ready mainWindow.once('ready-to-show', revealMainWindow); @@ -1997,18 +2053,26 @@ app.whenReady().then(async () => { const canProceed = await verifyNetworkSessionLock(); if (!canProceed) return; - createSplashWindow(); + // Only the first launch of a data folder shows the progress splash; later launches open directly + firstRunSetupPending = isFirstRunSetupNeeded(); + if (firstRunSetupPending) createSplashWindow(); createWindow(); // Defer background language seed & backup folder readiness so the UI thread renders immediately setImmediate(() => { try { + if (firstRunSetupPending) sendSplashProgress(20, 'folders'); getBackupDirectory(); + if (firstRunSetupPending) sendSplashProgress(45, 'languages'); ensureDefaultLanguageFiles(); + if (firstRunSetupPending) sendSplashProgress(70, 'watchers'); setupLanguagesFolderWatcher(); setupTemplatesWatcher(); + if (firstRunSetupPending) sendSplashProgress(90, 'finishing'); + finishFirstRunSetup(); } catch (e) { console.warn('Background init error:', e); + finishFirstRunSetup(); } }); diff --git a/electron/preload.cjs b/electron/preload.cjs index d4784e0..ead6087 100644 --- a/electron/preload.cjs +++ b/electron/preload.cjs @@ -6,6 +6,11 @@ contextBridge.exposeInMainWorld('electronAPI', { quitApp: () => ipcRenderer.invoke('socdof:quit-app'), downloadAndInstallUpdate: (payload) => ipcRenderer.invoke('socdof:download-and-install-update', payload), testUpdatingSplash: (version) => ipcRenderer.invoke('socdof:test-updating-splash', version), + onSplashProgress: (callback) => { + const listener = (_event, data) => callback(data); + ipcRenderer.on('socdof:splash-progress', listener); + return () => ipcRenderer.removeListener('socdof:splash-progress', listener); + }, onPrepareForUpdate: (callback) => { const listener = (_event, data) => callback(data); ipcRenderer.on('socdof:prepare-for-update', listener); diff --git a/electron/splash.html b/electron/splash.html index dcc7c8b..15e1199 100644 --- a/electron/splash.html +++ b/electron/splash.html @@ -106,6 +106,12 @@ box-shadow: 0 0 10px rgba(88, 101, 242, 0.5); animation: travel 1.4s cubic-bezier(0.4, 0, 0.2, 1) infinite; } +.percent-text { + font-size: 10px; + color: #8e9aff; + margin-top: 8px; + z-index: 2; +} .footer-text { position: absolute; bottom: 16px; @@ -136,17 +142,19 @@
OFFLINE COMMERCE & ERP SUITE
Wird gestartet...
-
+
+
diff --git a/package.json b/package.json index 102972e..86f6a0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "socdof", "private": true, - "version": "25.2.0", + "version": "25.2.1", "description": "Strudel's Organization, Commerce & Documentation Offline Flow - Offline-first Windows ERP Desktop Suite & Web Workspace", "author": "Yuri / Strudel", "publisher": "Yuri / Strudel", diff --git a/src/lib/version.ts b/src/lib/version.ts index 49b3007..9b8bdeb 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -5,7 +5,7 @@ export interface VersionRelease { highlights: string[]; } -export const APP_VERSION = '25.2.0'; +export const APP_VERSION = '25.2.1'; export const APP_NAME = 'SOCDOF'; export const APP_FULL_NAME = "Strudel's Organization, Commerce & Documentation Offline Flow"; export const APP_AUTHOR = 'Yuri / Strudel'; diff --git a/versions/V25.md b/versions/V25.md index 17b760e..7e31539 100644 --- a/versions/V25.md +++ b/versions/V25.md @@ -2,6 +2,13 @@ All notable changes and technical implementation details for SOCDOF Version 25.x are documented here in English. +## Version 25.2.1 — Portable First-Run Progress Splash +- **First-Run Progress Splash (`electron/main.cjs`, `electron/splash.html`, `electron/preload.cjs`)**: + - The splash now shows a localized progress bar (de, en, fr, es) while the data folder is prepared, but only on the first launch of a data folder. + - Progress steps are pushed from the main process over `socdof:splash-progress`. + - A marker file `first-run-setup-complete.flag` in the user data folder (the portable `data` folder when portable) records a finished setup. Later launches skip the splash and the setup progress. + - The main window is revealed only after the first-run setup has finished. + ## Version 25.2.0 — Custom Roles, Passwordless Accounts & Per-Person Recovery - **Per-User Language (`src/components/DesktopWindowWorkspace.tsx`)**: - Choosing a language in the language dialog now stores it on the signed-in account (`preferences.language`) instead of the shared company profile, so it is not synchronized across users.