Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
68 changes: 66 additions & 2 deletions electron/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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'));
}
Expand Down Expand Up @@ -527,13 +579,17 @@ 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);
mainWindow.show();
mainWindow.focus();
};

revealMainWindowFn = revealMainWindow;

// Reveal window once first paint is completed and DOM is ready
mainWindow.once('ready-to-show', revealMainWindow);

Expand Down Expand Up @@ -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();
}
});

Expand Down
5 changes: 5 additions & 0 deletions electron/preload.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
38 changes: 30 additions & 8 deletions electron/splash.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -136,17 +142,19 @@
<div class="app-subtitle">OFFLINE COMMERCE & ERP SUITE</div>
<div class="status-text" id="status-text">Wird gestartet...</div>
<div class="track">
<div class="bar"></div>
<div class="bar" id="progress-bar"></div>
</div>
<div class="percent-text" id="percent-text"></div>
<div class="footer-text">Strudelcode · 100% Offline Desktop Suite</div>

<script>
(async () => {
(() => {
// Localized labels for the first-run data preparation steps (de, en, fr, es)
const messages = {
de: 'Wird gestartet...',
en: 'Starting...',
fr: 'Démarrage...',
es: 'Iniciando...'
de: { start: 'Daten werden vorbereitet...', folders: 'Datenordner wird erstellt...', languages: 'Sprachdateien werden geladen...', watchers: 'Ordner werden überwacht...', finishing: 'Fast fertig...', ready: 'Fertig' },
en: { start: 'Preparing data...', folders: 'Creating data folders...', languages: 'Loading language files...', watchers: 'Setting up folder watchers...', finishing: 'Almost done...', ready: 'Done' },
fr: { start: 'Préparation des données...', folders: 'Création des dossiers de données...', languages: 'Chargement des fichiers de langue...', watchers: 'Surveillance des dossiers...', finishing: 'Presque terminé...', ready: 'Terminé' },
es: { start: 'Preparando los datos...', folders: 'Creando las carpetas de datos...', languages: 'Cargando los archivos de idioma...', watchers: 'Configurando la supervisión de carpetas...', finishing: 'Casi listo...', ready: 'Listo' }
};
let lang = 'de';
try {
Expand All @@ -158,9 +166,23 @@
if (messages[navLang]) lang = navLang;
}
} catch (_) {}
const dict = messages[lang];
const label = document.getElementById('status-text');
if (label && messages[lang]) {
label.textContent = messages[lang];
const bar = document.getElementById('progress-bar');
const percentText = document.getElementById('percent-text');
if (label) label.textContent = dict.start;
if (bar) bar.style.width = '5%';

// Progress is pushed from the main process; the bar switches from indeterminate to determinate
if (window.electronAPI && window.electronAPI.onSplashProgress) {
window.electronAPI.onSplashProgress(({ percent, stepKey }) => {
if (label && dict[stepKey]) label.textContent = dict[stepKey];
if (bar) {
bar.style.animation = 'none';
bar.style.width = Math.max(0, Math.min(100, percent)) + '%';
}
if (percentText) percentText.textContent = Math.round(percent) + '%';
});
}
})();
</script>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
7 changes: 7 additions & 0 deletions versions/V25.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading