diff --git a/python_rpc/main.py b/python_rpc/main.py index 03df83de..cc28d623 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -94,7 +94,7 @@ def seed_status(): @app.route("/healthcheck", methods=["GET"]) def healthcheck(): - return "", 200 + return "ok", 200 @app.route("/process-list", methods=["GET"]) def process_list(): diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 9e1021fc..0af6dd9d 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -172,7 +172,8 @@ "reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}", "reset_achievements_title": "Tem certeza?", "reset_achievements_success": "Conquistas resetadas com sucesso", - "reset_achievements_error": "Falha ao resetar conquistas" + "reset_achievements_error": "Falha ao resetar conquistas", + "no_write_permission": "Não é possível baixar nesse diretório. Clique aqui para saber mais." }, "activation": { "title": "Ativação", diff --git a/src/main/events/autoupdater/check-for-updates.ts b/src/main/events/autoupdater/check-for-updates.ts index 1dcc80f3..7ea60d0b 100644 --- a/src/main/events/autoupdater/check-for-updates.ts +++ b/src/main/events/autoupdater/check-for-updates.ts @@ -1,47 +1,8 @@ -import type { AppUpdaterEvent } from "@types"; import { registerEvent } from "../register-event"; -import updater, { UpdateInfo } from "electron-updater"; -import { WindowManager } from "@main/services"; -import { app } from "electron"; -import { publishNotificationUpdateReadyToInstall } from "@main/services/notifications"; - -const { autoUpdater } = updater; - -const sendEvent = (event: AppUpdaterEvent) => { - WindowManager.mainWindow?.webContents.send("autoUpdaterEvent", event); -}; - -const sendEventsForDebug = false; - -const isAutoInstallAvailable = - process.platform !== "darwin" && process.env.PORTABLE_EXECUTABLE_FILE == null; - -const mockValuesForDebug = () => { - sendEvent({ type: "update-available", info: { version: "1.3.0" } }); - sendEvent({ type: "update-downloaded" }); -}; - -const newVersionInfo = { version: "" }; +import { UpdateManager } from "@main/services/update-manager"; const checkForUpdates = async (_event: Electron.IpcMainInvokeEvent) => { - autoUpdater - .once("update-available", (info: UpdateInfo) => { - sendEvent({ type: "update-available", info }); - newVersionInfo.version = info.version; - }) - .once("update-downloaded", () => { - sendEvent({ type: "update-downloaded" }); - publishNotificationUpdateReadyToInstall(newVersionInfo.version); - }); - - if (app.isPackaged) { - autoUpdater.autoDownload = isAutoInstallAvailable; - autoUpdater.checkForUpdates(); - } else if (sendEventsForDebug) { - mockValuesForDebug(); - } - - return isAutoInstallAvailable; + return UpdateManager.checkForUpdates(); }; registerEvent("checkForUpdates", checkForUpdates); diff --git a/src/main/events/hardware/check-folder-write-permission.ts b/src/main/events/hardware/check-folder-write-permission.ts index c74f01e7..af896e98 100644 --- a/src/main/events/hardware/check-folder-write-permission.ts +++ b/src/main/events/hardware/check-folder-write-permission.ts @@ -1,15 +1,21 @@ import fs from "node:fs"; +import path from "node:path"; import { registerEvent } from "../register-event"; const checkFolderWritePermission = async ( _event: Electron.IpcMainInvokeEvent, - path: string -) => - new Promise((resolve) => { - fs.access(path, fs.constants.W_OK, (err) => { - resolve(!err); - }); - }); + testPath: string +) => { + const testFilePath = path.join(testPath, ".hydra-write-test"); + + try { + fs.writeFileSync(testFilePath, ""); + fs.rmSync(testFilePath); + return true; + } catch (err) { + return false; + } +}; registerEvent("checkFolderWritePermission", checkFolderWritePermission); diff --git a/src/main/events/helpers/parse-launch-options.ts b/src/main/events/helpers/parse-launch-options.ts new file mode 100644 index 00000000..89a0c611 --- /dev/null +++ b/src/main/events/helpers/parse-launch-options.ts @@ -0,0 +1,7 @@ +export const parseLaunchOptions = (params?: string | null): string[] => { + if (!params) { + return []; + } + + return params.split(" "); +}; diff --git a/src/main/events/library/open-game.ts b/src/main/events/library/open-game.ts index 3bbf7cdc..64e3d5fb 100644 --- a/src/main/events/library/open-game.ts +++ b/src/main/events/library/open-game.ts @@ -1,8 +1,10 @@ import { registerEvent } from "../register-event"; import { shell } from "electron"; +import { spawn } from "child_process"; import { parseExecutablePath } from "../helpers/parse-executable-path"; import { gamesSublevel, levelKeys } from "@main/level"; import { GameShop } from "@types"; +import { parseLaunchOptions } from "../helpers/parse-launch-options"; const openGame = async ( _event: Electron.IpcMainInvokeEvent, @@ -11,8 +13,8 @@ const openGame = async ( executablePath: string, launchOptions?: string | null ) => { - // TODO: revisit this for launchOptions const parsedPath = parseExecutablePath(executablePath); + const parsedParams = parseLaunchOptions(launchOptions); const gameKey = levelKeys.game(shop, objectId); @@ -26,7 +28,12 @@ const openGame = async ( launchOptions, }); - shell.openPath(parsedPath); + if (parsedParams.length === 0) { + shell.openPath(parsedPath); + return; + } + + spawn(parsedPath, parsedParams, { shell: false, detached: true }); }; registerEvent("openGame", openGame); diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index 7b90e483..f5a04f0d 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -7,7 +7,7 @@ import { omit } from "lodash-es"; import axios from "axios"; import { fileTypeFromFile } from "file-type"; -const patchUserProfile = async (updateProfile: UpdateProfileRequest) => { +export const patchUserProfile = async (updateProfile: UpdateProfileRequest) => { return HydraApi.patch("/profile", updateProfile); }; diff --git a/src/main/events/user-preferences/update-user-preferences.ts b/src/main/events/user-preferences/update-user-preferences.ts index 6001a85c..caec78a1 100644 --- a/src/main/events/user-preferences/update-user-preferences.ts +++ b/src/main/events/user-preferences/update-user-preferences.ts @@ -3,6 +3,7 @@ import { registerEvent } from "../register-event"; import type { UserPreferences } from "@types"; import i18next from "i18next"; import { db, levelKeys } from "@main/level"; +import { patchUserProfile } from "../profile/update-profile"; const updateUserPreferences = async ( _event: Electron.IpcMainInvokeEvent, @@ -19,6 +20,7 @@ const updateUserPreferences = async ( }); i18next.changeLanguage(preferences.language); + patchUserProfile({ language: preferences.language }).catch(() => {}); } await db.put( diff --git a/src/main/index.ts b/src/main/index.ts index 0f7c0297..2a18fa31 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,6 +9,7 @@ import resources from "@locales"; import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; import { db, levelKeys } from "./level"; +import { loadState } from "./main"; const { autoUpdater } = updater; @@ -57,7 +58,7 @@ app.whenReady().then(async () => { return net.fetch(url.pathToFileURL(decodeURI(filePath)).toString()); }); - await import("./main"); + await loadState(); const language = await db.get(levelKeys.language, { valueEncoding: "utf-8", diff --git a/src/main/main.ts b/src/main/main.ts index f7ad596d..d5d23cdb 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -21,8 +21,18 @@ import { import { Auth, User, type UserPreferences } from "@types"; import { knexClient } from "./knex-client"; -const loadState = async (userPreferences: UserPreferences | null) => { - import("./events"); +export const loadState = async () => { + const userPreferences = await migrateFromSqlite().then(async () => { + await db.put(levelKeys.sqliteMigrationDone, true, { + valueEncoding: "json", + }); + + return db.get(levelKeys.userPreferences, { + valueEncoding: "json", + }); + }); + + await import("./events"); Aria2.spawn(); @@ -192,15 +202,3 @@ const migrateFromSqlite = async () => { migrateUser, ]); }; - -migrateFromSqlite().then(async () => { - await db.put(levelKeys.sqliteMigrationDone, true, { - valueEncoding: "json", - }); - - db.get(levelKeys.userPreferences, { - valueEncoding: "json", - }).then((userPreferences) => { - loadState(userPreferences); - }); -}); diff --git a/src/main/services/achievements/achievement-watcher-manager.ts b/src/main/services/achievements/achievement-watcher-manager.ts index d1111b0d..8b076d9e 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -141,7 +141,7 @@ const processAchievementFileDiff = async ( export class AchievementWatcherManager { private static hasFinishedMergingWithRemote = false; - public static watchAchievements = () => { + public static watchAchievements() { if (!this.hasFinishedMergingWithRemote) return; if (process.platform === "win32") { @@ -149,12 +149,12 @@ export class AchievementWatcherManager { } return watchAchievementsWithWine(); - }; + } - private static preProcessGameAchievementFiles = ( + private static preProcessGameAchievementFiles( game: Game, gameAchievementFiles: AchievementFile[] - ) => { + ) { const unlockedAchievements: UnlockedAchievement[] = []; for (const achievementFile of gameAchievementFiles) { const parsedAchievements = parseAchievementFile( @@ -182,7 +182,7 @@ export class AchievementWatcherManager { } return mergeAchievements(game, unlockedAchievements, false); - }; + } private static preSearchAchievementsWindows = async () => { const games = await gamesSublevel @@ -230,7 +230,7 @@ export class AchievementWatcherManager { ); }; - public static preSearchAchievements = async () => { + public static async preSearchAchievements() { try { const newAchievementsCount = process.platform === "win32" @@ -256,5 +256,5 @@ export class AchievementWatcherManager { } this.hasFinishedMergingWithRemote = true; - }; + } } diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 4c03c7e1..0d0c58f9 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -40,7 +40,7 @@ export const getGameAchievementData = async ( throw err; } - logger.error("Failed to get game achievements", err); + logger.error("Failed to get game achievements for", objectId, err); return []; }); diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index a2541d5e..7e6ebf0a 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -59,7 +59,7 @@ export const mergeAchievements = async ( const unlockedAchievements = localGameAchievement?.unlockedAchievements ?? []; const newAchievementsMap = new Map( - achievements.reverse().map((achievement) => { + achievements.toReversed().map((achievement) => { return [achievement.name.toUpperCase(), achievement]; }) ); @@ -87,7 +87,7 @@ export const mergeAchievements = async ( userPreferences?.achievementNotificationsEnabled ) { const achievementsInfo = newAchievements - .sort((a, b) => { + .toSorted((a, b) => { return a.unlockTime - b.unlockTime; }) .map((achievement) => { diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 4d5623a0..ba972b44 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -3,7 +3,7 @@ import { WindowManager } from "./window-manager"; import url from "url"; import { uploadGamesBatch } from "./library-sync"; import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id"; -import { logger } from "./logger"; +import { networkLogger as logger } from "./logger"; import { UserNotLoggedInError, SubscriptionRequiredError } from "@shared"; import { omit } from "lodash-es"; import { appVersion } from "@main/constants"; @@ -32,7 +32,8 @@ export class HydraApi { private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes private static readonly ADD_LOG_INTERCEPTOR = true; - private static secondsToMilliseconds = (seconds: number) => seconds * 1000; + private static readonly secondsToMilliseconds = (seconds: number) => + seconds * 1000; private static userAuth: HydraApiUserAuth = { authToken: "", @@ -153,7 +154,8 @@ export class HydraApi { (error) => { logger.error(" ---- RESPONSE ERROR -----"); const { config } = error; - const data = JSON.parse(config.data); + + const data = JSON.parse(config.data ?? null); logger.error( config.method, @@ -174,14 +176,22 @@ export class HydraApi { error.response.status, error.response.data ); - } else if (error.request) { - const errorData = error.toJSON(); - logger.error("Request error:", errorData.message); - } else { - logger.error("Error", error.message); + + return Promise.reject(error as Error); } - logger.error(" ----- END RESPONSE ERROR -------"); - return Promise.reject(error); + + if (error.request) { + const errorData = error.toJSON(); + logger.error("Request error:", errorData.code, errorData.message); + return Promise.reject( + new Error( + `Request failed with ${errorData.code} ${errorData.message}` + ) + ); + } + + logger.error("Error", error.message); + return Promise.reject(error as Error); } ); } diff --git a/src/main/services/logger.ts b/src/main/services/logger.ts index 95a399ea..03bf6ad7 100644 --- a/src/main/services/logger.ts +++ b/src/main/services/logger.ts @@ -6,8 +6,12 @@ log.transports.file.resolvePathFn = ( _: log.PathVariables, message?: log.LogMessage | undefined ) => { - if (message?.scope === "python-instance") { - return path.join(logsPath, "pythoninstance.txt"); + if (message?.scope === "python-rpc") { + return path.join(logsPath, "pythonrpc.txt"); + } + + if (message?.scope === "network") { + return path.join(logsPath, "network.txt"); } if (message?.scope == "achievements") { @@ -34,3 +38,4 @@ log.initialize(); export const pythonRpcLogger = log.scope("python-rpc"); export const logger = log.scope("main"); export const achievementsLogger = log.scope("achievements"); +export const networkLogger = log.scope("network"); diff --git a/src/main/services/main-loop.ts b/src/main/services/main-loop.ts index a1c2b449..12b6e3a7 100644 --- a/src/main/services/main-loop.ts +++ b/src/main/services/main-loop.ts @@ -2,6 +2,7 @@ import { sleep } from "@main/helpers"; import { DownloadManager } from "./download"; import { watchProcesses } from "./process-watcher"; import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager"; +import { UpdateManager } from "./update-manager"; export const startMainLoop = async () => { // eslint-disable-next-line no-constant-condition @@ -11,6 +12,7 @@ export const startMainLoop = async () => { DownloadManager.watchDownloads(), AchievementWatcherManager.watchAchievements(), DownloadManager.getSeedStatus(), + UpdateManager.checkForUpdatePeriodically(), ]); await sleep(1500); diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index c9be2f54..63c666dc 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -9,6 +9,7 @@ import { achievementSoundPath } from "@main/constants"; import icon from "@resources/icon.png?asset"; import { NotificationOptions, toXmlString } from "./xml"; import { logger } from "../logger"; +import { WindowManager } from "../window-manager"; import type { Game, UserPreferences } from "@types"; import { db, levelKeys } from "@main/level"; @@ -96,7 +97,9 @@ export const publishCombinedNewAchievementNotification = async ( toastXml: toXmlString(options), }).show(); - if (process.platform !== "linux") { + if (WindowManager.mainWindow) { + WindowManager.mainWindow.webContents.send("on-achievement-unlocked"); + } else if (process.platform !== "linux") { sound.play(achievementSoundPath); } }; @@ -143,7 +146,9 @@ export const publishNewAchievementNotification = async (info: { toastXml: toXmlString(options), }).show(); - if (process.platform !== "linux") { + if (WindowManager.mainWindow) { + WindowManager.mainWindow.webContents.send("on-achievement-unlocked"); + } else if (process.platform !== "linux") { sound.play(achievementSoundPath); } }; diff --git a/src/main/services/update-manager.ts b/src/main/services/update-manager.ts new file mode 100644 index 00000000..9a277dd7 --- /dev/null +++ b/src/main/services/update-manager.ts @@ -0,0 +1,60 @@ +import updater, { UpdateInfo } from "electron-updater"; +import { logger, WindowManager } from "@main/services"; +import { AppUpdaterEvent } from "@types"; +import { app } from "electron"; +import { publishNotificationUpdateReadyToInstall } from "@main/services/notifications"; + +const isAutoInstallAvailable = + process.platform !== "darwin" && process.env.PORTABLE_EXECUTABLE_FILE == null; + +const { autoUpdater } = updater; +const sendEventsForDebug = false; + +export class UpdateManager { + private static hasNotified = false; + private static newVersion = ""; + private static checkTick = 0; + + private static mockValuesForDebug() { + this.sendEvent({ type: "update-available", info: { version: "1.3.0" } }); + this.sendEvent({ type: "update-downloaded" }); + } + + private static sendEvent(event: AppUpdaterEvent) { + WindowManager.mainWindow?.webContents.send("autoUpdaterEvent", event); + } + + public static checkForUpdates() { + autoUpdater + .once("update-available", (info: UpdateInfo) => { + this.sendEvent({ type: "update-available", info }); + this.newVersion = info.version; + }) + .once("update-downloaded", () => { + this.sendEvent({ type: "update-downloaded" }); + + if (!this.hasNotified) { + this.hasNotified = true; + publishNotificationUpdateReadyToInstall(this.newVersion); + } + }); + + if (app.isPackaged) { + autoUpdater.autoDownload = isAutoInstallAvailable; + autoUpdater.checkForUpdates().then((result) => { + logger.log(`Check for updates result: ${result}`); + }); + } else if (sendEventsForDebug) { + this.mockValuesForDebug(); + } + + return isAutoInstallAvailable; + } + + public static checkForUpdatePeriodically() { + if (this.checkTick % 2000 == 0) { + this.checkForUpdates(); + } + this.checkTick++; + } +} diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index ed07c61e..d26c995d 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -29,7 +29,6 @@ export const getUserData = async () => { }) .catch(async (err) => { if (err instanceof UserNotLoggedInError) { - logger.info("User is not logged in", err); return null; } logger.error("Failed to get logged user"); @@ -59,6 +58,7 @@ export const getUserData = async () => { expiresAt: loggedUser.subscription.expiresAt, } : null, + featurebaseJwt: "", } as UserDetails; } diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index cf9089b7..70e7255c 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -50,7 +50,7 @@ export class WindowManager { minHeight: 540, backgroundColor: "#1c1c1c", titleBarStyle: process.platform === "linux" ? "default" : "hidden", - ...(process.platform === "linux" ? { icon } : {}), + icon, trafficLightPosition: { x: 16, y: 16 }, titleBarOverlay: { symbolColor: "#DADBE1", @@ -145,6 +145,11 @@ export class WindowManager { WindowManager.mainWindow?.setProgressBar(-1); WindowManager.mainWindow = null; }); + + this.mainWindow.webContents.setWindowOpenHandler((handler) => { + shell.openExternal(handler.url); + return { action: "deny" }; + }); } public static openAuthWindow(page: AuthPage, searchParams: URLSearchParams) { diff --git a/src/preload/index.ts b/src/preload/index.ts index 588becdc..eac3c0a1 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -169,6 +169,12 @@ contextBridge.exposeInMainWorld("electron", { return () => ipcRenderer.removeListener("on-library-batch-complete", listener); }, + onAchievementUnlocked: (cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on("on-achievement-unlocked", listener); + return () => + ipcRenderer.removeListener("on-achievement-unlocked", listener); + }, /* Hardware */ getDiskFreeSpace: (path: string) => diff --git a/src/renderer/src/app.css.ts b/src/renderer/src/app.css.ts index 25c453c8..b5c4740e 100644 --- a/src/renderer/src/app.css.ts +++ b/src/renderer/src/app.css.ts @@ -123,7 +123,7 @@ export const titleBar = style({ alignItems: "center", padding: `0 ${SPACING_UNIT * 2}px`, WebkitAppRegion: "drag", - zIndex: "4", + zIndex: vars.zIndex.titleBar, borderBottom: `1px solid ${vars.color.border}`, } as ComplexStyleRule); diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index b03fb540..411e2c04 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef } from "react"; - +import achievementSound from "@renderer/assets/audio/achievement.wav"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; import { @@ -233,13 +233,29 @@ export function App() { downloadSourcesWorker.postMessage(["SYNC_DOWNLOAD_SOURCES", id]); }, [updateRepacks]); + const playAudio = useCallback(() => { + const audio = new Audio(achievementSound); + audio.volume = 0.2; + audio.play(); + }, []); + + useEffect(() => { + const unsubscribe = window.electron.onAchievementUnlocked(() => { + playAudio(); + }); + + return () => { + unsubscribe(); + }; + }, [playAudio]); + const handleToastClose = useCallback(() => { dispatch(closeToast()); }, [dispatch]); return ( <> - {window.electron.platform === "win32" && ( + {/* {window.electron.platform === "win32" && (

Hydra @@ -248,7 +264,15 @@ export function App() { )}

- )} + )} */} +
+

+ Hydra + {hasActiveSubscription && ( + Cloud + )} +

+
{status} - - {sessionHash ? `${sessionHash} -` : ""} v{version} " - {VERSION_CODENAME}" - + ); } diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 390b8c5e..eaf5cb49 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -142,6 +142,7 @@ declare global { minimized: boolean; }) => Promise; authenticateRealDebrid: (apiToken: string) => Promise; + onAchievementUnlocked: (cb: () => void) => () => Electron.IpcRenderer; /* Download sources */ putDownloadSource: ( diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 0679cde8..2fdac382 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -78,9 +78,15 @@ export function useUserDetails() { ...response, username: userDetails?.username || "", subscription: userDetails?.subscription || null, + featurebaseJwt: userDetails?.featurebaseJwt || "", }); }, - [updateUserDetails, userDetails?.username, userDetails?.subscription] + [ + updateUserDetails, + userDetails?.username, + userDetails?.subscription, + userDetails?.featurebaseJwt, + ] ); const syncFriendRequests = useCallback(async () => { diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 61c561f1..cb6ba45f 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -45,6 +45,7 @@ Sentry.init({ tracesSampleRate: 1.0, replaysSessionSampleRate: 0.1, replaysOnErrorSampleRate: 1.0, + release: await window.electron.getVersion(), }); console.log = logger.log; diff --git a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx index 541bd01c..acb4c169 100644 --- a/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/download-settings-modal.tsx @@ -98,9 +98,7 @@ export function DownloadSettingsModal({ ? Downloader.RealDebrid : filteredDownloaders[0]; - setSelectedDownloader( - selectedDownloader === undefined ? null : selectedDownloader - ); + setSelectedDownloader(selectedDownloader ?? null); }, [ userPreferences?.downloadsPath, downloaders, diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx index 8a872df9..54edafb7 100644 --- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx @@ -183,8 +183,6 @@ export function GameOptionsModal({ } }; - const shouldShowLaunchOptionsConfiguration = false; - return ( <> )} - {shouldShowLaunchOptionsConfiguration && ( +

{t("launch_options")}

{t("launch_options_description")}

- - {t("clear")} - - ) - } - />
- )} + + + {t("clear")} + + ) + } + /> +

{t("downloads_secion_title")}

diff --git a/src/renderer/src/pages/settings/settings-account.tsx b/src/renderer/src/pages/settings/settings-account.tsx index d43e5b2d..e8bac125 100644 --- a/src/renderer/src/pages/settings/settings-account.tsx +++ b/src/renderer/src/pages/settings/settings-account.tsx @@ -65,7 +65,7 @@ export function SettingsAccount() { return () => { unsubscribe(); }; - }, [fetchUserDetails, updateUserDetails]); + }, [fetchUserDetails, updateUserDetails, showSuccessToast]); const visibilityOptions = [ { value: "PUBLIC", label: t("public") }, diff --git a/src/renderer/src/scss/globals.scss b/src/renderer/src/scss/globals.scss index cc01c197..792abf86 100644 --- a/src/renderer/src/scss/globals.scss +++ b/src/renderer/src/scss/globals.scss @@ -16,6 +16,6 @@ $spacing-unit: 8px; $toast-z-index: 5; $bottom-panel-z-index: 3; -$title-bar-z-index: 4; +$title-bar-z-index: 1900000001; $backdrop-z-index: 4; $modal-z-index: 5; diff --git a/src/renderer/src/theme.css.ts b/src/renderer/src/theme.css.ts index b9fbaf55..7cd92ef3 100644 --- a/src/renderer/src/theme.css.ts +++ b/src/renderer/src/theme.css.ts @@ -24,7 +24,7 @@ export const vars = createGlobalTheme(":root", { zIndex: { toast: "5", bottomPanel: "3", - titleBar: "4", + titleBar: "1900000001", backdrop: "4", }, }); diff --git a/src/types/index.ts b/src/types/index.ts index fdca8009..1e089d08 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -139,6 +139,7 @@ export interface UserDetails { backgroundImageUrl: string | null; profileVisibility: ProfileVisibility; bio: string; + featurebaseJwt: string; subscription: Subscription | null; quirks?: { backupsPerGameLimit: number; @@ -171,6 +172,7 @@ export interface UpdateProfileRequest { profileImageUrl?: string | null; backgroundImageUrl?: string | null; bio?: string; + language?: string; } export interface DownloadSourceDownload {