diff --git a/.gitignore b/.gitignore index 624a4076..7bd76930 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ out *.log* .env .vite - diff --git a/README.be.md b/README.be.md index c5848f79..5f836d3e 100644 --- a/README.be.md +++ b/README.be.md @@ -18,6 +18,7 @@ [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) [![be](https://img.shields.io/badge/lang-be-orange)](README.be.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.es.md b/README.es.md index 3b261f60..6768b5a3 100644 --- a/README.es.md +++ b/README.es.md @@ -18,7 +18,7 @@ [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) -[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) +[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.md b/README.md index ab608f37..2e0bd4cc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.pl.md b/README.pl.md index 4fa7449e..7bc4d574 100644 --- a/README.pl.md +++ b/README.pl.md @@ -18,6 +18,7 @@ [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.pt-BR.md b/README.pt-BR.md index 07c89e96..835fd992 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -18,6 +18,8 @@ [![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) + ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.ru.md b/README.ru.md index 704a8c90..cbdda62e 100644 --- a/README.ru.md +++ b/README.ru.md @@ -18,6 +18,7 @@ [![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md) [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) [![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/README.uk-UA.md b/README.uk-UA.md index c8451bc2..11f45aa5 100644 --- a/README.uk-UA.md +++ b/README.uk-UA.md @@ -18,6 +18,7 @@ [![pl](https://img.shields.io/badge/lang-pl-white)](README.pl.md) [![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md) [![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md) +[![es](https://img.shields.io/badge/lang-es-red)](README.es.md) ![Hydra Catalogue](./docs/screenshot.png) diff --git a/build/icons/512x512.png b/build/icons/512x512.png new file mode 100644 index 00000000..865a96a2 Binary files /dev/null and b/build/icons/512x512.png differ diff --git a/electron-builder.yml b/electron-builder.yml index de64dbcb..3b35fd18 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -40,8 +40,11 @@ linux: - AppImage - snap - deb + - rpm maintainer: electronjs.org - category: Utility + category: Game + mimeTypes: + - x-scheme-handler/hydralauncher appImage: artifactName: ${name}-${version}.${ext} npmRebuild: false diff --git a/hydra.db b/hydra.db index 0015965f..49089bb3 100644 Binary files a/hydra.db and b/hydra.db differ diff --git a/package.json b/package.json index b37d5630..510f6a71 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "color": "^4.2.3", "color.js": "^1.2.0", "date-fns": "^3.6.0", + "electron-log": "^5.1.4", "electron-updater": "^6.1.8", "fetch-cookie": "^3.0.1", "flexsearch": "^0.7.43", @@ -66,7 +67,6 @@ "react-router-dom": "^6.22.3", "typeorm": "^0.3.20", "user-agents": "^1.1.193", - "winston": "^3.13.0", "yaml": "^2.4.1" }, "devDependencies": { diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 6185fbf8..a7ed1378 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -29,7 +29,8 @@ "catalogue": "Catalogue", "downloads": "Downloads", "search_results": "Search results", - "settings": "Settings" + "settings": "Settings", + "version_available": "Version {{version}} available. Click here to restart and install." }, "bottom_panel": { "no_downloads_in_progress": "No downloads in progress", @@ -153,6 +154,7 @@ "real_debrid": "Real-Debrid", "real_debrid_description": "Real-Debrid is an unrestricted downloader that allows you to download files instantly and at the best of your Internet speed.", "real_debrid_api_token_hint": "You can get your API token <0>here.", + "real_debrid_free_account": "The account \"{{username}}\" is a free account. Please subscribe to Real-Debrid.", "save_changes": "Save changes" }, "notifications": { @@ -176,11 +178,5 @@ }, "modal": { "close": "Close button" - }, - "splash": { - "downloading_version": "Downloading version {{version}}", - "searching_updates": "Searching for updates", - "update_found": "Update {{version}} found", - "restarting_and_applying": "Restarting and applying update" } } diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 945f2121..e0ff8a1b 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -29,7 +29,8 @@ "catalogue": "Catálogo", "downloads": "Descargas", "search_results": "Resultados de búsqueda", - "settings": "Ajustes" + "settings": "Ajustes", + "version_available": "Version {{version}} disponible. Haga clic aquí para reiniciar e instalar." }, "bottom_panel": { "no_downloads_in_progress": "Sin descargas en progreso", @@ -54,7 +55,7 @@ "remove": "Eliminar", "remove_from_list": "Quitar", "space_left_on_disk": "{{space}} restantes en el disco", - "eta": "Finalizando en {{eta}}", + "eta": "Tiempo restante: {{eta}}", "downloading_metadata": "Descargando metadatos…", "checking_files": "Analizando archivos…", "filter": "Buscar repacks", @@ -176,11 +177,5 @@ }, "modal": { "close": "Botón de cierre" - }, - "splash": { - "downloading_version": "Descargando versión {{version}}", - "searching_updates": "Buscando actualizaciones", - "update_found": "Actualización {{version}} encontrada", - "restarting_and_applying": "Reiniciando y aplicando actualización" } } diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index 57706dc9..e7c14f8c 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -72,7 +72,7 @@ "minutes": "minuti", "amount_hours": "{{amount}} ore", "amount_minutes": "{{amount}} minuti", - "accuracy": "{{accuratezza}}% di accuratezza", + "accuracy": "{{accuracy}}% di accuratezza", "add_to_library": "Aggiungi alla libreria", "remove_from_library": "Rimuovi dalla libreria", "no_downloads": "Nessun download disponibile", @@ -86,7 +86,6 @@ "playing_now": "Stai giocando adesso", "change": "Aggiorna", "repacks_modal_description": "Scegli il repack che vuoi scaricare", - "downloads_path": "Percorso dei download", "select_folder_hint": "Per cambiare la cartella predefinita, accedi alle", "download_now": "Scarica ora", "installation_instructions": "Istruzioni di installazione", @@ -96,7 +95,14 @@ "dont_show_it_again": "Non mostrarlo più", "copy_to_clipboard": "Copia", "copied_to_clipboard": "Copiato", - "got_it": "Capito" + "got_it": "Capito", + "no_shop_details": "Impossibile recuperare i dettagli del negozio.", + "download_options": "Opzioni di download", + "download_path": "Percorso di download", + "previous_screenshot": "Screenshot precedente", + "next_screenshot": "Screenshot successivo", + "screenshot": "Screenshot {{number}}", + "open_screenshot": "Apri screenshot {{number}}" }, "activation": { "title": "Attiva Hydra", @@ -127,7 +133,9 @@ "remove_from_list": "Rimuovi", "delete_modal_title": "Sei sicuro?", "delete_modal_description": "Questo rimuoverà tutti i file di installazione dal tuo computer", - "install": "Installa" + "install": "Installa", + "real_debrid": "Real Debrid", + "torrent": "Torrent" }, "settings": { "downloads_path": "Percorso dei download", @@ -136,7 +144,16 @@ "enable_download_notifications": "Quando un download è completo", "enable_repack_list_notifications": "Quando viene aggiunto un nuovo repack", "telemetry": "Telemetria", - "telemetry_description": "Abilita statistiche di utilizzo anonime" + "telemetry_description": "Abilita statistiche di utilizzo anonime", + "real_debrid_api_token_label": "Token API Real Debrid", + "quit_app_instead_hiding": "Esci da Hydra invece di nascondere nell'area di notifica", + "launch_with_system": "Apri Hydra all'avvio", + "general": "Generale", + "behavior": "Comportamento", + "enable_real_debrid": "Abilita Real Debrid", + "real_debrid": "Real Debrid", + "real_debrid_api_token_hint": "Puoi trovare la tua chiave API <0>here.", + "save_changes": "Salva modifiche" }, "notifications": { "download_complete": "Download completato", diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 834f9bf3..a7b62d92 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -29,7 +29,8 @@ "downloads": "Downloads", "search_results": "Resultados da busca", "settings": "Configurações", - "home": "Início" + "home": "Início", + "version_available": "Versão {{version}} disponível. Clique aqui para reiniciar e instalar." }, "bottom_panel": { "no_downloads_in_progress": "Sem downloads em andamento", diff --git a/src/main/constants.ts b/src/main/constants.ts index 17eea7ca..de1ccb60 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -26,6 +26,8 @@ export const databasePath = path.join( "hydra.db" ); +export const logsPath = path.join(app.getPath("appData"), "hydra", "logs"); + export const seedsPath = app.isPackaged ? path.join(process.resourcesPath, "seeds") : path.join(__dirname, "..", "..", "seeds"); diff --git a/src/main/entity/game.entity.ts b/src/main/entity/game.entity.ts index fd168f51..784380e0 100644 --- a/src/main/entity/game.entity.ts +++ b/src/main/entity/game.entity.ts @@ -57,7 +57,7 @@ export class Game { @Column("int", { default: 0 }) bytesDownloaded: number; - @Column("text", { nullable: true }) + @Column("datetime", { nullable: true }) lastTimePlayed: Date | null; @Column("float", { default: 0 }) diff --git a/src/main/events/autoupdater/check-for-updates.ts b/src/main/events/autoupdater/check-for-updates.ts index aa63575f..b6487f47 100644 --- a/src/main/events/autoupdater/check-for-updates.ts +++ b/src/main/events/autoupdater/check-for-updates.ts @@ -1,41 +1,27 @@ import { AppUpdaterEvents } from "@types"; import { registerEvent } from "../register-event"; -import updater, { ProgressInfo, UpdateInfo } from "electron-updater"; +import updater, { UpdateInfo } from "electron-updater"; import { WindowManager } from "@main/services"; import { app } from "electron"; const { autoUpdater } = updater; const sendEvent = (event: AppUpdaterEvents) => { - WindowManager.splashWindow?.webContents.send("autoUpdaterEvent", event); + WindowManager.mainWindow?.webContents.send("autoUpdaterEvent", event); }; const mockValuesForDebug = async () => { - sendEvent({ type: "update-downloaded" }); + sendEvent({ type: "update-available", info: { version: "1.3.0" } }); + // sendEvent({ type: "update-downloaded" }); }; const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => { autoUpdater - .addListener("error", () => { - sendEvent({ type: "error" }); - }) - .addListener("checking-for-update", () => { - sendEvent({ type: "checking-for-updates" }); - }) - .addListener("update-not-available", () => { - sendEvent({ type: "update-not-available" }); - }) .addListener("update-available", (info: UpdateInfo) => { sendEvent({ type: "update-available", info }); }) .addListener("update-downloaded", () => { sendEvent({ type: "update-downloaded" }); - }) - .addListener("download-progress", (info: ProgressInfo) => { - sendEvent({ type: "download-progress", info }); - }) - .addListener("update-cancelled", () => { - sendEvent({ type: "update-cancelled" }); }); if (app.isPackaged) { diff --git a/src/main/events/autoupdater/continue-to-main-window.ts b/src/main/events/autoupdater/continue-to-main-window.ts deleted file mode 100644 index 6a8965f9..00000000 --- a/src/main/events/autoupdater/continue-to-main-window.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { WindowManager } from "@main/services"; -import { registerEvent } from "../register-event"; -import updater from "electron-updater"; - -const { autoUpdater } = updater; - -const continueToMainWindow = async (_event: Electron.IpcMainInvokeEvent) => { - autoUpdater.removeAllListeners(); - WindowManager.prepareMainWindowAndCloseSplash(); -}; - -registerEvent("continueToMainWindow", continueToMainWindow); diff --git a/src/main/events/autoupdater/restart-and-install-update.ts b/src/main/events/autoupdater/restart-and-install-update.ts index be301c18..2dbef98f 100644 --- a/src/main/events/autoupdater/restart-and-install-update.ts +++ b/src/main/events/autoupdater/restart-and-install-update.ts @@ -1,16 +1,13 @@ import { app } from "electron"; import { registerEvent } from "../register-event"; import updater from "electron-updater"; -import { WindowManager } from "@main/services"; const { autoUpdater } = updater; const restartAndInstallUpdate = async (_event: Electron.IpcMainInvokeEvent) => { + autoUpdater.removeAllListeners(); if (app.isPackaged) { autoUpdater.quitAndInstall(true, true); - } else { - autoUpdater.removeAllListeners(); - WindowManager.prepareMainWindowAndCloseSplash(); } }; diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 70f483d5..0ebb5d86 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -29,7 +29,6 @@ import "./user-preferences/update-user-preferences"; import "./user-preferences/auto-launch"; import "./autoupdater/check-for-updates"; import "./autoupdater/restart-and-install-update"; -import "./autoupdater/continue-to-main-window"; import "./user-preferences/authenticate-real-debrid"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 85a4d483..bf9e0325 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -50,8 +50,6 @@ const startGameDownload = async ( isDeleted: false, } ); - - return DownloadManager.startDownload(game); } else { const steamGame = stateManager .getValue("steamGames") @@ -81,16 +79,16 @@ const startGameDownload = async ( return result; }); - - const createdGame = await gameRepository.findOne({ - where: { - objectID, - }, - relations: { repack: true }, - }); - - return DownloadManager.startDownload(createdGame!); } + + const updatedGame = await gameRepository.findOne({ + where: { + objectID, + }, + relations: { repack: true }, + }); + + await DownloadManager.startDownload(updatedGame!); }; registerEvent("startGameDownload", startGameDownload); diff --git a/src/main/index.ts b/src/main/index.ts index 22c13388..c56903dd 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -64,7 +64,7 @@ app.whenReady().then(() => { where: { id: 1 }, }); - WindowManager.createSplashScreen(); + WindowManager.createMainWindow(); WindowManager.createSystemTray(userPreferences?.language || "en"); }); }); diff --git a/src/main/migrations/1716776027208-alter_lastTimePlayed_to_datime.ts b/src/main/migrations/1716776027208-alter_lastTimePlayed_to_datime.ts new file mode 100644 index 00000000..6a562915 --- /dev/null +++ b/src/main/migrations/1716776027208-alter_lastTimePlayed_to_datime.ts @@ -0,0 +1,49 @@ +import { Game } from "@main/entity"; +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AlterLastTimePlayedToDatime1716776027208 + implements MigrationInterface +{ + public async up(queryRunner: QueryRunner): Promise { + // 2024-05-27 02:08:17 + // Mon, 27 May 2024 02:08:17 GMT + const updateLastTimePlayedValues = ` + UPDATE game SET lastTimePlayed = (SELECT + SUBSTR(lastTimePlayed, 13, 4) || '-' || -- Year + CASE SUBSTR(lastTimePlayed, 9, 3) + WHEN 'Jan' THEN '01' + WHEN 'Feb' THEN '02' + WHEN 'Mar' THEN '03' + WHEN 'Apr' THEN '04' + WHEN 'May' THEN '05' + WHEN 'Jun' THEN '06' + WHEN 'Jul' THEN '07' + WHEN 'Aug' THEN '08' + WHEN 'Sep' THEN '09' + WHEN 'Oct' THEN '10' + WHEN 'Nov' THEN '11' + WHEN 'Dec' THEN '12' + END || '-' || -- Month + SUBSTR(lastTimePlayed, 6, 2) || ' ' || -- Day + SUBSTR(lastTimePlayed, 18, 8) -- hh:mm:ss; + FROM game) + WHERE lastTimePlayed IS NOT NULL; + `; + + await queryRunner.query(updateLastTimePlayedValues); + } + + public async down(queryRunner: QueryRunner): Promise { + const queryBuilder = queryRunner.manager.createQueryBuilder(Game, "game"); + + const result = await queryBuilder.getMany(); + + for (const game of result) { + if (!game.lastTimePlayed) continue; + await queryRunner.query( + `UPDATE game set lastTimePlayed = ? WHERE id = ?;`, + [game.lastTimePlayed.toUTCString(), game.id] + ); + } + } +} diff --git a/src/main/migrations/index.ts b/src/main/migrations/index.ts index 65061fac..c0c96e45 100644 --- a/src/main/migrations/index.ts +++ b/src/main/migrations/index.ts @@ -1,3 +1,7 @@ import { FixRepackUploadDate1715900413313 } from "./1715900413313-fix_repack_uploadDate"; +import { AlterLastTimePlayedToDatime1716776027208 } from "./1716776027208-alter_lastTimePlayed_to_datime"; -export default [FixRepackUploadDate1715900413313]; +export default [ + FixRepackUploadDate1715900413313, + AlterLastTimePlayedToDatime1716776027208, +]; diff --git a/src/main/services/download-manager.ts b/src/main/services/download-manager.ts index 9872a712..e7a2a3b4 100644 --- a/src/main/services/download-manager.ts +++ b/src/main/services/download-manager.ts @@ -186,7 +186,8 @@ export class DownloadManager { } if (WindowManager.mainWindow && game) { - WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); + if (!isNaN(progress)) + WindowManager.mainWindow.setProgressBar(progress === 1 ? -1 : progress); const payload = { numPeers: Number(status.connections), @@ -237,11 +238,12 @@ export class DownloadManager { if (this.gid) { await this.aria2.call("forcePause", this.gid); this.gid = null; - this.game = null; - this.realDebridTorrentId = null; - - WindowManager.mainWindow?.setProgressBar(-1); } + + this.game = null; + this.realDebridTorrentId = null; + + WindowManager.mainWindow?.setProgressBar(-1); } static async resumeDownload(game: Game) { @@ -251,6 +253,7 @@ export class DownloadManager { this.gid = gid; this.game = game; + this.realDebridTorrentId = null; } else { return this.startDownload(game); } @@ -269,7 +272,6 @@ export class DownloadManager { ); } else { this.gid = await this.aria2.call("addUri", [game.repack.magnet], options); - this.downloads.set(game.id, this.gid); } diff --git a/src/main/services/logger.ts b/src/main/services/logger.ts index 07a5d153..8da27a9e 100644 --- a/src/main/services/logger.ts +++ b/src/main/services/logger.ts @@ -1,11 +1,26 @@ -import winston from "winston"; +import { logsPath } from "@main/constants"; +import log from "electron-log"; +import path from "path"; -export const logger = winston.createLogger({ - level: "info", - format: winston.format.json(), - transports: [ - new winston.transports.File({ filename: "error.log", level: "error" }), - new winston.transports.File({ filename: "info.log", level: "info" }), - new winston.transports.File({ filename: "combined.log" }), - ], +log.transports.file.resolvePathFn = ( + _: log.PathVariables, + message?: log.LogMessage | undefined +) => { + if (message?.level === "error") { + return path.join(logsPath, "error.txt"); + } + + if (message?.level === "info") { + return path.join(logsPath, "info.txt"); + } + + return path.join(logsPath, "logs.txt"); +}; + +log.errorHandler.startCatching({ + showDialog: false, }); + +log.initialize(); + +export const logger = log.scope("main"); diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 882aeea8..ea1b6355 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -46,7 +46,7 @@ export const watchProcesses = async () => { await gameRepository.update(game.id, { playTimeInMilliseconds: game.playTimeInMilliseconds + delta, - lastTimePlayed: new Date().toUTCString(), + lastTimePlayed: new Date(), }); } diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index e435ddb2..1c46ebce 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -17,8 +17,6 @@ import { IsNull, Not } from "typeorm"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; - public static splashWindow: Electron.BrowserWindow | null = null; - public static isReadyToShowMainWindow = false; private static loadURL(hash = "") { // HMR for renderer base on electron-vite cli. @@ -37,44 +35,8 @@ export class WindowManager { } } - private static loadSplashURL() { - // HMR for renderer base on electron-vite cli. - // Load the remote URL for development or the local html file for production. - if (is.dev && process.env["ELECTRON_RENDERER_URL"]) { - this.splashWindow?.loadURL( - `${process.env["ELECTRON_RENDERER_URL"]}#/splash` - ); - } else { - this.splashWindow?.loadFile( - path.join(__dirname, "../renderer/index.html"), - { - hash: "splash", - } - ); - } - } - - public static createSplashScreen() { - if (this.splashWindow) return; - - this.splashWindow = new BrowserWindow({ - width: 380, - height: 380, - frame: false, - resizable: false, - backgroundColor: "#1c1c1c", - webPreferences: { - preload: path.join(__dirname, "../preload/index.mjs"), - sandbox: false, - }, - }); - - this.loadSplashURL(); - this.splashWindow.removeMenu(); - } - public static createMainWindow() { - if (this.mainWindow || !this.isReadyToShowMainWindow) return; + if (this.mainWindow) return; this.mainWindow = new BrowserWindow({ width: 1200, @@ -94,6 +56,7 @@ export class WindowManager { preload: path.join(__dirname, "../preload/index.mjs"), sandbox: false, }, + show: false, }); this.loadURL(); @@ -101,6 +64,7 @@ export class WindowManager { this.mainWindow.on("ready-to-show", () => { if (!app.isPackaged) WindowManager.mainWindow?.webContents.openDevTools(); + WindowManager.mainWindow?.show(); }); this.mainWindow.on("close", async () => { @@ -115,12 +79,6 @@ export class WindowManager { }); } - public static prepareMainWindowAndCloseSplash() { - this.isReadyToShowMainWindow = true; - this.splashWindow?.close(); - this.createMainWindow(); - } - public static redirect(hash: string) { if (!this.mainWindow) this.createMainWindow(); this.loadURL(hash); diff --git a/src/preload/index.ts b/src/preload/index.ts index 57ddc43b..da3dfe20 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -104,7 +104,7 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("showOpenDialog", options), platform: process.platform, - /* Splash */ + /* Auto update */ onAutoUpdaterEvent: (cb: (value: AppUpdaterEvent) => void) => { const listener = ( _event: Electron.IpcRendererEvent, @@ -119,5 +119,4 @@ contextBridge.exposeInMainWorld("electron", { }, checkForUpdates: () => ipcRenderer.invoke("checkForUpdates"), restartAndInstallUpdate: () => ipcRenderer.invoke("restartAndInstallUpdate"), - continueToMainWindow: () => ipcRenderer.invoke("continueToMainWindow"), }); diff --git a/src/renderer/src/assets/icon.png b/src/renderer/src/assets/icon.png deleted file mode 100644 index 9254a8fb..00000000 Binary files a/src/renderer/src/assets/icon.png and /dev/null differ diff --git a/src/renderer/src/components/header/header.css.ts b/src/renderer/src/components/header/header.css.ts index a1ee5469..cb18ae3e 100644 --- a/src/renderer/src/components/header/header.css.ts +++ b/src/renderer/src/components/header/header.css.ts @@ -145,3 +145,21 @@ export const title = recipe({ }, }, }); + +export const subheader = style({ + borderBottom: `solid 1px ${vars.color.border}`, + padding: `${SPACING_UNIT / 2}px ${SPACING_UNIT * 3}px`, +}); + +export const newVersionButton = style({ + display: "flex", + alignItems: "center", + justifyContent: "center", + gap: `${SPACING_UNIT}px`, + color: vars.color.bodyText, + borderBottom: "1px solid transparent", + ":hover": { + borderBottom: `1px solid ${vars.color.bodyText}`, + cursor: "pointer", + }, +}); diff --git a/src/renderer/src/components/header/header.tsx b/src/renderer/src/components/header/header.tsx index ea363c00..31ada4cf 100644 --- a/src/renderer/src/components/header/header.tsx +++ b/src/renderer/src/components/header/header.tsx @@ -1,12 +1,18 @@ import { useTranslation } from "react-i18next"; import { useEffect, useMemo, useRef, useState } from "react"; import { useLocation, useNavigate } from "react-router-dom"; -import { ArrowLeftIcon, SearchIcon, XIcon } from "@primer/octicons-react"; +import { + ArrowLeftIcon, + SearchIcon, + SyncIcon, + XIcon, +} from "@primer/octicons-react"; import { useAppDispatch, useAppSelector } from "@renderer/hooks"; import * as styles from "./header.css"; import { clearSearch } from "@renderer/features"; +import { AppUpdaterEvents } from "@types"; export interface HeaderProps { onSearch: (query: string) => void; @@ -34,6 +40,9 @@ export function Header({ onSearch, onClear, search }: HeaderProps) { const [isFocused, setIsFocused] = useState(false); + const [showUpdateSubheader, setShowUpdateSubheader] = useState(false); + const [newVersion, setNewVersion] = useState(""); + const { t } = useTranslation("header"); const title = useMemo(() => { @@ -49,6 +58,30 @@ export function Header({ onSearch, onClear, search }: HeaderProps) { } }, [location.pathname, search, dispatch]); + const handleClickRestartAndUpdate = () => { + window.electron.restartAndInstallUpdate(); + }; + + useEffect(() => { + const unsubscribe = window.electron.onAutoUpdaterEvent( + (event: AppUpdaterEvents) => { + if (event.type == "update-available") { + setNewVersion(event.info.version || ""); + } + + if (event.type == "update-downloaded") { + setShowUpdateSubheader(true); + } + } + ); + + window.electron.checkForUpdates(); + + return () => { + unsubscribe(); + }; + }); + const focusInput = () => { setIsFocused(true); inputRef.current?.focus(); @@ -63,64 +96,80 @@ export function Header({ onSearch, onClear, search }: HeaderProps) { }; return ( -
-
- - -

- {title} -

-
- -
-
+ <> +
+
- onSearch(event.target.value)} - onFocus={() => setIsFocused(true)} - onBlur={handleBlur} - /> +

+ {title} +

+
- {search && ( +
+
- )} -
-
-
+ + onSearch(event.target.value)} + onFocus={() => setIsFocused(true)} + onBlur={handleBlur} + /> + + {search && ( + + )} +
+
+
+ {showUpdateSubheader && ( +
+ +
+ )} + ); } diff --git a/src/renderer/src/components/toast/toast.css.ts b/src/renderer/src/components/toast/toast.css.ts index 5cba3fdd..45e14b85 100644 --- a/src/renderer/src/components/toast/toast.css.ts +++ b/src/renderer/src/components/toast/toast.css.ts @@ -3,7 +3,7 @@ import { keyframes, style } from "@vanilla-extract/css"; import { SPACING_UNIT, vars } from "../../theme.css"; import { recipe } from "@vanilla-extract/recipes"; -const TOAST_HEIGHT = 55; +const TOAST_HEIGHT = 80; export const slideIn = keyframes({ "0%": { transform: `translateY(${TOAST_HEIGHT + SPACING_UNIT * 2}px)` }, @@ -19,12 +19,12 @@ export const toast = recipe({ base: { animationDuration: "0.2s", animationTimingFunction: "ease-in-out", - height: TOAST_HEIGHT, + maxHeight: TOAST_HEIGHT, position: "fixed", backgroundColor: vars.color.background, borderRadius: "4px", border: `solid 1px ${vars.color.border}`, - left: "50%", + right: `${SPACING_UNIT * 2}px`, /* Bottom panel height + 16px */ bottom: `${26 + SPACING_UNIT * 2}px`, overflow: "hidden", @@ -32,6 +32,7 @@ export const toast = recipe({ flexDirection: "column", justifyContent: "space-between", zIndex: "0", + maxWidth: "500px", }, variants: { closing: { diff --git a/src/renderer/src/components/toast/toast.tsx b/src/renderer/src/components/toast/toast.tsx index 0bded31d..371befde 100644 --- a/src/renderer/src/components/toast/toast.tsx +++ b/src/renderer/src/components/toast/toast.tsx @@ -82,6 +82,7 @@ export function Toast({ visible, message, type, onClose }: ToastProps) { {type === "success" && ( )} + {type === "error" && } {message} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ca86e95d..edc9bcb9 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -11,6 +11,7 @@ import type { DownloadProgress, UserPreferences, StartGameDownloadPayload, + RealDebridUser, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -88,13 +89,12 @@ declare global { ) => Promise; platform: NodeJS.Platform; - /* Splash */ + /* Auto update */ onAutoUpdaterEvent: ( cb: (event: AppUpdaterEvents) => void ) => () => Electron.IpcRenderer; checkForUpdates: () => Promise; restartAndInstallUpdate: () => Promise; - continueToMainWindow: () => Promise; } interface Window { diff --git a/src/renderer/src/features/toast-slice.ts b/src/renderer/src/features/toast-slice.ts index 112f641f..e27480fa 100644 --- a/src/renderer/src/features/toast-slice.ts +++ b/src/renderer/src/features/toast-slice.ts @@ -20,6 +20,7 @@ export const toastSlice = createSlice({ reducers: { showToast: (state, action: PayloadAction>) => { state.message = action.payload.message; + state.type = action.payload.type; state.visible = true; }, closeToast: (state) => { diff --git a/src/renderer/src/logger/index.ts b/src/renderer/src/logger/index.ts new file mode 100644 index 00000000..052b4452 --- /dev/null +++ b/src/renderer/src/logger/index.ts @@ -0,0 +1,3 @@ +import log from "electron-log/renderer"; + +export const logger = log.scope("renderer"); diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 3608af8d..a457592e 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -27,7 +27,6 @@ import { import { store } from "./store"; import * as resources from "@locales"; -import Splash from "./pages/splash/splash"; i18n .use(LanguageDetector) @@ -48,7 +47,6 @@ ReactDOM.createRoot(document.getElementById("root")!).render( - }> diff --git a/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx b/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx index 71fae213..d0a0c513 100644 --- a/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx @@ -8,6 +8,7 @@ import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react"; import { Downloader, formatBytes } from "@shared"; import type { GameRepack, UserPreferences } from "@types"; +import { SPACING_UNIT } from "@renderer/theme.css"; export interface SelectFolderModalProps { visible: boolean; @@ -95,7 +96,14 @@ export function SelectFolderModal({ >
- + + Method +
-
- +
+
+ - + +
+ +

+ + + +

-

- - - -

-