mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-19 01:03:57 +00:00
Merge branch 'feat/migration-to-leveldb' into feature/custom-themes
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -29,7 +29,7 @@ export function DropdownMenu({
|
||||
loop = true,
|
||||
align = "center",
|
||||
alignOffset = 0,
|
||||
}: DropdownMenuProps) {
|
||||
}: Readonly<DropdownMenuProps>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Root>
|
||||
<DropdownMenuPrimitive.Trigger asChild>
|
||||
|
||||
@@ -53,6 +53,7 @@ export function Modal({
|
||||
)
|
||||
)
|
||||
return false;
|
||||
|
||||
const openModals = document.querySelectorAll("[role=dialog]");
|
||||
|
||||
return (
|
||||
|
||||
@@ -58,7 +58,7 @@ export function Sidebar() {
|
||||
|
||||
useEffect(() => {
|
||||
updateLibrary();
|
||||
}, [lastPacket?.game.id, updateLibrary]);
|
||||
}, [lastPacket?.gameId, updateLibrary]);
|
||||
|
||||
const sidebarRef = useRef<HTMLElement>(null);
|
||||
|
||||
@@ -120,18 +120,17 @@ export function Sidebar() {
|
||||
}, [isResizing]);
|
||||
|
||||
const getGameTitle = (game: LibraryGame) => {
|
||||
if (lastPacket?.game.id === game.id) {
|
||||
if (lastPacket?.gameId === game.id) {
|
||||
return t("downloading", {
|
||||
title: game.title,
|
||||
percentage: progress,
|
||||
});
|
||||
}
|
||||
|
||||
if (game.downloadQueue !== null) {
|
||||
return t("queued", { title: game.title });
|
||||
}
|
||||
if (game.download?.queued) return t("queued", { title: game.title });
|
||||
|
||||
if (game.status === "paused") return t("paused", { title: game.title });
|
||||
if (game.download?.status === "paused")
|
||||
return t("paused", { title: game.title });
|
||||
|
||||
return game.title;
|
||||
};
|
||||
@@ -148,7 +147,7 @@ export function Sidebar() {
|
||||
) => {
|
||||
const path = buildGameDetailsPath({
|
||||
...game,
|
||||
objectId: game.objectID,
|
||||
objectId: game.objectId,
|
||||
});
|
||||
if (path !== location.pathname) {
|
||||
navigate(path);
|
||||
@@ -157,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
|
||||
);
|
||||
@@ -221,12 +221,13 @@ export function Sidebar() {
|
||||
<ul className="sidebar__menu">
|
||||
{filteredLibrary.map((game) => (
|
||||
<li
|
||||
key={game.id}
|
||||
key={`${game.shop}-${game.objectId}`}
|
||||
className={cn("sidebar__menu-item", {
|
||||
"sidebar__menu-item--active":
|
||||
location.pathname ===
|
||||
`/game/${game.shop}/${game.objectID}`,
|
||||
"sidebar__menu-item--muted": game.status === "removed",
|
||||
`/game/${game.shop}/${game.objectId}`,
|
||||
"sidebar__menu-item--muted":
|
||||
game.download?.status === "removed",
|
||||
})}
|
||||
>
|
||||
<button
|
||||
|
||||
@@ -18,9 +18,9 @@ import {
|
||||
} from "@renderer/hooks";
|
||||
|
||||
import type {
|
||||
Game,
|
||||
GameShop,
|
||||
GameStats,
|
||||
LibraryGame,
|
||||
ShopDetails,
|
||||
UserAchievement,
|
||||
} from "@types";
|
||||
@@ -68,12 +68,12 @@ export function GameDetailsContextProvider({
|
||||
objectId,
|
||||
gameTitle,
|
||||
shop,
|
||||
}: GameDetailsContextProps) {
|
||||
}: Readonly<GameDetailsContextProps>) {
|
||||
const [shopDetails, setShopDetails] = useState<ShopDetails | null>(null);
|
||||
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);
|
||||
|
||||
@@ -81,7 +81,7 @@ export function GameDetailsContextProvider({
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [gameColor, setGameColor] = useState("");
|
||||
const [isGameRunning, setisGameRunning] = useState(false);
|
||||
const [isGameRunning, setIsGameRunning] = useState(false);
|
||||
const [showRepacksModal, setShowRepacksModal] = useState(false);
|
||||
const [showGameOptionsModal, setShowGameOptionsModal] = useState(false);
|
||||
|
||||
@@ -101,15 +101,16 @@ export function GameDetailsContextProvider({
|
||||
|
||||
const updateGame = useCallback(async () => {
|
||||
return window.electron
|
||||
.getGameByObjectId(objectId!)
|
||||
.getGameByObjectId(shop, objectId)
|
||||
.then((result) => setGame(result));
|
||||
}, [setGame, objectId]);
|
||||
}, [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();
|
||||
@@ -167,7 +168,7 @@ export function GameDetailsContextProvider({
|
||||
setShopDetails(null);
|
||||
setGame(null);
|
||||
setIsLoading(true);
|
||||
setisGameRunning(false);
|
||||
setIsGameRunning(false);
|
||||
setAchievements(null);
|
||||
dispatch(setHeaderTitle(gameTitle));
|
||||
}, [objectId, gameTitle, dispatch]);
|
||||
@@ -182,17 +183,18 @@ export function GameDetailsContextProvider({
|
||||
updateGame();
|
||||
}
|
||||
|
||||
setisGameRunning(updatedIsGameRunning);
|
||||
setIsGameRunning(updatedIsGameRunning);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [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 +202,7 @@ export function GameDetailsContextProvider({
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [game?.uri, repacks]);
|
||||
}, [game?.download, repacks]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onUpdateAchievements(
|
||||
@@ -250,7 +252,7 @@ export function GameDetailsContextProvider({
|
||||
value={{
|
||||
game,
|
||||
shopDetails,
|
||||
shop: shop as GameShop,
|
||||
shop,
|
||||
repacks,
|
||||
gameTitle,
|
||||
isGameRunning,
|
||||
|
||||
@@ -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;
|
||||
|
||||
61
src/renderer/src/declaration.d.ts
vendored
61
src/renderer/src/declaration.d.ts
vendored
@@ -1,8 +1,6 @@
|
||||
import type { AuthPage, CatalogueCategory } from "@shared";
|
||||
import type {
|
||||
AppUpdaterEvent,
|
||||
Game,
|
||||
LibraryGame,
|
||||
GameShop,
|
||||
HowLongToBeatCategory,
|
||||
ShopDetails,
|
||||
@@ -23,12 +21,13 @@ import type {
|
||||
UserStats,
|
||||
UserDetails,
|
||||
FriendRequestSync,
|
||||
GameAchievement,
|
||||
GameArtifact,
|
||||
LudusaviBackup,
|
||||
UserAchievement,
|
||||
ComparedAchievements,
|
||||
CatalogueSearchPayload,
|
||||
LibraryGame,
|
||||
GameRunning,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type disk from "diskusage";
|
||||
@@ -42,11 +41,11 @@ declare global {
|
||||
interface Electron {
|
||||
/* Torrenting */
|
||||
startGameDownload: (payload: StartGameDownloadPayload) => Promise<void>;
|
||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||
resumeGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameSeed: (gameId: number) => Promise<void>;
|
||||
resumeGameSeed: (gameId: number) => Promise<void>;
|
||||
cancelGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
pauseGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
resumeGameDownload: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
pauseGameSeed: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
resumeGameSeed: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
onDownloadProgress: (
|
||||
cb: (value: DownloadProgress) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
@@ -77,52 +76,62 @@ declare global {
|
||||
onUpdateAchievements: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
cb: (achievements: GameAchievement[]) => void
|
||||
cb: (achievements: UserAchievement[]) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
getPublishers: () => Promise<string[]>;
|
||||
getDevelopers: () => Promise<string[]>;
|
||||
|
||||
/* Library */
|
||||
addGameToLibrary: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
title: string
|
||||
) => Promise<void>;
|
||||
createGameShortcut: (id: number) => Promise<boolean>;
|
||||
createGameShortcut: (shop: GameShop, objectId: string) => Promise<boolean>;
|
||||
updateExecutablePath: (
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
executablePath: string | null
|
||||
) => Promise<void>;
|
||||
updateLaunchOptions: (
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
launchOptions: string | null
|
||||
) => Promise<void>;
|
||||
selectGameWinePrefix: (
|
||||
id: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
winePrefixPath: string | null
|
||||
) => Promise<void>;
|
||||
verifyExecutablePathInUse: (executablePath: string) => Promise<Game>;
|
||||
getLibrary: () => Promise<LibraryGame[]>;
|
||||
openGameInstaller: (gameId: number) => Promise<boolean>;
|
||||
openGameInstallerPath: (gameId: number) => Promise<boolean>;
|
||||
openGameExecutablePath: (gameId: number) => Promise<void>;
|
||||
openGameInstaller: (shop: GameShop, objectId: string) => Promise<boolean>;
|
||||
openGameInstallerPath: (
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => Promise<boolean>;
|
||||
openGameExecutablePath: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
openGame: (
|
||||
gameId: number,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
executablePath: string,
|
||||
launchOptions: string | null
|
||||
launchOptions?: string | null
|
||||
) => Promise<void>;
|
||||
closeGame: (gameId: number) => Promise<boolean>;
|
||||
removeGameFromLibrary: (gameId: number) => Promise<void>;
|
||||
removeGame: (gameId: number) => Promise<void>;
|
||||
deleteGameFolder: (gameId: number) => Promise<unknown>;
|
||||
getGameByObjectId: (objectId: string) => Promise<Game | null>;
|
||||
closeGame: (shop: GameShop, objectId: string) => Promise<boolean>;
|
||||
removeGameFromLibrary: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
removeGame: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
deleteGameFolder: (shop: GameShop, objectId: string) => Promise<unknown>;
|
||||
getGameByObjectId: (
|
||||
shop: GameShop,
|
||||
objectId: string
|
||||
) => Promise<LibraryGame | null>;
|
||||
onGamesRunning: (
|
||||
cb: (
|
||||
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||
) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
|
||||
resetGameAchievements: (gameId: number) => Promise<void>;
|
||||
resetGameAchievements: (shop: GameShop, objectId: string) => Promise<void>;
|
||||
/* User preferences */
|
||||
getUserPreferences: () => Promise<UserPreferences | null>;
|
||||
updateUserPreferences: (
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ const initialState: GameRunningState = {
|
||||
};
|
||||
|
||||
export const gameRunningSlice = createSlice({
|
||||
name: "running-game",
|
||||
name: "game-running",
|
||||
initialState,
|
||||
reducers: {
|
||||
setGameRunning: (state, action: PayloadAction<GameRunning | null>) => {
|
||||
@@ -4,7 +4,7 @@ export * from "./download-slice";
|
||||
export * from "./window-slice";
|
||||
export * from "./toast-slice";
|
||||
export * from "./user-details-slice";
|
||||
export * from "./running-game-slice";
|
||||
export * from "./game-running.slice";
|
||||
export * from "./subscription-slice";
|
||||
export * from "./repacks-slice";
|
||||
export * from "./catalogue-search";
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
setGameDeleting,
|
||||
removeGameFromDeleting,
|
||||
} from "@renderer/features";
|
||||
import type { DownloadProgress, StartGameDownloadPayload } from "@types";
|
||||
import type {
|
||||
DownloadProgress,
|
||||
GameShop,
|
||||
StartGameDownloadPayload,
|
||||
} from "@types";
|
||||
import { useDate } from "./use-date";
|
||||
import { formatBytes } from "@shared";
|
||||
|
||||
@@ -31,48 +35,48 @@ export function useDownload() {
|
||||
return game;
|
||||
};
|
||||
|
||||
const pauseDownload = async (gameId: number) => {
|
||||
await window.electron.pauseGameDownload(gameId);
|
||||
const pauseDownload = async (shop: GameShop, objectId: string) => {
|
||||
await window.electron.pauseGameDownload(shop, objectId);
|
||||
await updateLibrary();
|
||||
dispatch(clearDownload());
|
||||
};
|
||||
|
||||
const resumeDownload = async (gameId: number) => {
|
||||
await window.electron.resumeGameDownload(gameId);
|
||||
const resumeDownload = async (shop: GameShop, objectId: string) => {
|
||||
await window.electron.resumeGameDownload(shop, objectId);
|
||||
return updateLibrary();
|
||||
};
|
||||
|
||||
const removeGameInstaller = async (gameId: number) => {
|
||||
dispatch(setGameDeleting(gameId));
|
||||
const removeGameInstaller = async (shop: GameShop, objectId: string) => {
|
||||
dispatch(setGameDeleting(objectId));
|
||||
|
||||
try {
|
||||
await window.electron.deleteGameFolder(gameId);
|
||||
await window.electron.deleteGameFolder(shop, objectId);
|
||||
updateLibrary();
|
||||
} finally {
|
||||
dispatch(removeGameFromDeleting(gameId));
|
||||
dispatch(removeGameFromDeleting(objectId));
|
||||
}
|
||||
};
|
||||
|
||||
const cancelDownload = async (gameId: number) => {
|
||||
await window.electron.cancelGameDownload(gameId);
|
||||
const cancelDownload = async (shop: GameShop, objectId: string) => {
|
||||
await window.electron.cancelGameDownload(shop, objectId);
|
||||
dispatch(clearDownload());
|
||||
updateLibrary();
|
||||
|
||||
removeGameInstaller(gameId);
|
||||
removeGameInstaller(shop, objectId);
|
||||
};
|
||||
|
||||
const removeGameFromLibrary = (gameId: number) =>
|
||||
window.electron.removeGameFromLibrary(gameId).then(() => {
|
||||
const removeGameFromLibrary = (shop: GameShop, objectId: string) =>
|
||||
window.electron.removeGameFromLibrary(shop, objectId).then(() => {
|
||||
updateLibrary();
|
||||
});
|
||||
|
||||
const pauseSeeding = async (gameId: number) => {
|
||||
await window.electron.pauseGameSeed(gameId);
|
||||
const pauseSeeding = async (shop: GameShop, objectId: string) => {
|
||||
await window.electron.pauseGameSeed(shop, objectId);
|
||||
await updateLibrary();
|
||||
};
|
||||
|
||||
const resumeSeeding = async (gameId: number) => {
|
||||
await window.electron.resumeGameSeed(gameId);
|
||||
const resumeSeeding = async (shop: GameShop, objectId: string) => {
|
||||
await window.electron.resumeGameSeed(shop, objectId);
|
||||
await updateLibrary();
|
||||
};
|
||||
|
||||
@@ -90,8 +94,8 @@ export function useDownload() {
|
||||
}
|
||||
};
|
||||
|
||||
const isGameDeleting = (gameId: number) => {
|
||||
return gamesWithDeletionInProgress.includes(gameId);
|
||||
const isGameDeleting = (objectId: string) => {
|
||||
return gamesWithDeletionInProgress.includes(objectId);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function Achievements() {
|
||||
.getComparedUnlockedAchievements(objectId, shop as GameShop, userId)
|
||||
.then(setComparedAchievements);
|
||||
}
|
||||
}, [objectId, shop, userId]);
|
||||
}, [objectId, shop, userDetails?.id, userId]);
|
||||
|
||||
const otherUserId = userDetails?.id === userId ? null : userId;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import type { LibraryGame, SeedingStatus } from "@types";
|
||||
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
|
||||
|
||||
import { Badge, Button } from "@renderer/components";
|
||||
import {
|
||||
@@ -33,8 +33,8 @@ import {
|
||||
export interface DownloadGroupProps {
|
||||
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[];
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export function DownloadGroup({
|
||||
openDeleteGameModal,
|
||||
openGameInstaller,
|
||||
seedingStatus,
|
||||
}: DownloadGroupProps) {
|
||||
}: Readonly<DownloadGroupProps>) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation("downloads");
|
||||
@@ -65,18 +65,19 @@ export function DownloadGroup({
|
||||
} = useDownload();
|
||||
|
||||
const getFinalDownloadSize = (game: LibraryGame) => {
|
||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||
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 (lastPacket?.download.fileSize && isGameDownloading)
|
||||
return formatBytes(lastPacket.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,7 +87,9 @@ export function DownloadGroup({
|
||||
}, [seedingStatus]);
|
||||
|
||||
const getGameInfo = (game: LibraryGame) => {
|
||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||
const download = game.download!;
|
||||
|
||||
const isGameDownloading = lastPacket?.gameId === game.id;
|
||||
const finalDownloadSize = getFinalDownloadSize(game);
|
||||
const seedingStatus = seedingMap.get(game.id);
|
||||
|
||||
@@ -113,11 +116,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>
|
||||
@@ -126,11 +129,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>}
|
||||
@@ -140,41 +143,44 @@ 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(download.queued ? "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: LibraryGame): DropdownMenuItem[] => {
|
||||
const isGameDownloading = lastPacket?.game.id === game.id;
|
||||
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 />,
|
||||
},
|
||||
{
|
||||
@@ -182,36 +188,48 @@ 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 />,
|
||||
},
|
||||
];
|
||||
@@ -221,14 +239,18 @@ 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 />,
|
||||
},
|
||||
];
|
||||
@@ -251,13 +273,19 @@ export function DownloadGroup({
|
||||
<div className="download-group__cover">
|
||||
<div className="download-group__cover-backdrop">
|
||||
<img
|
||||
src={steamUrlBuilder.library(game.objectID)}
|
||||
src={steamUrlBuilder.library(game.objectId)}
|
||||
className="download-group__cover-image"
|
||||
alt={game.title}
|
||||
/>
|
||||
|
||||
<div className="download-group__cover-content">
|
||||
<Badge>{DOWNLOADER_NAME[game.downloader]}</Badge>
|
||||
<Badge>
|
||||
{
|
||||
DOWNLOADER_NAME[
|
||||
game?.download?.downloader as Downloader
|
||||
]
|
||||
}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,7 +299,7 @@ export function DownloadGroup({
|
||||
navigate(
|
||||
buildGameDetailsPath({
|
||||
...game,
|
||||
objectId: game.objectID,
|
||||
objectId: game.objectId,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
|
||||
import "./downloads.scss";
|
||||
import { DeleteGameModal } from "./delete-game-modal";
|
||||
import { DownloadGroup } from "./download-group";
|
||||
import type { LibraryGame, 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,14 +40,14 @@ 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);
|
||||
};
|
||||
|
||||
@@ -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.queued || 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 = [
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ export function HeroPanelActions() {
|
||||
game,
|
||||
repacks,
|
||||
isGameRunning,
|
||||
shop,
|
||||
objectId,
|
||||
gameTitle,
|
||||
setShowGameOptionsModal,
|
||||
@@ -32,7 +33,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();
|
||||
|
||||
@@ -42,7 +43,7 @@ export function HeroPanelActions() {
|
||||
setToggleLibraryGameDisabled(true);
|
||||
|
||||
try {
|
||||
await window.electron.addGameToLibrary(objectId!, gameTitle, "steam");
|
||||
await window.electron.addGameToLibrary(shop, objectId!, gameTitle);
|
||||
|
||||
updateLibrary();
|
||||
updateGame();
|
||||
@@ -55,7 +56,8 @@ export function HeroPanelActions() {
|
||||
if (game) {
|
||||
if (game.executablePath) {
|
||||
window.electron.openGame(
|
||||
game.id,
|
||||
game.shop,
|
||||
game.objectId,
|
||||
game.executablePath,
|
||||
game.launchOptions
|
||||
);
|
||||
@@ -65,7 +67,8 @@ export function HeroPanelActions() {
|
||||
const gameExecutablePath = await selectGameExecutable();
|
||||
if (gameExecutablePath)
|
||||
window.electron.openGame(
|
||||
game.id,
|
||||
game.shop,
|
||||
game.objectId,
|
||||
gameExecutablePath,
|
||||
game.launchOptions
|
||||
);
|
||||
@@ -73,7 +76,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;
|
||||
|
||||
@@ -43,21 +43,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="hero-panel-playtime__download-details">
|
||||
<Link to="/downloads" className="hero-panel-playtime__downloads-link">
|
||||
{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>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
@@ -66,9 +66,11 @@ export function HeroPanel({ isHeaderStuck }: HeroPanelProps) {
|
||||
{showProgressBar && (
|
||||
<progress
|
||||
max={1}
|
||||
value={isGameDownloading ? lastPacket?.game.progress : game?.progress}
|
||||
value={
|
||||
isGameDownloading ? lastPacket?.progress : game?.download?.progress
|
||||
}
|
||||
className={`hero-panel__progress-bar ${
|
||||
game?.status === "paused"
|
||||
game?.download?.status === "paused"
|
||||
? "hero-panel__progress-bar--disabled"
|
||||
: ""
|
||||
}`}
|
||||
|
||||
@@ -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 { gameDetailsContext } from "@renderer/context";
|
||||
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
|
||||
import { useDownload, useToast, useUserDetails } from "@renderer/hooks";
|
||||
@@ -13,7 +13,7 @@ import "./game-options-modal.scss";
|
||||
|
||||
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) {
|
||||
@@ -330,7 +344,7 @@ export function GameOptionsModal({
|
||||
>
|
||||
{t("open_download_options")}
|
||||
</Button>
|
||||
{game.downloadPath && (
|
||||
{game.download?.downloadPath && (
|
||||
<Button
|
||||
onClick={handleOpenDownloadFolder}
|
||||
theme="outline"
|
||||
@@ -377,7 +391,9 @@ export function GameOptionsModal({
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
theme="danger"
|
||||
disabled={isGameDownloading || deleting || !game.downloadPath}
|
||||
disabled={
|
||||
isGameDownloading || deleting || !game.download?.downloadPath
|
||||
}
|
||||
>
|
||||
{t("remove_files")}
|
||||
</Button>
|
||||
|
||||
@@ -66,8 +66,8 @@ export function RepacksModal({
|
||||
};
|
||||
|
||||
const checkIfLastDownloadedOption = (repack: GameRepack) => {
|
||||
if (!game) return false;
|
||||
return repack.uris.some((uri) => uri.includes(game.uri!));
|
||||
if (!game?.download) return false;
|
||||
return repack.uris.some((uri) => uri.includes(game.download!.uri));
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -22,7 +22,7 @@ import { buildGameAchievementPath } from "@renderer/helpers";
|
||||
import { useSubscription } from "@renderer/hooks/use-subscription";
|
||||
import "./sidebar.scss";
|
||||
|
||||
const fakeAchievements: UserAchievement[] = [
|
||||
const achievementsPlaceholder: UserAchievement[] = [
|
||||
{
|
||||
displayName: "Timber!!",
|
||||
name: "",
|
||||
@@ -136,7 +136,7 @@ export function Sidebar() {
|
||||
<h3>{t("sign_in_to_see_achievements")}</h3>
|
||||
</div>
|
||||
<ul className="list" style={{ filter: "blur(4px)" }}>
|
||||
{fakeAchievements.map((achievement, index) => (
|
||||
{achievementsPlaceholder.map((achievement, index) => (
|
||||
<li key={index}>
|
||||
<div className="list__item">
|
||||
<img
|
||||
@@ -209,7 +209,6 @@ export function Sidebar() {
|
||||
))}
|
||||
|
||||
<Link
|
||||
style={{ textAlign: "center" }}
|
||||
to={buildGameAchievementPath({
|
||||
shop: shop,
|
||||
objectId: objectId!,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useAppDispatch, useFormat } from "@renderer/hooks";
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { TelescopeIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { LockedProfile } from "./locked-profile";
|
||||
import { ReportProfile } from "../report-profile/report-profile";
|
||||
import { FriendsBox } from "./friends-box";
|
||||
@@ -65,8 +64,6 @@ export function ProfileContent() {
|
||||
|
||||
const { numberFormatter } = useFormat();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const usersAreFriends = useMemo(() => {
|
||||
return userProfile?.relation?.status === "ACCEPTED";
|
||||
}, [userProfile]);
|
||||
@@ -139,7 +136,6 @@ export function ProfileContent() {
|
||||
userStats,
|
||||
numberFormatter,
|
||||
t,
|
||||
navigate,
|
||||
statsIndex,
|
||||
]);
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@ export function ProfileHero() {
|
||||
if (gameRunning)
|
||||
return {
|
||||
...gameRunning,
|
||||
objectId: gameRunning.objectID,
|
||||
objectId: gameRunning.objectId,
|
||||
sessionDurationInSeconds: gameRunning.sessionDurationInMillis / 1000,
|
||||
};
|
||||
|
||||
|
||||
@@ -60,10 +60,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;
|
||||
@@ -89,31 +109,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 (
|
||||
<div className="settings-general">
|
||||
<TextField
|
||||
|
||||
Reference in New Issue
Block a user