diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 1c948ba8..525d7803 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", @@ -176,11 +177,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 3a8f3343..b2e21889 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -176,11 +176,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 dc385156..e7c14f8c 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -176,11 +176,5 @@ }, "modal": { "close": "Pulsante Chiudi" - }, - "splash": { - "downloading_version": "Scaricando la versione {{version}}", - "searching_updates": "Ricerca di aggiornamenti", - "update_found": "Trovato aggiornamento {{version}}", - "restarting_and_applying": "Riavvio e applico l'aggiornamento" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 6894fb7c..7124f726 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/locales/ru/translation.json b/src/locales/ru/translation.json index 6094cb21..f7a80771 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -176,11 +176,5 @@ }, "modal": { "close": "Закрыть" - }, - "splash": { - "downloading_version": "Загрузка версии {{version}}", - "searching_updates": "Поиск обновлений", - "update_found": "Найдена новая версия {{version}}", - "restarting_and_applying": "Перезапуск и внесение изменений" } } diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 8481362a..134e83a1 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -174,11 +174,5 @@ }, "modal": { "close": "关闭按钮" - }, - "splash": { - "downloading_version": "正在下载新版本 {{version}}", - "searching_updates": "检查更新...", - "update_found": "有新版本 {{version}} 可用", - "restarting_and_applying": "重启并应用更新" } } 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 debca0e4..ae29d7b8 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"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => app.getVersion()); 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/services/window-manager.ts b/src/main/services/window-manager.ts index 7b2c87b7..1c46ebce 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -17,9 +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 isMainMaximized = false; private static loadURL(hash = "") { // HMR for renderer base on electron-vite cli. @@ -38,48 +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(); - if (this.splashWindow?.isMaximized()) { - this.splashWindow?.unmaximize(); - this.isMainMaximized = true; - } - } - public static createMainWindow() { - if (this.mainWindow || !this.isReadyToShowMainWindow) return; + if (this.mainWindow) return; this.mainWindow = new BrowserWindow({ width: 1200, @@ -104,7 +61,6 @@ export class WindowManager { this.loadURL(); this.mainWindow.removeMenu(); - if (this.isMainMaximized) this.mainWindow?.maximize(); this.mainWindow.on("ready-to-show", () => { if (!app.isPackaged) WindowManager.mainWindow?.webContents.openDevTools(); @@ -123,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 4ddf5009..90a753a8 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -114,7 +114,7 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("showOpenDialog", options), platform: process.platform, - /* Splash */ + /* Auto update */ onAutoUpdaterEvent: (cb: (value: AppUpdaterEvents) => void) => { const listener = ( _event: Electron.IpcRendererEvent, @@ -129,5 +129,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 705b533e..b6b65182 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/declaration.d.ts b/src/renderer/src/declaration.d.ts index 608f21a0..727f993d 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -92,13 +92,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/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/splash/splash.css.ts b/src/renderer/src/pages/splash/splash.css.ts deleted file mode 100644 index 36aacfff..00000000 --- a/src/renderer/src/pages/splash/splash.css.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { style } from "@vanilla-extract/css"; -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const main = style({ - width: "100%", - height: "100%", - display: "flex", - flexDirection: "column", - padding: `${SPACING_UNIT * 3}px`, - flex: "1", - overflowY: "auto", - alignItems: "center", -}); - -export const splashIcon = style({ - width: "75%", -}); - -export const updateInfoSection = style({ - width: "100%", - display: "flex", - flexDirection: "column", - gap: `${SPACING_UNIT * 2}px`, - flex: "1", - overflowY: "auto", - alignItems: "center", - justifyContent: "center", -}); - -export const progressBar = style({ - WebkitAppearance: "none", - appearance: "none", - borderRadius: "4px", - width: "100%", - border: `solid 1px ${vars.color.border}`, - overflow: "hidden", - height: "18px", - "::-webkit-progress-value": { - backgroundColor: vars.color.muted, - transition: "width 0.2s", - }, - "::-webkit-progress-bar": { - backgroundColor: vars.color.darkBackground, - }, -}); - -export const progressBarText = style({ - zIndex: 2, -}); diff --git a/src/renderer/src/pages/splash/splash.tsx b/src/renderer/src/pages/splash/splash.tsx deleted file mode 100644 index dec308c4..00000000 --- a/src/renderer/src/pages/splash/splash.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import icon from "@renderer/assets/icon.png"; -import * as styles from "./splash.css"; -import { themeClass } from "../../theme.css"; - -import "../../app.css"; -import { useEffect, useState } from "react"; -import { AppUpdaterEvents } from "@types"; -import { useTranslation } from "react-i18next"; - -document.body.classList.add(themeClass); - -export default function Splash() { - const [status, setStatus] = useState(null); - const [newVersion, setNewVersion] = useState(""); - - const { t } = useTranslation("splash"); - - useEffect(() => { - const unsubscribe = window.electron.onAutoUpdaterEvent( - (event: AppUpdaterEvents) => { - setStatus(event); - - switch (event.type) { - case "error": - window.electron.continueToMainWindow(); - break; - case "update-available": - setNewVersion(event.info.version); - break; - case "update-cancelled": - window.electron.continueToMainWindow(); - break; - case "update-downloaded": - window.electron.restartAndInstallUpdate(); - break; - case "update-not-available": - window.electron.continueToMainWindow(); - break; - } - } - ); - - window.electron.checkForUpdates(); - - return () => { - unsubscribe(); - }; - }, []); - - const renderUpdateInfo = () => { - switch (status?.type) { - case "download-progress": - return ( - <> -

{t("downloading_version", { version: newVersion })}

- - - ); - case "checking-for-updates": - return

{t("searching_updates")}

; - case "update-available": - return

{t("update_found", { version: newVersion })}

; - case "update-downloaded": - return

{t("restarting_and_applying")}

; - default: - return <>; - } - }; - - return ( -
- Hydra Launcher Logo -
- {renderUpdateInfo()} -
-
- ); -} diff --git a/src/types/index.ts b/src/types/index.ts index 0abce31f..958708f2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,5 @@ import type { Downloader, GameStatus } from "@shared"; -import { ProgressInfo, UpdateInfo } from "electron-updater"; +import { UpdateInfo } from "electron-updater"; export type GameShop = "steam" | "epic"; export type CatalogueCategory = "recently_added" | "trending"; @@ -146,10 +146,5 @@ export interface SteamGame { } export type AppUpdaterEvents = - | { type: "error" } - | { type: "checking-for-updates" } - | { type: "update-not-available" } - | { type: "update-available"; info: UpdateInfo } - | { type: "update-downloaded" } - | { type: "download-progress"; info: ProgressInfo } - | { type: "update-cancelled" }; + | { type: "update-available"; info: Partial } + | { type: "update-downloaded" };