feat: adding modal animations

This commit is contained in:
Chubby Granny Chaser 2024-06-08 18:58:23 +01:00
commit 80a25bf409
No known key found for this signature in database
14 changed files with 270 additions and 208 deletions

View File

@ -101,15 +101,23 @@
"open_screenshot": "Open screenshot {{number}}", "open_screenshot": "Open screenshot {{number}}",
"download_settings": "Download settings", "download_settings": "Download settings",
"downloader": "Downloader", "downloader": "Downloader",
"select_executable": "Select executable", "select_executable": "Select",
"no_executable_selected": "No executable selected", "no_executable_selected": "No executable selected",
"open_folder": "Open folder", "open_folder": "Open folder",
"open_download_location": "Abrir local de download", "open_download_location": "See downloaded files",
"create_shortcut": "Create shortcut", "create_shortcut": "Create desktop shortcut",
"remove_files": "Remove files", "remove_files": "Remove files",
"remove_from_library_title": "Are you sure?", "remove_from_library_title": "Are you sure?",
"remove_from_library_description": "This will remove {{game}} from your library", "remove_from_library_description": "This will remove {{game}} from your library",
"options": "Options" "options": "Options",
"executable_section_title": "Executable",
"executable_section_description": "Path of the file that will be executed when \"Play\" is clicked",
"downloads_secion_title": "Downloads",
"downloads_section_description": "Check out updates or other versions of this game",
"danger_zone_section_title": "Danger zone",
"danger_zone_section_description": "Remove this game from your library or the files downloaded by Hydra",
"download_in_progress": "Download in progress",
"download_paused": "Download paused"
}, },
"activation": { "activation": {
"title": "Activate Hydra", "title": "Activate Hydra",

View File

@ -98,15 +98,23 @@
"open_screenshot": "Ver captura de tela {{number}}", "open_screenshot": "Ver captura de tela {{number}}",
"download_settings": "Ajustes do download", "download_settings": "Ajustes do download",
"downloader": "Downloader", "downloader": "Downloader",
"select_executable": "Selecionar executável", "select_executable": "Selecionar",
"no_executable_selected": "Nenhum executável selecionado", "no_executable_selected": "Nenhum executável selecionado",
"open_folder": "Abrir pasta", "open_folder": "Abrir pasta",
"open_download_location": "Abrir local de download", "open_download_location": "Ver arquivos baixados",
"create_shortcut": "Criar atalho", "create_shortcut": "Criar atalho na área de trabalho",
"remove_files": "Remover arquivos", "remove_files": "Remover arquivos",
"remove_from_library_description": "Tem certeza que deseja remover {{game}} da sua biblioteca?", "options": "Opções",
"remove_from_library_description": "Isso irá remover {{game}} da sua biblioteca",
"remove_from_library_title": "Tem certeza?", "remove_from_library_title": "Tem certeza?",
"options": "Opções" "executable_section_title": "Executável",
"executable_section_description": "O caminho do arquivo que será executado ao clicar em \"Jogar\"",
"downloads_secion_title": "Downloads",
"downloads_section_description": "Confira atualizações ou versões diferentes para este mesmo título",
"danger_zone_section_title": "Zona de perigo",
"danger_zone_section_description": "Remova o jogo da sua biblioteca ou os arquivos que foram baixados pelo Hydra",
"download_in_progress": "Download em andamento",
"download_paused": "Download pausado"
}, },
"activation": { "activation": {
"title": "Ativação", "title": "Ativação",

View File

@ -13,10 +13,13 @@ const createGameShortcut = async (
if (game) { if (game) {
const filePath = game.executablePath; const filePath = game.executablePath;
const options = { filePath, name: game.title };
return createDesktopShortcut({ return createDesktopShortcut({
windows: { filePath }, windows: options,
linux: { filePath }, linux: options,
osx: { filePath }, osx: options,
}); });
} }

View File

@ -5,7 +5,10 @@ const removeGameFromLibrary = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
gameId: number gameId: number
) => { ) => {
gameRepository.update({ id: gameId }, { isDeleted: true }); gameRepository.update(
{ id: gameId },
{ isDeleted: true, executablePath: null }
);
}; };
registerEvent("removeGameFromLibrary", removeGameFromLibrary); registerEvent("removeGameFromLibrary", removeGameFromLibrary);

View File

@ -55,4 +55,15 @@ export const button = styleVariants({
color: "#c0c1c7", color: "#c0c1c7",
}, },
], ],
danger: [
base,
{
border: `solid 1px #a31533`,
backgroundColor: "transparent",
color: "white",
":hover": {
backgroundColor: "#a31533",
},
},
],
}); });

View File

@ -54,7 +54,7 @@ export function Hero() {
> >
<div className={styles.backdrop}> <div className={styles.backdrop}>
<img <img
src="https://cdn2.steamgriddb.com/hero/4ef10445b952a8b3c93a9379d581146a.jpg" src={steamUrlBuilder.libraryHero(FEATURED_GAME_ID)}
alt={FEATURED_GAME_TITLE} alt={FEATURED_GAME_TITLE}
className={styles.heroMedia} className={styles.heroMedia}
/> />

View File

@ -2,24 +2,25 @@ import { keyframes, style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes"; import { recipe } from "@vanilla-extract/recipes";
import { SPACING_UNIT, vars } from "../../theme.css"; import { SPACING_UNIT, vars } from "../../theme.css";
export const fadeIn = keyframes({ export const scaleFadeIn = keyframes({
"0%": { opacity: 0 }, "0%": { opacity: "0", scale: "0.5" },
"100%": { "100%": {
opacity: 1, opacity: "1",
scale: "1",
}, },
}); });
export const fadeOut = keyframes({ export const scaleFadeOut = keyframes({
"0%": { opacity: 1 }, "0%": { opacity: "1", scale: "1" },
"100%": { "100%": {
opacity: 0, opacity: "0",
scale: "0.5",
}, },
}); });
export const modal = recipe({ export const modal = recipe({
base: { base: {
animationName: fadeIn, animation: `${scaleFadeIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`,
animationDuration: "0.3s",
backgroundColor: vars.color.background, backgroundColor: vars.color.background,
borderRadius: "4px", borderRadius: "4px",
maxWidth: "600px", maxWidth: "600px",
@ -33,8 +34,8 @@ export const modal = recipe({
variants: { variants: {
closing: { closing: {
true: { true: {
animationName: fadeOut, animationName: scaleFadeOut,
opacity: 0, opacity: "0",
}, },
}, },
large: { large: {

View File

@ -3,7 +3,7 @@ import { useParams, useSearchParams } from "react-router-dom";
import { setHeaderTitle } from "@renderer/features"; import { setHeaderTitle } from "@renderer/features";
import { getSteamLanguage } from "@renderer/helpers"; import { getSteamLanguage } from "@renderer/helpers";
import { useAppDispatch, useDownload } from "@renderer/hooks"; import { useAppDispatch, useAppSelector, useDownload } from "@renderer/hooks";
import type { Game, GameRepack, GameShop, ShopDetails } from "@types"; import type { Game, GameRepack, GameShop, ShopDetails } from "@types";
@ -16,6 +16,7 @@ import {
RepacksModal, RepacksModal,
} from "./modals"; } from "./modals";
import { Downloader } from "@shared"; import { Downloader } from "@shared";
import { GameOptionsModal } from "./modals/game-options-modal";
export interface GameDetailsContext { export interface GameDetailsContext {
game: Game | null; game: Game | null;
@ -29,6 +30,8 @@ export interface GameDetailsContext {
gameColor: string; gameColor: string;
setGameColor: React.Dispatch<React.SetStateAction<string>>; setGameColor: React.Dispatch<React.SetStateAction<string>>;
openRepacksModal: () => void; openRepacksModal: () => void;
openGameOptionsModal: () => void;
selectGameExecutable: () => Promise<string | null>;
updateGame: () => Promise<void>; updateGame: () => Promise<void>;
} }
@ -44,6 +47,8 @@ export const gameDetailsContext = createContext<GameDetailsContext>({
gameColor: "", gameColor: "",
setGameColor: () => {}, setGameColor: () => {},
openRepacksModal: () => {}, openRepacksModal: () => {},
openGameOptionsModal: () => {},
selectGameExecutable: async () => null,
updateGame: async () => {}, updateGame: async () => {},
}); });
@ -70,6 +75,7 @@ export function GameDetailsContextProvider({
>(null); >(null);
const [isGameRunning, setisGameRunning] = useState(false); const [isGameRunning, setisGameRunning] = useState(false);
const [showRepacksModal, setShowRepacksModal] = useState(false); const [showRepacksModal, setShowRepacksModal] = useState(false);
const [showGameOptionsModal, setShowGameOptionsModal] = useState(false);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -81,6 +87,10 @@ export function GameDetailsContextProvider({
const { startDownload, lastPacket } = useDownload(); const { startDownload, lastPacket } = useDownload();
const userPreferences = useAppSelector(
(state) => state.userPreferences.value
);
const updateGame = useCallback(async () => { const updateGame = useCallback(async () => {
return window.electron return window.electron
.getGameByObjectID(objectID!) .getGameByObjectID(objectID!)
@ -158,6 +168,7 @@ export function GameDetailsContextProvider({
await updateGame(); await updateGame();
setShowRepacksModal(false); setShowRepacksModal(false);
setShowGameOptionsModal(false);
if ( if (
repack.repacker === "onlinefix" && repack.repacker === "onlinefix" &&
@ -172,7 +183,36 @@ export function GameDetailsContextProvider({
} }
}; };
const getDownloadsPath = async () => {
if (userPreferences?.downloadsPath) return userPreferences.downloadsPath;
return window.electron.getDefaultDownloadsPath();
};
const selectGameExecutable = async () => {
const downloadsPath = await getDownloadsPath();
return window.electron
.showOpenDialog({
properties: ["openFile"],
defaultPath: downloadsPath,
filters: [
{
name: "Game executable",
extensions: ["exe"],
},
],
})
.then(({ filePaths }) => {
if (filePaths && filePaths.length > 0) {
return filePaths[0];
}
return null;
});
};
const openRepacksModal = () => setShowRepacksModal(true); const openRepacksModal = () => setShowRepacksModal(true);
const openGameOptionsModal = () => setShowGameOptionsModal(true);
return ( return (
<Provider <Provider
@ -188,6 +228,8 @@ export function GameDetailsContextProvider({
gameColor, gameColor,
setGameColor, setGameColor,
openRepacksModal, openRepacksModal,
openGameOptionsModal,
selectGameExecutable,
updateGame, updateGame,
}} }}
> >
@ -208,6 +250,16 @@ export function GameDetailsContextProvider({
onClose={() => setShowInstructionsModal(null)} onClose={() => setShowInstructionsModal(null)}
/> />
{game && (
<GameOptionsModal
visible={showGameOptionsModal}
game={game}
onClose={() => {
setShowGameOptionsModal(false);
}}
/>
)}
{children} {children}
</> </>
</Provider> </Provider>

View File

@ -13,5 +13,6 @@ export const actions = style({
export const separator = style({ export const separator = style({
width: "1px", width: "1px",
backgroundColor: vars.color.border, backgroundColor: vars.color.muted,
opacity: "0.2",
}); });

View File

@ -1,16 +1,14 @@
import { GearIcon, PlayIcon, PlusCircleIcon } from "@primer/octicons-react"; import { GearIcon, PlayIcon, PlusCircleIcon } from "@primer/octicons-react";
import { Button } from "@renderer/components"; import { Button } from "@renderer/components";
import { useAppSelector, useDownload, useLibrary } from "@renderer/hooks"; import { useDownload, useLibrary } from "@renderer/hooks";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import * as styles from "./hero-panel-actions.css"; import * as styles from "./hero-panel-actions.css";
import { gameDetailsContext } from "../game-details.context"; import { gameDetailsContext } from "../game-details.context";
import { GameOptionsModal } from "../modals/game-options-modal";
export function HeroPanelActions() { export function HeroPanelActions() {
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] = const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
useState(false); useState(false);
const [showGameOptionsModal, setShowGameOptionsModal] = useState(false);
const { isGameDeleting } = useDownload(); const { isGameDeleting } = useDownload();
@ -21,45 +19,15 @@ export function HeroPanelActions() {
objectID, objectID,
gameTitle, gameTitle,
openRepacksModal, openRepacksModal,
openGameOptionsModal,
updateGame, updateGame,
selectGameExecutable,
} = useContext(gameDetailsContext); } = useContext(gameDetailsContext);
const userPreferences = useAppSelector(
(state) => state.userPreferences.value
);
const { updateLibrary } = useLibrary(); const { updateLibrary } = useLibrary();
const { t } = useTranslation("game_details"); const { t } = useTranslation("game_details");
const getDownloadsPath = async () => {
if (userPreferences?.downloadsPath) return userPreferences.downloadsPath;
return window.electron.getDefaultDownloadsPath();
};
const selectGameExecutable = async () => {
const downloadsPath = await getDownloadsPath();
return window.electron
.showOpenDialog({
properties: ["openFile"],
defaultPath: downloadsPath,
filters: [
{
name: "Game executable",
extensions: ["exe"],
},
],
})
.then(({ filePaths }) => {
if (filePaths && filePaths.length > 0) {
return filePaths[0];
}
return null;
});
};
const addGameToLibrary = async () => { const addGameToLibrary = async () => {
setToggleLibraryGameDisabled(true); setToggleLibraryGameDisabled(true);
@ -126,53 +94,40 @@ export function HeroPanelActions() {
if (game) { if (game) {
return ( return (
<> <div className={styles.actions}>
<GameOptionsModal {isGameRunning ? (
visible={showGameOptionsModal}
game={game}
onClose={() => {
setShowGameOptionsModal(false);
}}
selectGameExecutable={selectGameExecutable}
/>
<div className={styles.actions}>
{isGameRunning ? (
<Button
onClick={closeGame}
theme="outline"
disabled={deleting}
className={styles.heroPanelAction}
>
{t("close")}
</Button>
) : (
<Button
onClick={openGame}
theme="outline"
disabled={deleting || isGameRunning}
className={styles.heroPanelAction}
>
<PlayIcon />
{t("play")}
</Button>
)}
<div className={styles.separator} />
<Button <Button
onClick={() => { onClick={closeGame}
setShowGameOptionsModal(true); theme="outline"
}} disabled={deleting}
className={styles.heroPanelAction}
>
{t("close")}
</Button>
) : (
<Button
onClick={openGame}
theme="outline" theme="outline"
disabled={deleting || isGameRunning} disabled={deleting || isGameRunning}
className={styles.heroPanelAction} className={styles.heroPanelAction}
> >
<GearIcon /> <PlayIcon />
{t("options")} {t("play")}
</Button> </Button>
</div> )}
</>
<div className={styles.separator} />
<Button
onClick={openGameOptionsModal}
theme="outline"
disabled={deleting || isGameRunning}
className={styles.heroPanelAction}
>
<GearIcon />
{t("options")}
</Button>
</div>
); );
} }

View File

@ -15,6 +15,8 @@ export function HeroPanelPlaytime() {
const { i18n, t } = useTranslation("game_details"); const { i18n, t } = useTranslation("game_details");
const { progress, lastPacket } = useDownload();
const { formatDistance } = useDate(); const { formatDistance } = useDate();
useEffect(() => { useEffect(() => {
@ -48,40 +50,33 @@ export function HeroPanelPlaytime() {
return t("amount_hours", { amount: numberFormatter.format(hours) }); return t("amount_hours", { amount: numberFormatter.format(hours) });
}; };
const { progress, lastPacket } = useDownload(); if (!game) return null;
const hasDownload =
["active", "paused"].includes(game.status) && game.progress !== 1;
const isGameDownloading = const isGameDownloading =
game?.status === "active" && lastPacket?.game.id === game?.id; game.status === "active" && lastPacket?.game.id === game.id;
if (!game) return; const downloadInProgressInfo = (
<div className={styles.downloadDetailsRow}>
<Link to="/downloads" className={styles.downloadsLink}>
{game.status === "active"
? t("download_in_progress")
: t("download_paused")}
</Link>
let downloadContent: JSX.Element | null = null; <small>
{isGameDownloading ? progress : formatDownloadProgress(game.progress)}
if (game.status === "active") { </small>
if (lastPacket?.isDownloadingMetadata && isGameDownloading) { </div>
downloadContent = <p>{t("downloading_metadata")}</p>; );
} else if (game.progress !== 1) {
downloadContent = (
<div className={styles.downloadDetailsRow}>
<Link to="/downloads" className={styles.downloadsLink}>
Download em andamento
</Link>
<small>
{isGameDownloading
? progress
: formatDownloadProgress(game.progress)}
</small>
</div>
);
}
}
if (!game.lastTimePlayed) { if (!game.lastTimePlayed) {
return ( return (
<> <>
<p>{t("not_played_yet", { title: game?.title })}</p> <p>{t("not_played_yet", { title: game?.title })}</p>
{downloadContent} {hasDownload && downloadInProgressInfo}
</> </>
); );
} }
@ -89,15 +84,9 @@ export function HeroPanelPlaytime() {
if (isGameRunning) { if (isGameRunning) {
return ( return (
<> <>
{downloadContent || (
<p>
{t("play_time", {
amount: formatPlayTime(),
})}
</p>
)}
<p>{t("playing_now")}</p> <p>{t("playing_now")}</p>
{hasDownload && downloadInProgressInfo}
</> </>
); );
} }
@ -110,7 +99,9 @@ export function HeroPanelPlaytime() {
})} })}
</p> </p>
{downloadContent || ( {hasDownload ? (
downloadInProgressInfo
) : (
<p> <p>
{t("last_time_played", { {t("last_time_played", {
period: lastTimePlayed, period: lastTimePlayed,

View File

@ -36,6 +36,7 @@ export const downloadDetailsRow = style({
export const downloadsLink = style({ export const downloadsLink = style({
color: vars.color.body, color: vars.color.body,
textDecoration: "underline",
}); });
export const progressBar = recipe({ export const progressBar = recipe({

View File

@ -7,6 +7,17 @@ export const optionsContainer = style({
flexDirection: "column", flexDirection: "column",
}); });
export const gameOptionHeader = style({
display: "flex",
flexDirection: "column",
gap: `${SPACING_UNIT}px`,
});
export const gameOptionHeaderDescription = style({
fontFamily: "'Fira Sans', sans-serif",
fontWeight: "400",
});
export const gameOptionRow = style({ export const gameOptionRow = style({
display: "flex", display: "flex",
gap: `${SPACING_UNIT}px`, gap: `${SPACING_UNIT}px`,

View File

@ -4,7 +4,6 @@ import { Button, Modal, TextField } from "@renderer/components";
import type { Game } from "@types"; import type { Game } from "@types";
import * as styles from "./game-options-modal.css"; import * as styles from "./game-options-modal.css";
import { gameDetailsContext } from "../game-details.context"; import { gameDetailsContext } from "../game-details.context";
import { NoEntryIcon, TrashIcon } from "@primer/octicons-react";
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal"; import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
import { useDownload } from "@renderer/hooks"; import { useDownload } from "@renderer/hooks";
import { RemoveGameFromLibraryModal } from "./remove-from-library-modal"; import { RemoveGameFromLibraryModal } from "./remove-from-library-modal";
@ -13,43 +12,50 @@ export interface GameOptionsModalProps {
visible: boolean; visible: boolean;
game: Game; game: Game;
onClose: () => void; onClose: () => void;
selectGameExecutable: () => Promise<string | null>;
} }
export function GameOptionsModal({ export function GameOptionsModal({
visible, visible,
game, game,
onClose, onClose,
selectGameExecutable,
}: GameOptionsModalProps) { }: GameOptionsModalProps) {
const { t } = useTranslation("game_details"); const { t } = useTranslation("game_details");
const { updateGame, openRepacksModal } = useContext(gameDetailsContext); const { updateGame, openRepacksModal, selectGameExecutable } =
useContext(gameDetailsContext);
const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showRemoveGameModal, setShowRemoveGameModal] = useState(false); const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
const { removeGameInstaller, removeGameFromLibrary, isGameDeleting } = const {
useDownload(); removeGameInstaller,
removeGameFromLibrary,
isGameDeleting,
cancelDownload,
} = useDownload();
const deleting = game ? isGameDeleting(game?.id) : false; const deleting = isGameDeleting(game.id);
const { lastPacket } = useDownload(); const { lastPacket } = useDownload();
const isGameDownloading = const isGameDownloading =
game?.status === "active" && lastPacket?.game.id === game?.id; game.status === "active" && lastPacket?.game.id === game.id;
const handleRemoveGameFromLibrary = async () => { const handleRemoveGameFromLibrary = async () => {
if (isGameDownloading) {
await cancelDownload(game.id);
}
await removeGameFromLibrary(game.id); await removeGameFromLibrary(game.id);
updateGame(); updateGame();
onClose(); onClose();
}; };
const handleChangeExecutableLocation = async () => { const handleChangeExecutableLocation = async () => {
const location = await selectGameExecutable(); const path = await selectGameExecutable();
if (location) { if (path) {
await window.electron.updateExecutablePath(game.id, location); await window.electron.updateExecutablePath(game.id, path);
updateGame(); updateGame();
} }
}; };
@ -73,54 +79,34 @@ export function GameOptionsModal({
return ( return (
<> <>
<DeleteGameModal
visible={showDeleteModal}
onClose={() => setShowDeleteModal(false)}
deleteGame={handleDeleteGame}
/>
<RemoveGameFromLibraryModal
visible={showRemoveGameModal}
onClose={() => setShowRemoveGameModal(false)}
removeGameFromLibrary={handleRemoveGameFromLibrary}
game={game}
/>
<Modal <Modal
visible={visible} visible={visible}
title={game.title} title={game.title}
onClose={onClose} onClose={onClose}
large={true} large={true}
> >
<DeleteGameModal
visible={showDeleteModal}
onClose={() => setShowDeleteModal(false)}
deleteGame={handleDeleteGame}
/>
<RemoveGameFromLibraryModal
visible={showRemoveGameModal}
onClose={() => setShowRemoveGameModal(false)}
removeGameFromLibrary={handleRemoveGameFromLibrary}
game={game}
/>
<div className={styles.optionsContainer}> <div className={styles.optionsContainer}>
<div className={styles.gameOptionRow}> <div className={styles.gameOptionHeader}>
<Button <h2>{t("executable_section_title")}</h2>
onClick={openRepacksModal} <h4 className={styles.gameOptionHeaderDescription}>
theme="outline" {t("executable_section_description")}
disabled={deleting} </h4>
>
{t("open_download_options")}
</Button>
<Button
onClick={handleOpenDownloadFolder}
style={{ alignSelf: "flex-end" }}
theme="outline"
disabled={deleting || !game.downloadPath}
>
{t("open_download_location")}
</Button>
<Button
onClick={handleCreateShortcut}
style={{ alignSelf: "flex-end" }}
theme="outline"
disabled={deleting || !game.executablePath}
>
{t("create_shortcut")}
</Button>
</div> </div>
<div className={styles.gameOptionRow}> <div className={styles.gameOptionRow}>
<TextField <TextField
label="Caminho do executável"
value={game.executablePath || ""} value={game.executablePath || ""}
readOnly readOnly
theme="dark" theme="dark"
@ -130,43 +116,74 @@ export function GameOptionsModal({
<Button <Button
type="button" type="button"
theme="outline" theme="outline"
style={{ alignSelf: "flex-end" }}
onClick={handleChangeExecutableLocation} onClick={handleChangeExecutableLocation}
> >
{t("select_executable")} {t("select_executable")}
</Button> </Button>
<Button </div>
type="button"
theme="outline" {game.executablePath && (
style={{ alignSelf: "flex-end" }} <div className={styles.gameOptionRow}>
onClick={handleOpenGameExecutablePath} <Button
disabled={!game.executablePath} type="button"
> theme="outline"
{t("open_folder")} onClick={handleOpenGameExecutablePath}
</Button> >
{t("open_folder")}
</Button>
<Button onClick={handleCreateShortcut} theme="outline">
{t("create_shortcut")}
</Button>
</div>
)}
<div className={styles.gameOptionHeader}>
<h2>{t("downloads_secion_title")}</h2>
<h4 className={styles.gameOptionHeaderDescription}>
{t("downloads_section_description")}
</h4>
</div> </div>
<div className={styles.gameOptionRow}> <div className={styles.gameOptionRow}>
<Button
onClick={openRepacksModal}
theme="outline"
disabled={deleting || isGameDownloading}
>
{t("open_download_options")}
</Button>
{game.downloadPath && (
<Button
onClick={handleOpenDownloadFolder}
theme="outline"
disabled={deleting}
>
{t("open_download_location")}
</Button>
)}
</div>
<div className={styles.gameOptionHeader}>
<h2>{t("danger_zone_section_title")}</h2>
<h4 className={styles.gameOptionHeaderDescription}>
{t("danger_zone_section_description")}
</h4>
</div>
<div className={styles.gameOptionRow}>
<Button
onClick={() => setShowRemoveGameModal(true)}
theme="danger"
disabled={deleting}
>
{t("remove_from_library")}
</Button>
<Button <Button
onClick={() => { onClick={() => {
setShowDeleteModal(true); setShowDeleteModal(true);
}} }}
style={{ alignSelf: "flex-end" }} theme="danger"
theme="outline"
disabled={isGameDownloading || deleting || !game.downloadPath} disabled={isGameDownloading || deleting || !game.downloadPath}
> >
<TrashIcon />
{t("remove_files")} {t("remove_files")}
</Button> </Button>
<Button
onClick={() => setShowRemoveGameModal(true)}
style={{ alignSelf: "flex-end" }}
theme="outline"
disabled={deleting}
>
<NoEntryIcon />
{t("remove_from_library")}
</Button>
</div> </div>
</div> </div>
</Modal> </Modal>