Endpunkt-Status-Icon mit Ereignis-Log und Update-Erkennung ergaenzen
Ein neues Status-Icon im Titelbalken zeigt den Zustand der lokalen Transkripte und der Serverabfrage; ein Klick oeffnet ein eigenes Fenster mit persistiertem, zeitgestempeltem Ereignis-/Fehlerprotokoll (loeschbar). Ausserdem erkennt die App beim Start Erstinstallation vs. Update (plattformunabhaengig fuer Windows und Linux) und meldet Updates per nativer Benachrichtigung. Dazu: Versionierungs-Policy dokumentiert (package.json vor jedem Release erhoehen, sonst greift weder die Update-Erkennung noch bleiben alte Release-Dateien in release/ erhalten), sowie zwei neue Dokumente fuer Endanwender (BENUTZERHANDBUCH.md) und eine Download-Webseite (PRODUKTSEITE.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c1b899ec8b
commit
e149a9118f
+85
-2
@@ -7,7 +7,7 @@
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { app, BrowserWindow, Tray, Menu, ipcMain, screen, nativeImage, shell } = require('electron');
|
||||
const { app, BrowserWindow, Tray, Menu, ipcMain, screen, nativeImage, shell, Notification } = require('electron');
|
||||
|
||||
const { Store, loadConfig } = require('./store');
|
||||
const { Collector } = require('./collector');
|
||||
@@ -26,8 +26,12 @@ const ROOT = path.join(__dirname, '..', '..');
|
||||
const SETTINGS_WIDTH = 520;
|
||||
const SETTINGS_HEIGHT = 660;
|
||||
|
||||
const ERRORS_WIDTH = 420;
|
||||
const ERRORS_HEIGHT = 480;
|
||||
|
||||
let win = null;
|
||||
let settingsWin = null;
|
||||
let errorsWin = null;
|
||||
let tray = null;
|
||||
let collector = null;
|
||||
let store = null;
|
||||
@@ -301,6 +305,50 @@ function openSettings() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Fehler-/Ereignis-Log — ein Fenster, mehrfaches Öffnen holt es nur nach vorn. */
|
||||
function openErrorLog() {
|
||||
if (errorsWin && !errorsWin.isDestroyed()) {
|
||||
errorsWin.show();
|
||||
errorsWin.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
const target = win && !win.isDestroyed()
|
||||
? screen.getDisplayMatching(win.getBounds())
|
||||
: screen.getPrimaryDisplay();
|
||||
const area = target.workArea;
|
||||
|
||||
errorsWin = new BrowserWindow({
|
||||
width: ERRORS_WIDTH,
|
||||
height: ERRORS_HEIGHT,
|
||||
x: Math.round(area.x + (area.width - ERRORS_WIDTH) / 2),
|
||||
y: Math.round(area.y + Math.max(0, (area.height - ERRORS_HEIGHT) / 2)),
|
||||
minWidth: 360,
|
||||
minHeight: 320,
|
||||
frame: false,
|
||||
resizable: true,
|
||||
skipTaskbar: false,
|
||||
// Über dem Widget, das selbst always-on-top ist — sonst verschwindet der
|
||||
// Dialog dahinter.
|
||||
alwaysOnTop: true,
|
||||
show: false,
|
||||
backgroundColor: '#1e1e22',
|
||||
icon: path.join(ROOT, 'assets', 'icon.png'),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '..', 'errors-preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
},
|
||||
});
|
||||
|
||||
errorsWin.loadFile(path.join(__dirname, '..', 'errors', 'index.html'));
|
||||
errorsWin.once('ready-to-show', () => errorsWin.show());
|
||||
errorsWin.on('closed', () => {
|
||||
errorsWin = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Übernimmt geänderte Einstellungen ohne Neustart und schreibt sie in die
|
||||
* Datei. Unbekannte Felder und die `_`-Kommentare bleiben erhalten, weil die
|
||||
@@ -345,6 +393,9 @@ function registerIpc() {
|
||||
ipcMain.on('set-expanded', (_event, expanded) => applyExpanded(Boolean(expanded)));
|
||||
ipcMain.on('hide-window', () => win && win.hide());
|
||||
ipcMain.on('open-settings', openSettings);
|
||||
ipcMain.on('open-error-log', openErrorLog);
|
||||
ipcMain.on('errors:clear', () => collector && collector.clearErrors());
|
||||
ipcMain.on('errors:close', () => errorsWin && errorsWin.close());
|
||||
|
||||
ipcMain.handle('settings:load', () => ({
|
||||
config,
|
||||
@@ -352,6 +403,7 @@ function registerIpc() {
|
||||
configPath: CONFIG_FILE,
|
||||
autostart: getAutostart(),
|
||||
platform: process.platform,
|
||||
version: app.getVersion(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -386,6 +438,23 @@ async function main() {
|
||||
store = new Store(STATE_FILE);
|
||||
store.load();
|
||||
|
||||
// Neuinstallations-/Update-Erkennung: rein additiv, überschreibt oder
|
||||
// löscht keine bestehenden Einstellungsdaten — nur `store.data.appVersion`
|
||||
// wird gesetzt. Funktioniert identisch unter Windows (NSIS/portable) und
|
||||
// Linux (AppImage/deb), da app.getVersion() plattformunabhängig aus den
|
||||
// Paketmetadaten liest und state.json auf beiden Plattformen im selben
|
||||
// Nutzerprofil-Ordner liegt (siehe paths.js).
|
||||
const currentVersion = app.getVersion();
|
||||
const previousVersion = store.data.appVersion;
|
||||
let versionEvent = null;
|
||||
if (previousVersion == null) {
|
||||
versionEvent = { type: 'install', previousVersion: null, currentVersion };
|
||||
} else if (previousVersion !== currentVersion) {
|
||||
versionEvent = { type: 'update', previousVersion, currentVersion };
|
||||
}
|
||||
store.data.appVersion = currentVersion;
|
||||
store.save();
|
||||
|
||||
registerIpc();
|
||||
createWindow();
|
||||
createTray();
|
||||
@@ -393,9 +462,23 @@ async function main() {
|
||||
if (config.opacity != null) win.setOpacity(config.opacity);
|
||||
if (config.clickThrough) win.setIgnoreMouseEvents(true, { forward: true });
|
||||
|
||||
collector = new Collector({ config, store, configError: error });
|
||||
collector = new Collector({ config, store, configError: error, versionEvent });
|
||||
|
||||
// Hinweis auf ein Update über die native Systembenachrichtigung — der
|
||||
// Titelbalken ist mit dem Endpunkt-Status-Icon bereits eng, und eine
|
||||
// Notification ist naturgemäß einmalig. Eine Neuinstallation bekommt
|
||||
// bewusst keine Meldung: dafür gibt es nichts, worüber zu informieren wäre.
|
||||
if (versionEvent?.type === 'update' && Notification.isSupported()) {
|
||||
new Notification({
|
||||
title: 'Claude Live Dashboard aktualisiert',
|
||||
body: `Version ${versionEvent.previousVersion} → ${versionEvent.currentVersion}`,
|
||||
icon: path.join(ROOT, 'assets', 'icon.png'),
|
||||
}).show();
|
||||
}
|
||||
|
||||
collector.on('update', (state) => {
|
||||
if (win && !win.isDestroyed()) win.webContents.send('state', state);
|
||||
if (errorsWin && !errorsWin.isDestroyed()) errorsWin.webContents.send('state', state);
|
||||
if (tray) {
|
||||
const parts = [`Claude · Woche ${Math.round(state.week.percent)} %`];
|
||||
if (state.block.hasLimit) parts.push(`5h ${Math.round(state.block.percent)} %`);
|
||||
|
||||
Reference in New Issue
Block a user