Files
fellakte/main/main.js
T
2026-09-14 18:57:05 +00:00

146 lines
4.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const { app, BrowserWindow, dialog } = require("electron");
const fs = require("fs");
const path = require("path");
const db = require("./db");
const lock = require("./lock");
const backup = require("./backup");
const reminders = require("./reminders");
const webserver = require("./webserver");
const registerIpc = require("./ipc");
// Works around a blank/unpainted-window glitch seen on some Windows GPU drivers
// (matches the "GPU process exited unexpectedly" warnings this app triggered there) —
// newly inserted DOM content (e.g. the modal) wouldn't repaint until the window was
// resized or moved. Must be called before app is ready.
app.disableHardwareAcceleration();
const settingsPath = path.join(app.getPath("userData"), "settings.json");
const iconPath = path.join(__dirname, "..", "assets", "icon.ico");
function readSettings() {
try {
return JSON.parse(fs.readFileSync(settingsPath, "utf8"));
} catch {
return {};
}
}
function writeSettings(s) {
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(s, null, 2));
}
let mainWindow = null;
async function chooseDataFolder(isFirstRun) {
const message = isFirstRun
? "Wo soll die Fellakte ihre Daten speichern?\n\nFür zwei PCs in der Praxis: einen Ordner auf einem gemeinsamen Netzlaufwerk oder einem synchronisierten Ordner (z. B. OneDrive/Dropbox) wählen. Für einen einzelnen PC reicht ein normaler Ordner."
: "Neuen Datenordner wählen";
await dialog.showMessageBox({
type: "info",
title: "Fellakte Datenordner",
message,
buttons: ["Weiter"],
});
const result = await dialog.showOpenDialog({
title: "Datenordner für die Fellakte wählen oder anlegen",
properties: ["openDirectory", "createDirectory"],
});
if (result.canceled || !result.filePaths[0]) return null;
return result.filePaths[0];
}
async function ensureDataFolder() {
const settings = readSettings();
let folder = settings.dataFolder;
if (!folder || !fs.existsSync(folder)) {
folder = await chooseDataFolder(true);
if (!folder) {
app.quit();
return null;
}
writeSettings({ ...settings, dataFolder: folder });
}
return folder;
}
async function checkLockAndWarn(folder) {
const { active, info } = lock.checkOther(folder);
if (!active) return true;
const when = new Date(info.updatedAt).toLocaleTimeString("de-DE");
const { response } = await dialog.showMessageBox({
type: "warning",
title: "Fellakte wird eventuell gerade woanders benutzt",
message: `Auf "${info.host}" wurde dieser Datenordner zuletzt um ${when} Uhr verwendet.`,
detail:
"Wenn dort gerade jemand arbeitet und du hier ebenfalls speicherst, überschreibt die zuletzt gespeicherte Version die andere. Am sichersten: kurz nachfragen, ob der andere PC gerade offen ist.",
buttons: ["Trotzdem öffnen", "Abbrechen"],
defaultId: 1,
cancelId: 1,
});
return response === 0;
}
async function createWindow(dataFolder) {
mainWindow = new BrowserWindow({
width: 1360,
height: 860,
minWidth: 980,
minHeight: 640,
backgroundColor: "#f7f0e8",
icon: iconPath,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
backgroundThrottling: false,
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
mainWindow.setMenuBarVisibility(false);
// Belt-and-suspenders alongside the webPreferences option above: without this,
// Chromium can throttle/skip repainting a window that isn't the active foreground
// one yet, which reads as "newly opened content stays blank until you resize/refocus".
mainWindow.webContents.setBackgroundThrottling(false);
await mainWindow.loadFile(path.join(__dirname, "..", "renderer", "index.html"));
}
app.whenReady().then(async () => {
const dataFolder = await ensureDataFolder();
if (!dataFolder) return;
const proceed = await checkLockAndWarn(dataFolder);
if (!proceed) {
app.quit();
return;
}
lock.acquire(dataFolder);
await db.init(dataFolder);
backup.runDailyBackupIfNeeded(dataFolder);
registerIpc({ dataFolder });
await createWindow(dataFolder);
reminders.start();
webserver.start(dataFolder);
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow(dataFolder);
});
});
app.on("window-all-closed", () => {
lock.release();
reminders.stop();
webserver.stop();
if (process.platform !== "darwin") app.quit();
});
app.on("before-quit", () => {
lock.release();
reminders.stop();
webserver.stop();
});