diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json
index 63682eee..3b227e5d 100644
--- a/src/locales/en/translation.json
+++ b/src/locales/en/translation.json
@@ -101,15 +101,23 @@
"open_screenshot": "Open screenshot {{number}}",
"download_settings": "Download settings",
"downloader": "Downloader",
- "select_executable": "Select executable",
+ "select_executable": "Select",
"no_executable_selected": "No executable selected",
"open_folder": "Open folder",
- "open_download_location": "Abrir local de download",
- "create_shortcut": "Create shortcut",
+ "open_download_location": "See downloaded files",
+ "create_shortcut": "Create desktop shortcut",
"remove_files": "Remove files",
"remove_from_library_title": "Are you sure?",
"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": {
"title": "Activate Hydra",
diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json
index 77121bd5..174abacd 100644
--- a/src/locales/pt/translation.json
+++ b/src/locales/pt/translation.json
@@ -98,15 +98,23 @@
"open_screenshot": "Ver captura de tela {{number}}",
"download_settings": "Ajustes do download",
"downloader": "Downloader",
- "select_executable": "Selecionar executável",
+ "select_executable": "Selecionar",
"no_executable_selected": "Nenhum executável selecionado",
"open_folder": "Abrir pasta",
- "open_download_location": "Abrir local de download",
- "create_shortcut": "Criar atalho",
+ "open_download_location": "Ver arquivos baixados",
+ "create_shortcut": "Criar atalho na área de trabalho",
"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?",
- "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": {
"title": "Ativação",
diff --git a/src/main/events/library/create-game-shortcut.ts b/src/main/events/library/create-game-shortcut.ts
index 7ce37bee..17b7e42c 100644
--- a/src/main/events/library/create-game-shortcut.ts
+++ b/src/main/events/library/create-game-shortcut.ts
@@ -13,10 +13,13 @@ const createGameShortcut = async (
if (game) {
const filePath = game.executablePath;
+
+ const options = { filePath, name: game.title };
+
return createDesktopShortcut({
- windows: { filePath },
- linux: { filePath },
- osx: { filePath },
+ windows: options,
+ linux: options,
+ osx: options,
});
}
diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts
index 2581a555..29a7a635 100644
--- a/src/main/events/library/remove-game-from-library.ts
+++ b/src/main/events/library/remove-game-from-library.ts
@@ -5,7 +5,10 @@ const removeGameFromLibrary = async (
_event: Electron.IpcMainInvokeEvent,
gameId: number
) => {
- gameRepository.update({ id: gameId }, { isDeleted: true });
+ gameRepository.update(
+ { id: gameId },
+ { isDeleted: true, executablePath: null }
+ );
};
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
diff --git a/src/renderer/src/components/button/button.css.ts b/src/renderer/src/components/button/button.css.ts
index e342b2e9..c730ecfd 100644
--- a/src/renderer/src/components/button/button.css.ts
+++ b/src/renderer/src/components/button/button.css.ts
@@ -55,4 +55,15 @@ export const button = styleVariants({
color: "#c0c1c7",
},
],
+ danger: [
+ base,
+ {
+ border: `solid 1px #a31533`,
+ backgroundColor: "transparent",
+ color: "white",
+ ":hover": {
+ backgroundColor: "#a31533",
+ },
+ },
+ ],
});
diff --git a/src/renderer/src/components/hero/hero.tsx b/src/renderer/src/components/hero/hero.tsx
index f0d52029..0cfdbea5 100644
--- a/src/renderer/src/components/hero/hero.tsx
+++ b/src/renderer/src/components/hero/hero.tsx
@@ -54,7 +54,7 @@ export function Hero() {
>
diff --git a/src/renderer/src/components/modal/modal.css.ts b/src/renderer/src/components/modal/modal.css.ts
index fcac6f4b..110f16f8 100644
--- a/src/renderer/src/components/modal/modal.css.ts
+++ b/src/renderer/src/components/modal/modal.css.ts
@@ -2,24 +2,25 @@ import { keyframes, style } from "@vanilla-extract/css";
import { recipe } from "@vanilla-extract/recipes";
import { SPACING_UNIT, vars } from "../../theme.css";
-export const fadeIn = keyframes({
- "0%": { opacity: 0 },
+export const scaleFadeIn = keyframes({
+ "0%": { opacity: "0", scale: "0.5" },
"100%": {
- opacity: 1,
+ opacity: "1",
+ scale: "1",
},
});
-export const fadeOut = keyframes({
- "0%": { opacity: 1 },
+export const scaleFadeOut = keyframes({
+ "0%": { opacity: "1", scale: "1" },
"100%": {
- opacity: 0,
+ opacity: "0",
+ scale: "0.5",
},
});
export const modal = recipe({
base: {
- animationName: fadeIn,
- animationDuration: "0.3s",
+ animation: `${scaleFadeIn} 0.2s cubic-bezier(0.33, 1, 0.68, 1) 0s 1 normal none running`,
backgroundColor: vars.color.background,
borderRadius: "4px",
maxWidth: "600px",
@@ -33,8 +34,8 @@ export const modal = recipe({
variants: {
closing: {
true: {
- animationName: fadeOut,
- opacity: 0,
+ animationName: scaleFadeOut,
+ opacity: "0",
},
},
large: {
diff --git a/src/renderer/src/pages/game-details/game-details.context.tsx b/src/renderer/src/pages/game-details/game-details.context.tsx
index 2c3fd61a..e4889275 100644
--- a/src/renderer/src/pages/game-details/game-details.context.tsx
+++ b/src/renderer/src/pages/game-details/game-details.context.tsx
@@ -3,7 +3,7 @@ import { useParams, useSearchParams } from "react-router-dom";
import { setHeaderTitle } from "@renderer/features";
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";
@@ -16,6 +16,7 @@ import {
RepacksModal,
} from "./modals";
import { Downloader } from "@shared";
+import { GameOptionsModal } from "./modals/game-options-modal";
export interface GameDetailsContext {
game: Game | null;
@@ -29,6 +30,8 @@ export interface GameDetailsContext {
gameColor: string;
setGameColor: React.Dispatch
>;
openRepacksModal: () => void;
+ openGameOptionsModal: () => void;
+ selectGameExecutable: () => Promise;
updateGame: () => Promise;
}
@@ -44,6 +47,8 @@ export const gameDetailsContext = createContext({
gameColor: "",
setGameColor: () => {},
openRepacksModal: () => {},
+ openGameOptionsModal: () => {},
+ selectGameExecutable: async () => null,
updateGame: async () => {},
});
@@ -70,6 +75,7 @@ export function GameDetailsContextProvider({
>(null);
const [isGameRunning, setisGameRunning] = useState(false);
const [showRepacksModal, setShowRepacksModal] = useState(false);
+ const [showGameOptionsModal, setShowGameOptionsModal] = useState(false);
const [searchParams] = useSearchParams();
@@ -81,6 +87,10 @@ export function GameDetailsContextProvider({
const { startDownload, lastPacket } = useDownload();
+ const userPreferences = useAppSelector(
+ (state) => state.userPreferences.value
+ );
+
const updateGame = useCallback(async () => {
return window.electron
.getGameByObjectID(objectID!)
@@ -158,6 +168,7 @@ export function GameDetailsContextProvider({
await updateGame();
setShowRepacksModal(false);
+ setShowGameOptionsModal(false);
if (
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 openGameOptionsModal = () => setShowGameOptionsModal(true);
return (
@@ -208,6 +250,16 @@ export function GameDetailsContextProvider({
onClose={() => setShowInstructionsModal(null)}
/>
+ {game && (
+ {
+ setShowGameOptionsModal(false);
+ }}
+ />
+ )}
+
{children}
>
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.css.ts b/src/renderer/src/pages/game-details/hero/hero-panel-actions.css.ts
index 48f8105b..ac9e6df1 100644
--- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.css.ts
+++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.css.ts
@@ -13,5 +13,6 @@ export const actions = style({
export const separator = style({
width: "1px",
- backgroundColor: vars.color.border,
+ backgroundColor: vars.color.muted,
+ opacity: "0.2",
});
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
index fcb7fbdd..6c35db3b 100644
--- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
+++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx
@@ -1,16 +1,14 @@
import { GearIcon, PlayIcon, PlusCircleIcon } from "@primer/octicons-react";
import { Button } from "@renderer/components";
-import { useAppSelector, useDownload, useLibrary } from "@renderer/hooks";
+import { useDownload, useLibrary } from "@renderer/hooks";
import { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import * as styles from "./hero-panel-actions.css";
import { gameDetailsContext } from "../game-details.context";
-import { GameOptionsModal } from "../modals/game-options-modal";
export function HeroPanelActions() {
const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] =
useState(false);
- const [showGameOptionsModal, setShowGameOptionsModal] = useState(false);
const { isGameDeleting } = useDownload();
@@ -21,45 +19,15 @@ export function HeroPanelActions() {
objectID,
gameTitle,
openRepacksModal,
+ openGameOptionsModal,
updateGame,
+ selectGameExecutable,
} = useContext(gameDetailsContext);
- const userPreferences = useAppSelector(
- (state) => state.userPreferences.value
- );
-
const { updateLibrary } = useLibrary();
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 () => {
setToggleLibraryGameDisabled(true);
@@ -126,53 +94,40 @@ export function HeroPanelActions() {
if (game) {
return (
- <>
- {
- setShowGameOptionsModal(false);
- }}
- selectGameExecutable={selectGameExecutable}
- />
-
-
- {isGameRunning ? (
-
- ) : (
-
- )}
-
-
-
+
+ {isGameRunning ? (
+ ) : (
+
-
- >
+ )}
+
+
+
+
+
);
}
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-playtime.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-playtime.tsx
index c32f24b0..1991d53f 100644
--- a/src/renderer/src/pages/game-details/hero/hero-panel-playtime.tsx
+++ b/src/renderer/src/pages/game-details/hero/hero-panel-playtime.tsx
@@ -15,6 +15,8 @@ export function HeroPanelPlaytime() {
const { i18n, t } = useTranslation("game_details");
+ const { progress, lastPacket } = useDownload();
+
const { formatDistance } = useDate();
useEffect(() => {
@@ -48,40 +50,33 @@ export function HeroPanelPlaytime() {
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 =
- game?.status === "active" && lastPacket?.game.id === game?.id;
+ game.status === "active" && lastPacket?.game.id === game.id;
- if (!game) return;
+ const downloadInProgressInfo = (
+
+
+ {game.status === "active"
+ ? t("download_in_progress")
+ : t("download_paused")}
+
- let downloadContent: JSX.Element | null = null;
-
- if (game.status === "active") {
- if (lastPacket?.isDownloadingMetadata && isGameDownloading) {
- downloadContent =
{t("downloading_metadata")}
;
- } else if (game.progress !== 1) {
- downloadContent = (
-
-
- Download em andamento
-
-
-
- {isGameDownloading
- ? progress
- : formatDownloadProgress(game.progress)}
-
-
- );
- }
- }
+
+ {isGameDownloading ? progress : formatDownloadProgress(game.progress)}
+
+
+ );
if (!game.lastTimePlayed) {
return (
<>
{t("not_played_yet", { title: game?.title })}
- {downloadContent}
+ {hasDownload && downloadInProgressInfo}
>
);
}
@@ -89,15 +84,9 @@ export function HeroPanelPlaytime() {
if (isGameRunning) {
return (
<>
- {downloadContent || (
-
- {t("play_time", {
- amount: formatPlayTime(),
- })}
-
- )}
-
{t("playing_now")}
+
+ {hasDownload && downloadInProgressInfo}
>
);
}
@@ -110,7 +99,9 @@ export function HeroPanelPlaytime() {
})}
- {downloadContent || (
+ {hasDownload ? (
+ downloadInProgressInfo
+ ) : (
{t("last_time_played", {
period: lastTimePlayed,
diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.css.ts b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts
index 54297b0b..5dfed5d8 100644
--- a/src/renderer/src/pages/game-details/hero/hero-panel.css.ts
+++ b/src/renderer/src/pages/game-details/hero/hero-panel.css.ts
@@ -36,6 +36,7 @@ export const downloadDetailsRow = style({
export const downloadsLink = style({
color: vars.color.body,
+ textDecoration: "underline",
});
export const progressBar = recipe({
diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts b/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts
index a6a854df..b8c538f0 100644
--- a/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts
+++ b/src/renderer/src/pages/game-details/modals/game-options-modal.css.ts
@@ -7,6 +7,17 @@ export const optionsContainer = style({
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({
display: "flex",
gap: `${SPACING_UNIT}px`,
diff --git a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx
index 145e262c..b40d93ab 100644
--- a/src/renderer/src/pages/game-details/modals/game-options-modal.tsx
+++ b/src/renderer/src/pages/game-details/modals/game-options-modal.tsx
@@ -4,7 +4,6 @@ import { Button, Modal, TextField } from "@renderer/components";
import type { Game } from "@types";
import * as styles from "./game-options-modal.css";
import { gameDetailsContext } from "../game-details.context";
-import { NoEntryIcon, TrashIcon } from "@primer/octicons-react";
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
import { useDownload } from "@renderer/hooks";
import { RemoveGameFromLibraryModal } from "./remove-from-library-modal";
@@ -13,43 +12,50 @@ export interface GameOptionsModalProps {
visible: boolean;
game: Game;
onClose: () => void;
- selectGameExecutable: () => Promise;
}
export function GameOptionsModal({
visible,
game,
onClose,
- selectGameExecutable,
}: GameOptionsModalProps) {
const { t } = useTranslation("game_details");
- const { updateGame, openRepacksModal } = useContext(gameDetailsContext);
+ const { updateGame, openRepacksModal, selectGameExecutable } =
+ useContext(gameDetailsContext);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
- const { removeGameInstaller, removeGameFromLibrary, isGameDeleting } =
- useDownload();
+ const {
+ removeGameInstaller,
+ removeGameFromLibrary,
+ isGameDeleting,
+ cancelDownload,
+ } = useDownload();
- const deleting = game ? isGameDeleting(game?.id) : false;
+ const deleting = isGameDeleting(game.id);
const { lastPacket } = useDownload();
const isGameDownloading =
- game?.status === "active" && lastPacket?.game.id === game?.id;
+ game.status === "active" && lastPacket?.game.id === game.id;
const handleRemoveGameFromLibrary = async () => {
+ if (isGameDownloading) {
+ await cancelDownload(game.id);
+ }
+
await removeGameFromLibrary(game.id);
updateGame();
onClose();
};
const handleChangeExecutableLocation = async () => {
- const location = await selectGameExecutable();
+ const path = await selectGameExecutable();
- if (location) {
- await window.electron.updateExecutablePath(game.id, location);
+ if (path) {
+ await window.electron.updateExecutablePath(game.id, path);
updateGame();
}
};
@@ -73,54 +79,34 @@ export function GameOptionsModal({
return (
<>
+ setShowDeleteModal(false)}
+ deleteGame={handleDeleteGame}
+ />
+
+ setShowRemoveGameModal(false)}
+ removeGameFromLibrary={handleRemoveGameFromLibrary}
+ game={game}
+ />
+
- setShowDeleteModal(false)}
- deleteGame={handleDeleteGame}
- />
-
- setShowRemoveGameModal(false)}
- removeGameFromLibrary={handleRemoveGameFromLibrary}
- game={game}
- />
-
-
-
-
-
+
+
{t("executable_section_title")}
+
+ {t("executable_section_description")}
+
{t("select_executable")}
-
+
+
+ {game.executablePath && (
+
+
+
+
+ )}
+
+
+
{t("downloads_secion_title")}
+
+ {t("downloads_section_description")}
+
+
+ {game.downloadPath && (
+
+ )}
+
+
+
{t("danger_zone_section_title")}
+
+ {t("danger_zone_section_description")}
+
+
+
+
-
-