diff --git a/electron/main.mjs b/electron/main.mjs index d07e889..08475b1 100644 --- a/electron/main.mjs +++ b/electron/main.mjs @@ -1,4 +1,5 @@ import { spawn } from "node:child_process"; +import { readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { @@ -19,6 +20,7 @@ const port = Number(process.env.PORT || 3001); const rendererUrl = process.env.ECHOFORM_RENDERER_URL?.trim() || null; const useExternalServer = rendererUrl !== null; const baseUrl = rendererUrl ?? `http://127.0.0.1:${port}`; +const appStateFile = "app-state.json"; let mainWindow = null; let tray = null; @@ -59,6 +61,40 @@ function resolveResourcesRoot() { return app.isPackaged ? process.resourcesPath : projectRoot; } +function resolveAppStatePath() { + return join(app.getPath("userData"), appStateFile); +} + +async function readAppState() { + try { + const raw = await readFile(resolveAppStatePath(), "utf8"); + const parsed = JSON.parse(raw); + return typeof parsed === "object" && parsed !== null ? parsed : {}; + } catch (error) { + if (error && typeof error === "object" && "code" in error) { + if (error.code === "ENOENT") { + return {}; + } + } + + console.error("[echoform] failed to read app state", error); + return {}; + } +} + +async function markFirstLaunchHandled() { + const currentState = await readAppState(); + await writeFile( + resolveAppStatePath(), + JSON.stringify( + { ...currentState, hasCompletedFirstLaunch: true }, + null, + 2 + ), + "utf8" + ); +} + function resolveServerProcess() { if (app.isPackaged) { return { @@ -271,6 +307,16 @@ function createWindow() { const loadingHtml = join(__dirname, "loading.html"); +async function prepareWindow() { + const window = createWindow(); + if (window.webContents.getURL() === `${baseUrl}/`) { + return; + } + + await startServer(); + await window.loadURL(baseUrl); +} + async function showWindow() { const window = createWindow(); const currentUrl = window.webContents.getURL(); @@ -288,8 +334,7 @@ async function showWindow() { window.focus(); // Wait for the server, then load the real app - await startServer(); - await window.loadURL(baseUrl); + await prepareWindow(); } function toggleWindow() { @@ -354,8 +399,20 @@ ipcMain.handle("echoform:pick-folder", async () => { }); async function bootstrap() { + if (process.platform === "darwin") { + app.dock.hide(); + } + createTray(); createWindow(); + const { hasCompletedFirstLaunch = false } = await readAppState(); + + if (!hasCompletedFirstLaunch) { + await markFirstLaunchHandled(); + await prepareWindow(); + return; + } + await showWindow(); } diff --git a/package.json b/package.json index 3a7af82..991e233 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "dir" ], "extendInfo": { + "LSUIElement": true, "NSDocumentsFolderUsageDescription": "Echoform needs access to your documents to find and version Ableton projects.", "NSDesktopFolderUsageDescription": "Echoform needs access to your desktop to find and version Ableton projects.", "NSDownloadsFolderUsageDescription": "Echoform needs access to your downloads to find and version Ableton projects."