diff --git a/python_rpc/main.py b/python_rpc/main.py index 03df83dee..3650c668e 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -4,6 +4,11 @@ from http_downloader import HttpDownloader from profile_image_processor import ProfileImageProcessor import libtorrent as lt +import logging + +log = logging.getLogger('werkzeug') + +log.setLevel(logging.ERROR) app = Flask(__name__) @@ -94,7 +99,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 2a80084f3..cd9cc0b7f 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/hardware/check-folder-write-permission.ts b/src/main/events/hardware/check-folder-write-permission.ts index c74f01e70..af896e983 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 000000000..5db895678 --- /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 cf73c8109..736fde966 100644 --- a/src/main/events/library/open-game.ts +++ b/src/main/events/library/open-game.ts @@ -2,7 +2,9 @@ import { gameRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { shell } from "electron"; +import { spawn } from "child_process"; import { parseExecutablePath } from "../helpers/parse-executable-path"; +import { parseLaunchOptions } from "../helpers/parse-launch-options"; const openGame = async ( _event: Electron.IpcMainInvokeEvent, @@ -10,15 +12,20 @@ const openGame = async ( executablePath: string, launchOptions: string | null ) => { - // TODO: revisit this for launchOptions const parsedPath = parseExecutablePath(executablePath); + const parsedParams = parseLaunchOptions(launchOptions); await gameRepository.update( { id: gameId }, { executablePath: parsedPath, 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 7b90e4833..f5a04f0d0 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 f45af5193..33c800596 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 { patchUserProfile } from "../profile/update-profile"; const updateUserPreferences = async ( _event: Electron.IpcMainInvokeEvent, @@ -10,6 +11,7 @@ const updateUserPreferences = async ( ) => { if (preferences.language) { i18next.changeLanguage(preferences.language); + patchUserProfile({ language: preferences.language }).catch(() => {}); } return userPreferencesRepository.upsert( diff --git a/src/main/index.ts b/src/main/index.ts index ca49a9fb0..a1c45402a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -13,6 +13,7 @@ import { knexClient, migrationConfig } from "./knex-client"; import { databaseDirectory } from "./constants"; import { PythonRPC } from "./services/python-rpc"; import { Aria2 } from "./services/aria2"; +import { loadState } from "./main"; const { autoUpdater } = updater; @@ -86,12 +87,12 @@ app.whenReady().then(async () => { await dataSource.initialize(); - await import("./main"); - const userPreferences = await userPreferencesRepository.findOne({ where: { id: 1 }, }); + await loadState(userPreferences); + if (userPreferences?.language) { i18n.changeLanguage(userPreferences.language); } diff --git a/src/main/main.ts b/src/main/main.ts index add619e18..456d5d8ff 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,9 +1,5 @@ import { DownloadManager, Ludusavi, startMainLoop } from "./services"; -import { - downloadQueueRepository, - gameRepository, - userPreferencesRepository, -} from "./repository"; +import { downloadQueueRepository, gameRepository } from "./repository"; import { UserPreferences } from "./entity"; import { RealDebridClient } from "./services/download/real-debrid"; import { HydraApi } from "./services/hydra-api"; @@ -12,8 +8,8 @@ import { Aria2 } from "./services/aria2"; import { Downloader } from "@shared"; import { IsNull, Not } from "typeorm"; -const loadState = async (userPreferences: UserPreferences | null) => { - import("./events"); +export const loadState = async (userPreferences: UserPreferences | null) => { + await import("./events"); Aria2.spawn(); @@ -49,11 +45,3 @@ const loadState = async (userPreferences: UserPreferences | null) => { startMainLoop(); }; - -userPreferencesRepository - .findOne({ - where: { id: 1 }, - }) - .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 6a1eb11c5..2cc7a4f4a 100644 --- a/src/main/services/achievements/achievement-watcher-manager.ts +++ b/src/main/services/achievements/achievement-watcher-manager.ts @@ -144,7 +144,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") { @@ -152,12 +152,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( @@ -185,9 +185,9 @@ export class AchievementWatcherManager { } return mergeAchievements(game, unlockedAchievements, false); - }; + } - private static preSearchAchievementsWindows = async () => { + private static async preSearchAchievementsWindows() { const games = await gameRepository.find({ where: { isDeleted: false, @@ -213,9 +213,9 @@ export class AchievementWatcherManager { return this.preProcessGameAchievementFiles(game, gameAchievementFiles); }) ); - }; + } - private static preSearchAchievementsWithWine = async () => { + private static async preSearchAchievementsWithWine() { const games = await gameRepository.find({ where: { isDeleted: false, @@ -233,9 +233,9 @@ export class AchievementWatcherManager { return this.preProcessGameAchievementFiles(game, gameAchievementFiles); }) ); - }; + } - public static preSearchAchievements = async () => { + public static async preSearchAchievements() { try { const newAchievementsCount = process.platform === "win32" @@ -261,5 +261,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 daac7e113..b1a7476fb 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -13,7 +13,7 @@ export const getGameAchievementData = async ( shop: GameShop, cachedAchievements: GameAchievement | null ) => { - if (cachedAchievements && cachedAchievements.achievements) { + if (cachedAchievements?.achievements) { return JSON.parse(cachedAchievements.achievements) as AchievementData[]; } @@ -42,7 +42,7 @@ export const getGameAchievementData = async ( if (err instanceof UserNotLoggedInError) { throw err; } - logger.error("Failed to get game achievements", err); + logger.error("Failed to get game achievements for", objectId, err); return gameAchievementRepository .findOne({ where: { objectId, shop }, diff --git a/src/main/services/achievements/merge-achievements.ts b/src/main/services/achievements/merge-achievements.ts index dd8c877dd..61a444c5b 100644 --- a/src/main/services/achievements/merge-achievements.ts +++ b/src/main/services/achievements/merge-achievements.ts @@ -64,7 +64,7 @@ export const mergeAchievements = async ( ).filter((achievement) => achievement.name) as UnlockedAchievement[]; const newAchievementsMap = new Map( - achievements.reverse().map((achievement) => { + achievements.toReversed().map((achievement) => { return [achievement.name.toUpperCase(), achievement]; }) ); @@ -92,7 +92,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/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts index d77e7d514..ae1444180 100644 --- a/src/main/services/hosters/datanodes.ts +++ b/src/main/services/hosters/datanodes.ts @@ -33,7 +33,8 @@ export class DatanodesApi { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", }, - maxRedirects: 0, validateStatus: (status: number) => status === 302 || status < 400, + maxRedirects: 0, + validateStatus: (status: number) => status === 302 || status < 400, } ); diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 63dd9b16f..dff530dcf 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -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) { + + return Promise.reject(error as Error); + } + + if (error.request) { const errorData = error.toJSON(); - logger.error("Request error:", errorData.message); - } else { - logger.error("Error", error.message); + logger.error("Request error:", errorData.code, errorData.message); + return Promise.reject( + new Error( + `Request failed with ${errorData.code} ${errorData.message}` + ) + ); } - logger.error(" ----- END RESPONSE ERROR -------"); - return Promise.reject(error); + + logger.error("Error", error.message); + return Promise.reject(error as Error); } ); } @@ -261,7 +271,7 @@ export class HydraApi { }; } - private static handleUnauthorizedError = (err) => { + private static readonly handleUnauthorizedError = (err) => { if (err instanceof AxiosError && err.response?.status === 401) { logger.error( "401 - Current credentials:", diff --git a/src/main/services/logger.ts b/src/main/services/logger.ts index 95a399ea3..eee85ddf3 100644 --- a/src/main/services/logger.ts +++ b/src/main/services/logger.ts @@ -6,8 +6,8 @@ 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 == "achievements") { diff --git a/src/main/services/notifications/index.ts b/src/main/services/notifications/index.ts index f3e2541be..e33cd6ecf 100644 --- a/src/main/services/notifications/index.ts +++ b/src/main/services/notifications/index.ts @@ -11,6 +11,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"; async function downloadImage(url: string | null) { if (!url) return undefined; @@ -93,7 +94,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); } }; @@ -140,7 +143,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/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index 7e9244547..bea8f4d0e 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -42,7 +42,6 @@ export const getUserData = () => { }) .catch(async (err) => { if (err instanceof UserNotLoggedInError) { - logger.info("User is not logged in", err); return null; } logger.error("Failed to get logged user"); @@ -73,6 +72,7 @@ export const getUserData = () => { 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 a7cfcee2a..4b00910db 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -48,7 +48,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", @@ -140,6 +140,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() { diff --git a/src/preload/index.ts b/src/preload/index.ts index 316397d22..05388a8df 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -148,6 +148,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 25c453c85..b5c4740e5 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 5fefe90c2..e461c0cb7 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/components/header/auto-update-sub-header.tsx b/src/renderer/src/components/header/auto-update-sub-header.tsx index 005cdfda1..cbba001ee 100644 --- a/src/renderer/src/components/header/auto-update-sub-header.tsx +++ b/src/renderer/src/components/header/auto-update-sub-header.tsx @@ -4,10 +4,13 @@ import { SyncIcon } from "@primer/octicons-react"; import { Link } from "../link/link"; import * as styles from "./header.css"; import type { AppUpdaterEvent } from "@types"; +import { minutesToMilliseconds } from "date-fns"; export const releasesPageUrl = "https://github.com/hydralauncher/hydra/releases/latest"; +const CHECK_FOR_UPDATES_INTERVAL = minutesToMilliseconds(60); + export function AutoUpdateSubHeader() { const [isReadyToInstall, setIsReadyToInstall] = useState(false); const [newVersion, setNewVersion] = useState(null); @@ -32,15 +35,24 @@ export function AutoUpdateSubHeader() { } ); - window.electron.checkForUpdates().then((isAutoInstallAvailable) => { - setIsAutoInstallAvailable(isAutoInstallAvailable); - }); - return () => { unsubscribe(); }; }, []); + useEffect(() => { + const checkInterval = setInterval(() => { + window.electron.checkForUpdates().then((isAutoInstallAvailable) => { + console.log("checking for updates"); + setIsAutoInstallAvailable(isAutoInstallAvailable); + }); + }, CHECK_FOR_UPDATES_INTERVAL); + + return () => { + clearInterval(checkInterval); + }; + }, []); + if (!newVersion) return null; if (!isAutoInstallAvailable) { diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index 49e56ab78..ad33b26e5 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -30,7 +30,7 @@ export function SidebarProfile() { return; } - navigate(`/profile/${userDetails!.id}`); + navigate(`/profile/${userDetails.id}`); }; useEffect(() => { diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 88f3297f1..744183334 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -133,6 +133,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 0679cde84..0e5ae5010 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -78,6 +78,7 @@ export function useUserDetails() { ...response, username: userDetails?.username || "", subscription: userDetails?.subscription || null, + featurebaseJwt: userDetails?.featurebaseJwt || "", }); }, [updateUserDetails, userDetails?.username, userDetails?.subscription] diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 61c561f12..cb6ba45f3 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/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 88cf1433d..14eab11ea 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -130,15 +130,16 @@ export function DownloadGroup({ if (game.progress === 1) { const uploadSpeed = formatBytes(seedingStatus?.uploadSpeed ?? 0); - return game.status === "seeding" && - game.downloader === Downloader.Torrent ? ( - <> -

{t("seeding")}

- {uploadSpeed &&

{uploadSpeed}/s

} - - ) : ( -

{t("completed")}

- ); + if (game.status === "seeding" && game.downloader === Downloader.Torrent) { + return ( + <> +

{t("seeding")}

+ {uploadSpeed &&

{uploadSpeed}/s

} + + ); + } else { + return

{t("completed")}

; + } } if (game.status === "paused") { 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 541bd01cd..acb4c169e 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 b06de28a8..10a2aeb22 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 @@ -169,8 +169,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/scss/globals.scss b/src/renderer/src/scss/globals.scss index cc01c197d..792abf866 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 b9fbaf550..7cd92ef3b 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 345893a5d..610292b2f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -267,6 +267,7 @@ export interface UserDetails { backgroundImageUrl: string | null; profileVisibility: ProfileVisibility; bio: string; + featurebaseJwt: string; subscription: Subscription | null; quirks?: { backupsPerGameLimit: number; @@ -299,6 +300,7 @@ export interface UpdateProfileRequest { profileImageUrl?: string | null; backgroundImageUrl?: string | null; bio?: string; + language?: string; } export interface DownloadSourceDownload {