Merge remote-tracking branch 'origin/main' into feat/add-select-folder-modal-in-game-installation

This commit is contained in:
José Luís 2024-04-20 17:03:29 -03:00
commit af715aa110
30 changed files with 416 additions and 275 deletions

View file

@ -203,7 +203,7 @@ export function Sidebar() {
className={styles.menuItem({
active:
location.pathname === `/game/${game.shop}/${game.objectID}`,
muted: game.status === null || game.status === "cancelled",
muted: game.status === "cancelled",
})}
>
<button

View file

@ -55,12 +55,13 @@ declare global {
addGameToLibrary: (
objectID: string,
title: string,
shop: GameShop
shop: GameShop,
executablePath: string
) => Promise<void>;
getLibrary: () => Promise<Game[]>;
getRepackersFriendlyNames: () => Promise<Record<string, string>>;
openGameInstaller: (gameId: number) => Promise<boolean>;
openGame: (gameId: number, path: string) => Promise<void>;
openGame: (gameId: number, executablePath: string) => Promise<void>;
closeGame: (gameId: number) => Promise<boolean>;
removeGame: (gameId: number) => Promise<void>;
deleteGameFolder: (gameId: number) => Promise<unknown>;

View file

@ -32,7 +32,18 @@ import { store } from "./store";
import * as resources from "@locales";
if (process.env.SENTRY_DSN) {
init({ dsn: process.env.SENTRY_DSN }, reactInit);
init(
{
dsn: process.env.SENTRY_DSN,
beforeSend: async (event) => {
const userPreferences = await window.electron.getUserPreferences();
if (userPreferences?.telemetryEnabled) return event;
return null;
},
},
reactInit
);
}
const router = createHashRouter([

View file

@ -71,6 +71,7 @@ export function Catalogue() {
display: "flex",
width: "100%",
justifyContent: "space-between",
alignItems: "center",
borderBottom: `1px solid ${vars.color.borderColor}`,
}}
>
@ -103,7 +104,6 @@ export function Catalogue() {
key={game.objectID}
game={game}
onClick={() => handleGameClick(game)}
disabled={!game.repacks.length}
/>
))}
</>

View file

@ -83,7 +83,9 @@ export function GameDetails() {
}, [getGame, gameDownloading?.id]);
useEffect(() => {
setGame(null);
setIsLoading(true);
setIsGamePlaying(false);
dispatch(setHeaderTitle(""));
getRandomGame();

View file

@ -0,0 +1,212 @@
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
import { Button } from "@renderer/components";
import { useDownload, useLibrary } from "@renderer/hooks";
import type { Game, ShopDetails } from "@types";
import { useState } from "react";
import { useTranslation } from "react-i18next";
export interface HeroPanelActionsProps {
game: Game | null;
gameDetails: ShopDetails | null;
isGamePlaying: boolean;
isGameDownloading: boolean;
openRepacksModal: () => void;
openBinaryNotFoundModal: () => void;
getGame: () => void;
}
export function HeroPanelActions({
game,
gameDetails,
isGamePlaying,
isGameDownloading,
openRepacksModal,
openBinaryNotFoundModal,
getGame,
}: HeroPanelActionsProps) {
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
useState(false);
const {
resumeDownload,
pauseDownload,
cancelDownload,
removeGame,
isGameDeleting,
} = useDownload();
const { updateLibrary } = useLibrary();
const { t } = useTranslation("game_details");
const selectGameExecutable = async () => {
return window.electron
.showOpenDialog({
properties: ["openFile"],
filters: [
{
name: "Game executable",
extensions: window.electron.platform === "win32" ? ["exe"] : [],
},
],
})
.then(({ filePaths }) => {
if (filePaths && filePaths.length > 0) {
return filePaths[0];
}
});
};
const toggleGameOnLibrary = async () => {
setToggleLibraryGameDisabled(true);
try {
if (game) {
await removeGame(game.id);
} else {
const gameExecutablePath = await selectGameExecutable();
await window.electron.addGameToLibrary(
gameDetails.objectID,
gameDetails.name,
"steam",
gameExecutablePath
);
}
updateLibrary();
getGame();
} finally {
setToggleLibraryGameDisabled(false);
}
};
const openGameInstaller = () => {
window.electron.openGameInstaller(game.id).then((isBinaryInPath) => {
if (!isBinaryInPath) openBinaryNotFoundModal();
updateLibrary();
});
};
const openGame = async () => {
if (game.executablePath) {
window.electron.openGame(game.id, game.executablePath);
return;
}
if (game?.executablePath) {
window.electron.openGame(game.id, game.executablePath);
return;
}
const gameExecutablePath = await selectGameExecutable();
window.electron.openGame(game.id, gameExecutablePath);
};
const closeGame = () => window.electron.closeGame(game.id);
const deleting = isGameDeleting(game?.id);
const toggleGameOnLibraryButton = (
<Button
theme="outline"
disabled={!gameDetails || toggleLibraryGameDisabled}
onClick={toggleGameOnLibrary}
>
{game ? <NoEntryIcon /> : <PlusCircleIcon />}
{game ? t("remove_from_library") : t("add_to_library")}
</Button>
);
if (isGameDownloading) {
return (
<>
<Button onClick={() => pauseDownload(game.id)} theme="outline">
{t("pause")}
</Button>
<Button onClick={() => cancelDownload(game.id)} theme="outline">
{t("cancel")}
</Button>
</>
);
}
if (game?.status === "paused") {
return (
<>
<Button onClick={() => resumeDownload(game.id)} theme="outline">
{t("resume")}
</Button>
<Button
onClick={() => cancelDownload(game.id).then(getGame)}
theme="outline"
>
{t("cancel")}
</Button>
</>
);
}
if (game?.status === "seeding" || (game && !game.status)) {
return (
<>
{game?.status === "seeding" ? (
<Button
onClick={openGameInstaller}
theme="outline"
disabled={deleting || isGamePlaying}
>
{t("install")}
</Button>
) : (
toggleGameOnLibraryButton
)}
{isGamePlaying ? (
<Button onClick={closeGame} theme="outline" disabled={deleting}>
{t("close")}
</Button>
) : (
<Button
onClick={openGame}
theme="outline"
disabled={deleting || isGamePlaying}
>
{t("play")}
</Button>
)}
</>
);
}
if (game?.status === "cancelled") {
return (
<>
<Button onClick={openRepacksModal} theme="outline" disabled={deleting}>
{t("open_download_options")}
</Button>
<Button
onClick={() => removeGame(game.id).then(getGame)}
theme="outline"
disabled={deleting}
>
{t("remove_from_list")}
</Button>
</>
);
}
if (gameDetails && gameDetails.repacks.length) {
return (
<>
{toggleGameOnLibraryButton}
<Button onClick={openRepacksModal} theme="outline">
{t("open_download_options")}
</Button>
</>
);
}
return toggleGameOnLibraryButton;
}

View file

@ -2,16 +2,15 @@ import { format } from "date-fns";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@renderer/components";
import { useDownload, useLibrary } from "@renderer/hooks";
import { useDownload } from "@renderer/hooks";
import type { Game, ShopDetails } from "@types";
import { formatDownloadProgress } from "@renderer/helpers";
import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react";
import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal";
import * as styles from "./hero-panel.css";
import { useDate } from "@renderer/hooks/use-date";
import { formatBytes } from "@renderer/utils";
import { HeroPanelActions } from "./hero-panel-actions";
export interface HeroPanelProps {
game: Game | null;
@ -44,21 +43,8 @@ export function HeroPanel({
eta,
numPeers,
numSeeds,
resumeDownload,
pauseDownload,
cancelDownload,
removeGame,
isGameDeleting,
} = useDownload();
const { updateLibrary, library } = useLibrary();
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
useState(false);
const gameOnLibrary = library.find(
({ objectID }) => objectID === gameDetails?.objectID
);
const isGameDownloading = isDownloading && gameDownloading?.id === game?.id;
const updateLastTimePlayed = useCallback(() => {
@ -83,41 +69,6 @@ export function HeroPanel({
}
}, [game?.lastTimePlayed, updateLastTimePlayed]);
const openGameInstaller = () => {
window.electron.openGameInstaller(game.id).then((isBinaryInPath) => {
if (!isBinaryInPath) setShowBinaryNotFoundModal(true);
updateLibrary();
});
};
const openGame = () => {
if (game.executablePath) {
window.electron.openGame(game.id, game.executablePath);
return;
}
if (game?.executablePath) {
window.electron.openGame(game.id, game.executablePath);
return;
}
window.electron
.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Game executable (.exe)", extensions: ["exe"] }],
})
.then(({ filePaths }) => {
if (filePaths && filePaths.length > 0) {
const path = filePaths[0];
window.electron.openGame(game.id, path);
}
});
};
const closeGame = () => {
window.electron.closeGame(game.id);
};
const finalDownloadSize = useMemo(() => {
if (!game) return "N/A";
if (game.fileSize) return formatBytes(game.fileSize);
@ -128,26 +79,6 @@ export function HeroPanel({
return game.repack?.fileSize ?? "N/A";
}, [game, isGameDownloading, gameDownloading]);
const toggleLibraryGame = async () => {
setToggleLibraryGameDisabled(true);
try {
if (gameOnLibrary) {
await window.electron.removeGame(gameOnLibrary.id);
} else {
await window.electron.addGameToLibrary(
gameDetails.objectID,
gameDetails.name,
"steam"
);
}
await updateLibrary();
} finally {
setToggleLibraryGameDisabled(false);
}
};
const getInfo = () => {
if (!gameDetails) return null;
@ -196,7 +127,7 @@ export function HeroPanel({
);
}
if (game?.status === "seeding") {
if (game?.status === "seeding" || (game && !game.status)) {
if (!game.lastTimePlayed) {
return <p>{t("not_played_yet", { title: game.title })}</p>;
}
@ -239,121 +170,26 @@ export function HeroPanel({
return <p>{t("no_downloads")}</p>;
};
const getActions = () => {
const deleting = isGameDeleting(game?.id);
const toggleGameOnLibraryButton = (
<Button
theme="outline"
disabled={!gameDetails || toggleLibraryGameDisabled}
onClick={toggleLibraryGame}
>
{gameOnLibrary ? <NoEntryIcon /> : <PlusCircleIcon />}
{gameOnLibrary ? t("remove_from_library") : t("add_to_library")}
</Button>
);
if (isGameDownloading) {
return (
<>
<Button onClick={() => pauseDownload(game.id)} theme="outline">
{t("pause")}
</Button>
<Button onClick={() => cancelDownload(game.id)} theme="outline">
{t("cancel")}
</Button>
</>
);
}
if (game?.status === "paused") {
return (
<>
<Button onClick={() => resumeDownload(game.id)} theme="outline">
{t("resume")}
</Button>
<Button
onClick={() => cancelDownload(game.id).then(getGame)}
theme="outline"
>
{t("cancel")}
</Button>
</>
);
}
if (game?.status === "seeding") {
return (
<>
<Button
onClick={openGameInstaller}
theme="outline"
disabled={deleting || isGamePlaying}
>
{t("install")}
</Button>
{isGamePlaying ? (
<Button onClick={closeGame} theme="outline" disabled={deleting}>
{t("close")}
</Button>
) : (
<Button
onClick={openGame}
theme="outline"
disabled={deleting || isGamePlaying}
>
{t("play")}
</Button>
)}
</>
);
}
if (game?.status === "cancelled") {
return (
<>
<Button
onClick={openRepacksModal}
theme="outline"
disabled={deleting}
>
{t("open_download_options")}
</Button>
<Button
onClick={() => removeGame(game.id).then(getGame)}
theme="outline"
disabled={deleting}
>
{t("remove_from_list")}
</Button>
</>
);
}
if (gameDetails && gameDetails.repacks.length) {
return (
<>
{toggleGameOnLibraryButton}
<Button onClick={openRepacksModal} theme="outline">
{t("open_download_options")}
</Button>
</>
);
}
return toggleGameOnLibraryButton;
};
return (
<>
<BinaryNotFoundModal
visible={showBinaryNotFoundModal}
onClose={() => setShowBinaryNotFoundModal(false)}
/>
<div style={{ backgroundColor: color }} className={styles.panel}>
<div className={styles.content}>{getInfo()}</div>
<div className={styles.actions}>{getActions()}</div>
<div className={styles.actions}>
<HeroPanelActions
game={game}
gameDetails={gameDetails}
getGame={getGame}
openRepacksModal={openRepacksModal}
openBinaryNotFoundModal={() => setShowBinaryNotFoundModal(true)}
isGamePlaying={isGamePlaying}
isGameDownloading={isGameDownloading}
/>
</div>
</div>
</>
);

View file

@ -10,6 +10,7 @@ import type { DiskSpace } from "check-disk-space";
import { format } from "date-fns";
import { SPACING_UNIT } from "@renderer/theme.css";
import { formatBytes } from "@renderer/utils";
import { useAppSelector } from "@renderer/hooks";
import { SelectFolderModal } from "./select-folder-modal";
export interface RepacksModalProps {
@ -30,6 +31,10 @@ export function RepacksModal({
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
const [repack, setRepack] = useState<GameRepack>(null);
const repackersFriendlyNames = useAppSelector(
(state) => state.repackersFriendlyNames.value
);
const { t } = useTranslation("game_details");
useEffect(() => {
@ -91,7 +96,7 @@ export function RepacksModal({
>
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
<p style={{ fontSize: "12px" }}>
{repack.fileSize} - {repack.repacker} -{" "}
{repack.fileSize} - {repackersFriendlyNames[repack.repacker]} -{" "}
{format(repack.uploadDate, "dd/MM/yyyy")}
</p>
</Button>

View file

@ -67,7 +67,6 @@ export function SearchResults() {
key={game.objectID}
game={game}
onClick={() => handleGameClick(game)}
disabled={!game.repacks.length}
/>
))}
</>

View file

@ -10,6 +10,7 @@ export function Settings() {
downloadsPath: "",
downloadNotificationsEnabled: false,
repackUpdatesNotificationsEnabled: false,
telemetryEnabled: false,
});
const { t } = useTranslation("settings");
@ -25,6 +26,7 @@ export function Settings() {
userPreferences?.downloadNotificationsEnabled,
repackUpdatesNotificationsEnabled:
userPreferences?.repackUpdatesNotificationsEnabled,
telemetryEnabled: userPreferences?.telemetryEnabled,
});
});
}, []);
@ -95,6 +97,16 @@ export function Settings() {
)
}
/>
<h3>{t("telemetry")}</h3>
<CheckboxField
label={t("telemetry_description")}
checked={form.telemetryEnabled}
onChange={() =>
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
}
/>
</div>
</section>
);