diff --git a/desktop/.gitignore b/desktop/.gitignore index 73bccf9..722c55a 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -14,6 +14,7 @@ npm-debug.log* .vscode/ *.swp *.swo +.claude/ # OS .DS_Store @@ -26,3 +27,6 @@ Thumbs.db # Electron *.asar + +# Lock files (optional - remove if you want to commit) +package-lock.json diff --git a/desktop/package.json b/desktop/package.json index 25487db..6300142 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,6 @@ { "name": "game-marathon-tracker", - "version": "1.0.0", + "version": "1.0.1", "description": "Desktop app for tracking game time in Game Marathon", "main": "dist/main/main/index.js", "author": "Game Marathon", @@ -54,13 +54,20 @@ "output": "release" }, "files": [ - "dist/**/*", - "resources/**/*" + "dist/**/*" + ], + "extraResources": [ + { + "from": "resources", + "to": "resources" + } ], "win": { "target": [ - "nsis", - "portable" + { + "target": "nsis", + "arch": ["x64"] + } ], "icon": "resources/icon.ico", "signAndEditExecutable": false @@ -69,7 +76,9 @@ "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, - "createStartMenuShortcut": true + "createStartMenuShortcut": true, + "runAfterFinish": false, + "artifactName": "Game-Marathon-Tracker-Setup-${version}.${ext}" }, "publish": { "provider": "github", diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts index 0e2c1a4..d5d6f03 100644 --- a/desktop/src/main/index.ts +++ b/desktop/src/main/index.ts @@ -34,12 +34,23 @@ const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged // Prevent multiple instances const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { - app.quit() + app.exit(0) } +// Someone tried to run a second instance, focus our window +app.on('second-instance', () => { + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore() + mainWindow.show() + mainWindow.focus() + } +}) + function createWindow() { - // __dirname is dist/main/main/ in both dev and prod - const iconPath = path.join(__dirname, '../../../resources/icon.ico') + // In dev: use project resources folder, in prod: use app resources + const iconPath = isDev + ? path.join(__dirname, '../../../resources/icon.ico') + : path.join(process.resourcesPath, 'resources/icon.ico') mainWindow = new BrowserWindow({ width: 450, @@ -149,6 +160,11 @@ ipcMain.on('minimize-to-tray', () => { mainWindow?.hide() }) +ipcMain.on('close-window', () => { + // This triggers the 'close' event handler which checks minimizeToTray setting + mainWindow?.close() +}) + ipcMain.on('quit-app', () => { app.isQuitting = true app.quit() diff --git a/desktop/src/main/tray.ts b/desktop/src/main/tray.ts index b1f164d..7682e11 100644 --- a/desktop/src/main/tray.ts +++ b/desktop/src/main/tray.ts @@ -10,10 +10,10 @@ export function setupTray( ) { const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged - // In dev: __dirname is dist/main/main/, in prod: same + // In dev: use project resources folder, in prod: use app resources const iconPath = isDev ? path.join(__dirname, '../../../resources/icon.ico') - : path.join(__dirname, '../../../resources/icon.ico') + : path.join(process.resourcesPath, 'resources/icon.ico') // Create tray icon let trayIcon: NativeImage diff --git a/desktop/src/main/updater.ts b/desktop/src/main/updater.ts index 91da0e5..541c95a 100644 --- a/desktop/src/main/updater.ts +++ b/desktop/src/main/updater.ts @@ -53,15 +53,20 @@ function sendProgressToSplash(percent: number) { export function setupAutoUpdater(onComplete: () => void) { const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged + let hasCompleted = false + + const safeComplete = () => { + if (hasCompleted) return + hasCompleted = true + closeSplashWindow() + onComplete() + } // In development, skip update check if (isDev) { console.log('[Updater] Skipping update check in development mode') sendStatusToSplash('Режим разработки') - setTimeout(() => { - closeSplashWindow() - onComplete() - }, 1500) + setTimeout(safeComplete, 1500) return } @@ -69,24 +74,21 @@ export function setupAutoUpdater(onComplete: () => void) { autoUpdater.autoDownload = true autoUpdater.autoInstallOnAppQuit = true - // Check for updates - autoUpdater.on('checking-for-update', () => { + // Check for updates (use 'once' to prevent handlers from triggering on manual update checks) + autoUpdater.once('checking-for-update', () => { console.log('[Updater] Checking for updates...') sendStatusToSplash('Проверка обновлений...') }) - autoUpdater.on('update-available', (info) => { + autoUpdater.once('update-available', (info) => { console.log('[Updater] Update available:', info.version) sendStatusToSplash(`Найдено обновление v${info.version}`) }) - autoUpdater.on('update-not-available', () => { + autoUpdater.once('update-not-available', () => { console.log('[Updater] No updates available') sendStatusToSplash('Актуальная версия') - setTimeout(() => { - closeSplashWindow() - onComplete() - }, 1000) + setTimeout(safeComplete, 1000) }) autoUpdater.on('download-progress', (progress) => { @@ -96,7 +98,7 @@ export function setupAutoUpdater(onComplete: () => void) { sendProgressToSplash(percent) }) - autoUpdater.on('update-downloaded', (info) => { + autoUpdater.once('update-downloaded', (info) => { console.log('[Updater] Update downloaded:', info.version) sendStatusToSplash('Установка обновления...') // Install and restart @@ -105,23 +107,18 @@ export function setupAutoUpdater(onComplete: () => void) { }, 1500) }) - autoUpdater.on('error', (error) => { - console.error('[Updater] Error:', error) - sendStatusToSplash('Ошибка проверки обновлений') - setTimeout(() => { - closeSplashWindow() - onComplete() - }, 2000) + autoUpdater.once('error', (error) => { + console.error('[Updater] Error:', error.message) + console.error('[Updater] Error stack:', error.stack) + sendStatusToSplash('Запуск...') + setTimeout(safeComplete, 1500) }) // Start checking autoUpdater.checkForUpdates().catch((error) => { console.error('[Updater] Failed to check for updates:', error) - sendStatusToSplash('Не удалось проверить обновления') - setTimeout(() => { - closeSplashWindow() - onComplete() - }, 2000) + sendStatusToSplash('Запуск...') + setTimeout(safeComplete, 1500) }) } diff --git a/desktop/src/preload/index.ts b/desktop/src/preload/index.ts index 059aee5..23d5b00 100644 --- a/desktop/src/preload/index.ts +++ b/desktop/src/preload/index.ts @@ -52,6 +52,7 @@ const electronAPI = { // Window controls minimizeToTray: (): void => ipcRenderer.send('minimize-to-tray'), + closeWindow: (): void => ipcRenderer.send('close-window'), quitApp: (): void => ipcRenderer.send('quit-app'), // Monitoring control diff --git a/desktop/src/renderer/components/Layout.tsx b/desktop/src/renderer/components/Layout.tsx index 37e22c7..a9952c9 100644 --- a/desktop/src/renderer/components/Layout.tsx +++ b/desktop/src/renderer/components/Layout.tsx @@ -32,7 +32,7 @@ export function Layout({ children }: LayoutProps) {