feat: migrating user preferences

This commit is contained in:
Chubby Granny Chaser
2025-01-21 03:48:46 +00:00
parent d760d0139d
commit f1e0ba4dd6
53 changed files with 737 additions and 790 deletions

View File

@@ -84,7 +84,7 @@ export function App() {
useEffect(() => {
const unsubscribe = window.electron.onDownloadProgress(
(downloadProgress) => {
if (downloadProgress.game.progress === 1) {
if (downloadProgress.progress === 1) {
clearDownload();
updateLibrary();
return;

View File

@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useDownload, useUserDetails } from "@renderer/hooks";
import { useDownload, useLibrary, useUserDetails } from "@renderer/hooks";
import "./bottom-panel.scss";
@@ -15,9 +15,11 @@ export function BottomPanel() {
const { userDetails } = useUserDetails();
const { library } = useLibrary();
const { lastPacket, progress, downloadSpeed, eta } = useDownload();
const isGameDownloading = !!lastPacket?.game;
const isGameDownloading = !!lastPacket;
const [version, setVersion] = useState("");
const [sessionHash, setSessionHash] = useState<null | string>("");
@@ -32,27 +34,29 @@ export function BottomPanel() {
const status = useMemo(() => {
if (isGameDownloading) {
const game = library.find((game) => game.id === lastPacket?.gameId)!;
if (lastPacket?.isCheckingFiles)
return t("checking_files", {
title: lastPacket?.game.title,
title: game.title,
percentage: progress,
});
if (lastPacket?.isDownloadingMetadata)
return t("downloading_metadata", {
title: lastPacket?.game.title,
title: game.title,
percentage: progress,
});
if (!eta) {
return t("calculating_eta", {
title: lastPacket?.game.title,
title: game.title,
percentage: progress,
});
}
return t("downloading", {
title: lastPacket?.game.title,
title: game.title,
percentage: progress,
eta,
speed: downloadSpeed,
@@ -60,16 +64,7 @@ export function BottomPanel() {
}
return t("no_downloads_in_progress");
}, [
t,
isGameDownloading,
lastPacket?.game,
lastPacket?.isDownloadingMetadata,
lastPacket?.isCheckingFiles,
progress,
eta,
downloadSpeed,
]);
}, [t, isGameDownloading, library, lastPacket, progress, eta, downloadSpeed]);
return (
<footer className="bottom-panel">

View File

@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useNavigate } from "react-router-dom";
import type { Game } from "@types";
import type { LibraryGame } from "@types";
import { TextField } from "@renderer/components";
import {
@@ -35,7 +35,7 @@ export function Sidebar() {
const { library, updateLibrary } = useLibrary();
const navigate = useNavigate();
const [filteredLibrary, setFilteredLibrary] = useState<Game[]>([]);
const [filteredLibrary, setFilteredLibrary] = useState<LibraryGame[]>([]);
const [isResizing, setIsResizing] = useState(false);
const [sidebarWidth, setSidebarWidth] = useState(
@@ -56,7 +56,7 @@ export function Sidebar() {
useEffect(() => {
updateLibrary();
}, [lastPacket?.game.id, updateLibrary]);
}, [lastPacket?.gameId, updateLibrary]);
const sidebarRef = useRef<HTMLElement>(null);
@@ -117,20 +117,21 @@ export function Sidebar() {
};
}, [isResizing]);
const getGameTitle = (game: Game) => {
if (lastPacket?.game.id === game.id) {
const getGameTitle = (game: LibraryGame) => {
if (lastPacket?.gameId === game.id) {
return t("downloading", {
title: game.title,
percentage: progress,
});
}
if (game.downloadQueue !== null) {
if (game.download?.status === "paused")
return t("paused", { title: game.title });
if (game.download) {
return t("queued", { title: game.title });
}
if (game.status === "paused") return t("paused", { title: game.title });
return game.title;
};
@@ -140,10 +141,13 @@ export function Sidebar() {
}
};
const handleSidebarGameClick = (event: React.MouseEvent, game: Game) => {
const handleSidebarGameClick = (
event: React.MouseEvent,
game: LibraryGame
) => {
const path = buildGameDetailsPath({
...game,
objectId: game.objectID,
objectId: game.objectId,
});
if (path !== location.pathname) {
navigate(path);
@@ -152,7 +156,8 @@ export function Sidebar() {
if (event.detail === 2) {
if (game.executablePath) {
window.electron.openGame(
game.id,
game.shop,
game.objectId,
game.executablePath,
game.launchOptions
);
@@ -216,12 +221,12 @@ export function Sidebar() {
<ul className={styles.menu}>
{filteredLibrary.map((game) => (
<li
key={game.id}
key={`${game.shop}-${game.objectId}`}
className={styles.menuItem({
active:
location.pathname ===
`/game/${game.shop}/${game.objectID}`,
muted: game.status === "removed",
`/game/${game.shop}/${game.objectId}`,
muted: game.download?.status === "removed",
})}
>
<button

View File

@@ -18,9 +18,9 @@ import {
} from "@renderer/hooks";
import type {
Game,
GameShop,
GameStats,
LibraryGame,
ShopDetails,
UserAchievement,
} from "@types";
@@ -73,7 +73,7 @@ export function GameDetailsContextProvider({
const [achievements, setAchievements] = useState<UserAchievement[] | null>(
null
);
const [game, setGame] = useState<Game | null>(null);
const [game, setGame] = useState<LibraryGame | null>(null);
const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false);
const abortControllerRef = useRef<AbortController | null>(null);
@@ -105,11 +105,12 @@ export function GameDetailsContextProvider({
.then((result) => setGame(result));
}, [setGame, shop, objectId]);
const isGameDownloading = lastPacket?.game.id === game?.id;
const isGameDownloading =
lastPacket?.gameId === game?.id && game?.download?.status === "active";
useEffect(() => {
updateGame();
}, [updateGame, isGameDownloading, lastPacket?.game.status]);
}, [updateGame, isGameDownloading, lastPacket?.gameId]);
useEffect(() => {
if (abortControllerRef.current) abortControllerRef.current.abort();
@@ -190,9 +191,9 @@ export function GameDetailsContextProvider({
}, [game?.id, isGameRunning, updateGame]);
const lastDownloadedOption = useMemo(() => {
if (game?.uri) {
if (game?.download) {
const repack = repacks.find((repack) =>
repack.uris.some((uri) => uri.includes(game.uri!))
repack.uris.some((uri) => uri.includes(game.download!.uri!))
);
if (!repack) return null;
@@ -200,7 +201,7 @@ export function GameDetailsContextProvider({
}
return null;
}, [game?.uri, repacks]);
}, [game?.download, repacks]);
useEffect(() => {
const unsubscribe = window.electron.onUpdateAchievements(

View File

@@ -1,14 +1,14 @@
import type {
Game,
GameRepack,
GameShop,
GameStats,
LibraryGame,
ShopDetails,
UserAchievement,
} from "@types";
export interface GameDetailsContext {
game: Game | null;
game: LibraryGame | null;
shopDetails: ShopDetails | null;
repacks: GameRepack[];
shop: GameShop;

View File

@@ -1,7 +1,6 @@
import type { CatalogueCategory } from "@shared";
import type {
AppUpdaterEvent,
Game,
GameShop,
HowLongToBeatCategory,
ShopDetails,
@@ -27,6 +26,7 @@ import type {
UserAchievement,
ComparedAchievements,
CatalogueSearchPayload,
LibraryGame,
} from "@types";
import type { AxiosProgressEvent } from "axios";
import type disk from "diskusage";
@@ -103,7 +103,7 @@ declare global {
winePrefixPath: string | null
) => Promise<void>;
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
getLibrary: () => Promise<Game[]>;
getLibrary: () => Promise<LibraryGame[]>;
openGameInstaller: (shop: GameShop, objectId: string) => Promise<boolean>;
openGameInstallerPath: (
shop: GameShop,
@@ -114,7 +114,7 @@ declare global {
shop: GameShop,
objectId: string,
executablePath: string,
launchOptions: string | null
launchOptions?: string | null
) => Promise<void>;
closeGame: (shop: GameShop, objectId: string) => Promise<boolean>;
removeGameFromLibrary: (shop: GameShop, objectId: string) => Promise<void>;
@@ -123,7 +123,7 @@ declare global {
getGameByObjectId: (
shop: GameShop,
objectId: string
) => Promise<Game | null>;
) => Promise<LibraryGame | null>;
onGamesRunning: (
cb: (
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]

View File

@@ -4,8 +4,8 @@ import type { DownloadProgress } from "@types";
export interface DownloadState {
lastPacket: DownloadProgress | null;
gameId: number | null;
gamesWithDeletionInProgress: number[];
gameId: string | null;
gamesWithDeletionInProgress: string[];
}
const initialState: DownloadState = {
@@ -20,13 +20,13 @@ export const downloadSlice = createSlice({
reducers: {
setLastPacket: (state, action: PayloadAction<DownloadProgress>) => {
state.lastPacket = action.payload;
if (!state.gameId) state.gameId = action.payload.game.id;
if (!state.gameId) state.gameId = action.payload.gameId;
},
clearDownload: (state) => {
state.lastPacket = null;
state.gameId = null;
},
setGameDeleting: (state, action: PayloadAction<number>) => {
setGameDeleting: (state, action: PayloadAction<string>) => {
if (
!state.gamesWithDeletionInProgress.includes(action.payload) &&
action.payload
@@ -34,7 +34,7 @@ export const downloadSlice = createSlice({
state.gamesWithDeletionInProgress.push(action.payload);
}
},
removeGameFromDeleting: (state, action: PayloadAction<number>) => {
removeGameFromDeleting: (state, action: PayloadAction<string>) => {
const index = state.gamesWithDeletionInProgress.indexOf(action.payload);
if (index >= 0) state.gamesWithDeletionInProgress.splice(index, 1);
},

View File

@@ -1,10 +1,10 @@
import { createSlice } from "@reduxjs/toolkit";
import type { PayloadAction } from "@reduxjs/toolkit";
import type { Game } from "@types";
import type { LibraryGame } from "@types";
export interface LibraryState {
value: Game[];
value: LibraryGame[];
}
const initialState: LibraryState = {

View File

@@ -1,6 +1,6 @@
import { useNavigate } from "react-router-dom";
import type { Game, SeedingStatus } from "@types";
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
import { Badge, Button } from "@renderer/components";
import {
@@ -32,10 +32,10 @@ import {
} from "@primer/octicons-react";
export interface DownloadGroupProps {
library: Game[];
library: LibraryGame[];
title: string;
openDeleteGameModal: (gameId: number) => void;
openGameInstaller: (gameId: number) => void;
openDeleteGameModal: (shop: GameShop, objectId: string) => void;
openGameInstaller: (shop: GameShop, objectId: string) => void;
seedingStatus: SeedingStatus[];
}
@@ -65,19 +65,20 @@ export function DownloadGroup({
resumeSeeding,
} = useDownload();
const getFinalDownloadSize = (game: Game) => {
const isGameDownloading = lastPacket?.game.id === game.id;
const getFinalDownloadSize = (game: LibraryGame) => {
const download = game.download!;
const isGameDownloading = lastPacket?.gameId === game.id;
if (game.fileSize) return formatBytes(game.fileSize);
if (download.fileSize) return formatBytes(download.fileSize);
if (lastPacket?.game.fileSize && isGameDownloading)
return formatBytes(lastPacket?.game.fileSize);
if (download.fileSize && isGameDownloading)
return formatBytes(download?.fileSize);
return "N/A";
};
const seedingMap = useMemo(() => {
const map = new Map<number, SeedingStatus>();
const map = new Map<string, SeedingStatus>();
seedingStatus.forEach((seed) => {
map.set(seed.gameId, seed);
@@ -86,8 +87,12 @@ export function DownloadGroup({
return map;
}, [seedingStatus]);
const getGameInfo = (game: Game) => {
const isGameDownloading = lastPacket?.game.id === game.id;
const getGameInfo = (game: LibraryGame) => {
const download = game.download!;
console.log(game);
const isGameDownloading = lastPacket?.gameId === game.id;
const finalDownloadSize = getFinalDownloadSize(game);
const seedingStatus = seedingMap.get(game.id);
@@ -114,11 +119,11 @@ export function DownloadGroup({
<p>{progress}</p>
<p>
{formatBytes(lastPacket?.game.bytesDownloaded)} /{" "}
{formatBytes(lastPacket.download.bytesDownloaded)} /{" "}
{finalDownloadSize}
</p>
{game.downloader === Downloader.Torrent && (
{download.downloader === Downloader.Torrent && (
<small>
{lastPacket?.numPeers} peers / {lastPacket?.numSeeds} seeds
</small>
@@ -127,11 +132,11 @@ export function DownloadGroup({
);
}
if (game.progress === 1) {
if (download.progress === 1) {
const uploadSpeed = formatBytes(seedingStatus?.uploadSpeed ?? 0);
return game.status === "seeding" &&
game.downloader === Downloader.Torrent ? (
return download.status === "seeding" &&
download.downloader === Downloader.Torrent ? (
<>
<p>{t("seeding")}</p>
{uploadSpeed && <p>{uploadSpeed}/s</p>}
@@ -141,41 +146,42 @@ export function DownloadGroup({
);
}
if (game.status === "paused") {
if (download.status === "paused") {
return (
<>
<p>{formatDownloadProgress(game.progress)}</p>
<p>{t(game.downloadQueue && lastPacket ? "queued" : "paused")}</p>
<p>{formatDownloadProgress(download.progress)}</p>
{/* <p>{t(game.downloadQueue && lastPacket ? "queued" : "paused")}</p> */}
</>
);
}
if (game.status === "active") {
if (download.status === "active") {
return (
<>
<p>{formatDownloadProgress(game.progress)}</p>
<p>{formatDownloadProgress(download.progress)}</p>
<p>
{formatBytes(game.bytesDownloaded)} / {finalDownloadSize}
{formatBytes(download.bytesDownloaded)} / {finalDownloadSize}
</p>
</>
);
}
return <p>{t(game.status as string)}</p>;
return <p>{t(download.status as string)}</p>;
};
const getGameActions = (game: Game): DropdownMenuItem[] => {
const isGameDownloading = lastPacket?.game.id === game.id;
const getGameActions = (game: LibraryGame): DropdownMenuItem[] => {
const download = lastPacket?.download;
const isGameDownloading = lastPacket?.gameId === game.id;
const deleting = isGameDeleting(game.id);
if (game.progress === 1) {
if (download?.progress === 1) {
return [
{
label: t("install"),
disabled: deleting,
onClick: () => openGameInstaller(game.id),
onClick: () => openGameInstaller(game.shop, game.objectId),
icon: <DownloadIcon />,
},
{
@@ -183,36 +189,38 @@ export function DownloadGroup({
disabled: deleting,
icon: <UnlinkIcon />,
show:
game.status === "seeding" && game.downloader === Downloader.Torrent,
onClick: () => pauseSeeding(game.id),
download.status === "seeding" &&
download.downloader === Downloader.Torrent,
onClick: () => pauseSeeding(game.shop, game.objectId),
},
{
label: t("resume_seeding"),
disabled: deleting,
icon: <LinkIcon />,
show:
game.status !== "seeding" && game.downloader === Downloader.Torrent,
onClick: () => resumeSeeding(game.id),
download.status !== "seeding" &&
download.downloader === Downloader.Torrent,
onClick: () => resumeSeeding(game.shop, game.objectId),
},
{
label: t("delete"),
disabled: deleting,
icon: <TrashIcon />,
onClick: () => openDeleteGameModal(game.id),
onClick: () => openDeleteGameModal(game.shop, game.objectId),
},
];
}
if (isGameDownloading || game.status === "active") {
if (isGameDownloading || download?.status === "active") {
return [
{
label: t("pause"),
onClick: () => pauseDownload(game.id),
onClick: () => pauseDownload(game.shop, game.objectId),
icon: <ColumnsIcon />,
},
{
label: t("cancel"),
onClick: () => cancelDownload(game.id),
onClick: () => cancelDownload(game.shop, game.objectId),
icon: <XCircleIcon />,
},
];
@@ -222,14 +230,14 @@ export function DownloadGroup({
{
label: t("resume"),
disabled:
game.downloader === Downloader.RealDebrid &&
download?.downloader === Downloader.RealDebrid &&
!userPreferences?.realDebridApiToken,
onClick: () => resumeDownload(game.id),
onClick: () => resumeDownload(game.shop, game.objectId),
icon: <PlayIcon />,
},
{
label: t("cancel"),
onClick: () => cancelDownload(game.id),
onClick: () => cancelDownload(game.shop, game.objectId),
icon: <XCircleIcon />,
},
];
@@ -270,13 +278,19 @@ export function DownloadGroup({
<div className={styles.downloadCover}>
<div className={styles.downloadCoverBackdrop}>
<img
src={steamUrlBuilder.library(game.objectID)}
src={steamUrlBuilder.library(game.objectId)}
className={styles.downloadCoverImage}
alt={game.title}
/>
<div className={styles.downloadCoverContent}>
<Badge>{DOWNLOADER_NAME[game.downloader]}</Badge>
<Badge>
{
DOWNLOADER_NAME[
game?.download?.downloader as Downloader
]
}
</Badge>
</div>
</div>
</div>
@@ -290,7 +304,7 @@ export function DownloadGroup({
navigate(
buildGameDetailsPath({
...game,
objectId: game.objectID,
objectId: game.objectId,
})
)
}

View File

@@ -7,7 +7,7 @@ import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
import * as styles from "./downloads.css";
import { DeleteGameModal } from "./delete-game-modal";
import { DownloadGroup } from "./download-group";
import type { Game, SeedingStatus } from "@types";
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
import { orderBy } from "lodash-es";
import { ArrowDownIcon } from "@primer/octicons-react";
@@ -16,7 +16,7 @@ export default function Downloads() {
const { t } = useTranslation("downloads");
const gameToBeDeleted = useRef<number | null>(null);
const gameToBeDeleted = useRef<[GameShop, string] | null>(null);
const [showBinaryNotFoundModal, setShowBinaryNotFoundModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
@@ -25,8 +25,10 @@ export default function Downloads() {
const handleDeleteGame = async () => {
if (gameToBeDeleted.current) {
await pauseSeeding(gameToBeDeleted.current);
await removeGameInstaller(gameToBeDeleted.current);
const [shop, objectId] = gameToBeDeleted.current;
await pauseSeeding(shop, objectId);
await removeGameInstaller(shop, objectId);
}
};
@@ -38,19 +40,19 @@ export default function Downloads() {
window.electron.onSeedingStatus((value) => setSeedingStatus(value));
}, []);
const handleOpenGameInstaller = (gameId: number) =>
window.electron.openGameInstaller(gameId).then((isBinaryInPath) => {
const handleOpenGameInstaller = (shop: GameShop, objectId: string) =>
window.electron.openGameInstaller(shop, objectId).then((isBinaryInPath) => {
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
updateLibrary();
});
const handleOpenDeleteGameModal = (gameId: number) => {
gameToBeDeleted.current = gameId;
const handleOpenDeleteGameModal = (shop: GameShop, objectId: string) => {
gameToBeDeleted.current = [shop, objectId];
setShowDeleteModal(true);
};
const libraryGroup: Record<string, Game[]> = useMemo(() => {
const initialValue: Record<string, Game[]> = {
const libraryGroup: Record<string, LibraryGame[]> = useMemo(() => {
const initialValue: Record<string, LibraryGame[]> = {
downloading: [],
queued: [],
complete: [],
@@ -58,27 +60,26 @@ export default function Downloads() {
const result = library.reduce((prev, next) => {
/* Game has been manually added to the library or has been canceled */
if (!next.status || next.status === "removed") return prev;
if (!next.download?.status || next.download?.status === "removed")
return prev;
/* Is downloading */
if (lastPacket?.game.id === next.id)
if (lastPacket?.gameId === next.id)
return { ...prev, downloading: [...prev.downloading, next] };
/* Is either queued or paused */
if (next.downloadQueue || next.status === "paused")
if (next.download?.status === "paused")
return { ...prev, queued: [...prev.queued, next] };
return { ...prev, complete: [...prev.complete, next] };
}, initialValue);
const queued = orderBy(
result.queued,
(game) => game.downloadQueue?.id ?? -1,
["desc"]
);
const queued = orderBy(result.queued, (game) => game.download?.timestamp, [
"desc",
]);
const complete = orderBy(result.complete, (game) =>
game.progress === 1 ? 0 : 1
game.download?.progress === 1 ? 0 : 1
);
return {
@@ -86,7 +87,7 @@ export default function Downloads() {
queued,
complete,
};
}, [library, lastPacket?.game.id]);
}, [library, lastPacket?.gameId]);
const downloadGroups = [
{

View File

@@ -22,6 +22,7 @@ export function HeroPanelActions() {
game,
repacks,
isGameRunning,
shop,
objectId,
gameTitle,
setShowGameOptionsModal,
@@ -33,7 +34,7 @@ export function HeroPanelActions() {
const { lastPacket } = useDownload();
const isGameDownloading =
game?.status === "active" && lastPacket?.game.id === game?.id;
game?.download?.status === "active" && lastPacket?.gameId === game?.id;
const { updateLibrary } = useLibrary();
@@ -43,7 +44,7 @@ export function HeroPanelActions() {
setToggleLibraryGameDisabled(true);
try {
await window.electron.addGameToLibrary(objectId!, gameTitle, "steam");
await window.electron.addGameToLibrary(shop, objectId!, gameTitle);
updateLibrary();
updateGame();
@@ -56,7 +57,8 @@ export function HeroPanelActions() {
if (game) {
if (game.executablePath) {
window.electron.openGame(
game.id,
game.shop,
game.objectId,
game.executablePath,
game.launchOptions
);
@@ -66,7 +68,8 @@ export function HeroPanelActions() {
const gameExecutablePath = await selectGameExecutable();
if (gameExecutablePath)
window.electron.openGame(
game.id,
game.shop,
game.objectId,
gameExecutablePath,
game.launchOptions
);
@@ -74,7 +77,7 @@ export function HeroPanelActions() {
};
const closeGame = () => {
if (game) window.electron.closeGame(game.id);
if (game) window.electron.closeGame(game.shop, game.objectId);
};
const deleting = game ? isGameDeleting(game?.id) : false;

View File

@@ -49,21 +49,24 @@ export function HeroPanelPlaytime() {
if (!game) return null;
const hasDownload =
["active", "paused"].includes(game.status as string) && game.progress !== 1;
["active", "paused"].includes(game.download?.status as string) &&
game.download?.progress !== 1;
const isGameDownloading =
game.status === "active" && lastPacket?.game.id === game.id;
game.download?.status === "active" && lastPacket?.gameId === game.id;
const downloadInProgressInfo = (
<div className={styles.downloadDetailsRow}>
<Link to="/downloads" className={styles.downloadsLink}>
{game.status === "active"
{game.download?.status === "active"
? t("download_in_progress")
: t("download_paused")}
</Link>
<small>
{isGameDownloading ? progress : formatDownloadProgress(game.progress)}
{isGameDownloading
? progress
: formatDownloadProgress(game.download?.progress)}
</small>
</div>
);

View File

@@ -23,7 +23,7 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
const { lastPacket } = useDownload();
const isGameDownloading =
game?.status === "active" && lastPacket?.game.id === game?.id;
game?.download?.status === "active" && lastPacket?.gameId === game?.id;
const getInfo = () => {
if (!game) {
@@ -50,8 +50,8 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
};
const showProgressBar =
(game?.status === "active" && game?.progress < 1) ||
game?.status === "paused";
(game?.download?.status === "active" && game?.download?.progress < 1) ||
game?.download?.status === "paused";
return (
<>
@@ -68,10 +68,12 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
<progress
max={1}
value={
isGameDownloading ? lastPacket?.game.progress : game?.progress
isGameDownloading
? lastPacket?.progress
: game?.download?.progress
}
className={styles.progressBar({
disabled: game?.status === "paused",
disabled: game?.download?.status === "paused",
})}
/>
)}

View File

@@ -1,7 +1,7 @@
import { useContext, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button, Modal, TextField } from "@renderer/components";
import type { Game } from "@types";
import type { LibraryGame } from "@types";
import * as styles from "./game-options-modal.css";
import { gameDetailsContext } from "@renderer/context";
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
@@ -13,7 +13,7 @@ import { debounce } from "lodash-es";
export interface GameOptionsModalProps {
visible: boolean;
game: Game;
game: LibraryGame;
onClose: () => void;
}
@@ -59,21 +59,25 @@ export function GameOptionsModal({
const { lastPacket } = useDownload();
const isGameDownloading =
game.status === "active" && lastPacket?.game.id === game.id;
game.download?.status === "active" && lastPacket?.gameId === game.id;
const debounceUpdateLaunchOptions = useRef(
debounce(async (value: string) => {
await window.electron.updateLaunchOptions(game.id, value);
await window.electron.updateLaunchOptions(
game.shop,
game.objectId,
value
);
updateGame();
}, 1000)
).current;
const handleRemoveGameFromLibrary = async () => {
if (isGameDownloading) {
await cancelDownload(game.id);
await cancelDownload(game.shop, game.objectId);
}
await removeGameFromLibrary(game.id);
await removeGameFromLibrary(game.shop, game.objectId);
updateGame();
onClose();
};
@@ -92,35 +96,39 @@ export function GameOptionsModal({
return;
}
window.electron.updateExecutablePath(game.id, path).then(updateGame);
window.electron
.updateExecutablePath(game.shop, game.objectId, path)
.then(updateGame);
}
};
const handleCreateShortcut = async () => {
window.electron.createGameShortcut(game.id).then((success) => {
if (success) {
showSuccessToast(t("create_shortcut_success"));
} else {
showErrorToast(t("create_shortcut_error"));
}
});
window.electron
.createGameShortcut(game.shop, game.objectId)
.then((success) => {
if (success) {
showSuccessToast(t("create_shortcut_success"));
} else {
showErrorToast(t("create_shortcut_error"));
}
});
};
const handleOpenDownloadFolder = async () => {
await window.electron.openGameInstallerPath(game.id);
await window.electron.openGameInstallerPath(game.shop, game.objectId);
};
const handleDeleteGame = async () => {
await removeGameInstaller(game.id);
await removeGameInstaller(game.shop, game.objectId);
updateGame();
};
const handleOpenGameExecutablePath = async () => {
await window.electron.openGameExecutablePath(game.id);
await window.electron.openGameExecutablePath(game.shop, game.objectId);
};
const handleClearExecutablePath = async () => {
await window.electron.updateExecutablePath(game.id, null);
await window.electron.updateExecutablePath(game.shop, game.objectId, null);
updateGame();
};
@@ -130,13 +138,17 @@ export function GameOptionsModal({
});
if (filePaths && filePaths.length > 0) {
await window.electron.selectGameWinePrefix(game.id, filePaths[0]);
await window.electron.selectGameWinePrefix(
game.shop,
game.objectId,
filePaths[0]
);
await updateGame();
}
};
const handleClearWinePrefixPath = async () => {
await window.electron.selectGameWinePrefix(game.id, null);
await window.electron.selectGameWinePrefix(game.shop, game.objectId, null);
updateGame();
};
@@ -150,7 +162,9 @@ export function GameOptionsModal({
const handleClearLaunchOptions = async () => {
setLaunchOptions("");
window.electron.updateLaunchOptions(game.id, null).then(updateGame);
window.electron
.updateLaunchOptions(game.shop, game.objectId, null)
.then(updateGame);
};
const shouldShowWinePrefixConfiguration =
@@ -159,7 +173,7 @@ export function GameOptionsModal({
const handleResetAchievements = async () => {
setIsDeletingAchievements(true);
try {
await window.electron.resetGameAchievements(game.id);
await window.electron.resetGameAchievements(game.shop, game.objectId);
await updateGame();
showSuccessToast(t("reset_achievements_success"));
} catch (error) {
@@ -322,7 +336,7 @@ export function GameOptionsModal({
>
{t("open_download_options")}
</Button>
{game.downloadPath && (
{game.download?.downloadPath && (
<Button
onClick={handleOpenDownloadFolder}
theme="outline"
@@ -367,7 +381,9 @@ export function GameOptionsModal({
setShowDeleteModal(true);
}}
theme="danger"
disabled={isGameDownloading || deleting || !game.downloadPath}
disabled={
isGameDownloading || deleting || !game.download?.downloadPath
}
>
{t("remove_files")}
</Button>

View File

@@ -67,8 +67,8 @@ export function RepacksModal({
};
const checkIfLastDownloadedOption = (repack: GameRepack) => {
if (!game) return false;
return repack.uris.some((uri) => uri.includes(game.uri!));
if (!game || !game.download) return false;
return repack.uris.some((uri) => uri.includes(game.download!.uri));
};
return (

View File

@@ -254,7 +254,7 @@ export function ProfileHero() {
if (gameRunning)
return {
...gameRunning,
objectId: gameRunning.objectID,
objectId: gameRunning.objectId,
sessionDurationInSeconds: gameRunning.sessionDurationInMillis / 1000,
};

View File

@@ -57,10 +57,30 @@ export function SettingsGeneral() {
);
}, []);
useEffect(updateFormWithUserPreferences, [
userPreferences,
defaultDownloadsPath,
]);
useEffect(() => {
if (userPreferences) {
const languageKeys = Object.keys(languageResources);
const language =
languageKeys.find(
(language) => language === userPreferences.language
) ??
languageKeys.find((language) => {
return language.startsWith(userPreferences.language.split("-")[0]);
});
setForm((prev) => ({
...prev,
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
downloadNotificationsEnabled:
userPreferences.downloadNotificationsEnabled,
repackUpdatesNotificationsEnabled:
userPreferences.repackUpdatesNotificationsEnabled,
achievementNotificationsEnabled:
userPreferences.achievementNotificationsEnabled,
language: language ?? "en",
}));
}
}, [userPreferences, defaultDownloadsPath]);
const handleLanguageChange = (event) => {
const value = event.target.value;
@@ -86,31 +106,6 @@ export function SettingsGeneral() {
}
};
function updateFormWithUserPreferences() {
if (userPreferences) {
const languageKeys = Object.keys(languageResources);
const language =
languageKeys.find((language) => {
return language === userPreferences.language;
}) ??
languageKeys.find((language) => {
return language.startsWith(userPreferences.language.split("-")[0]);
});
setForm((prev) => ({
...prev,
downloadsPath: userPreferences.downloadsPath ?? defaultDownloadsPath,
downloadNotificationsEnabled:
userPreferences.downloadNotificationsEnabled,
repackUpdatesNotificationsEnabled:
userPreferences.repackUpdatesNotificationsEnabled,
achievementNotificationsEnabled:
userPreferences.achievementNotificationsEnabled,
language: language ?? "en",
}));
}
}
return (
<>
<TextField