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) {