mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: add a modal to select game installation folder (pt and en translation)
This commit is contained in:
parent
d9056ff0d6
commit
cacc4ca57a
@ -20,7 +20,7 @@ const linuxPkgConfig = {
|
||||
icon: "images/icon.png",
|
||||
genericName: "Games Launcher",
|
||||
name: "hydra-launcher",
|
||||
productName: "Hydra"
|
||||
productName: "Hydra",
|
||||
};
|
||||
|
||||
const config: ForgeConfig = {
|
||||
@ -50,10 +50,10 @@ const config: ForgeConfig = {
|
||||
}),
|
||||
new MakerZIP({}, ["darwin", "linux"]),
|
||||
new MakerRpm({
|
||||
options: linuxPkgConfig
|
||||
options: linuxPkgConfig,
|
||||
}),
|
||||
new MakerDeb({
|
||||
options: linuxPkgConfig
|
||||
options: linuxPkgConfig,
|
||||
}),
|
||||
],
|
||||
publishers: [
|
||||
|
@ -77,7 +77,13 @@
|
||||
"play": "Play",
|
||||
"deleting": "Deleting installer…",
|
||||
"close": "Close",
|
||||
"playing_now": "Playing now"
|
||||
"playing_now": "Playing now",
|
||||
"change": "Change",
|
||||
"select_folder_description": "Select the folder where the game will be installed",
|
||||
"downloads_path": "Downloads path",
|
||||
"select_folder_hint": "To change the default folder, access the",
|
||||
"hydra_settings": "Hydra settings",
|
||||
"download_now": "Download now"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Activate Hydra",
|
||||
|
@ -73,7 +73,13 @@
|
||||
"not_played_yet": "Você ainda não jogou {{title}}",
|
||||
"close": "Fechar",
|
||||
"deleting": "Excluindo instalador…",
|
||||
"playing_now": "Jogando agora"
|
||||
"playing_now": "Jogando agora",
|
||||
"change": "Mudar",
|
||||
"select_folder_description": "Selecione a pasta em que o jogo será baixado",
|
||||
"downloads_path": "Diretório do download",
|
||||
"select_folder_hint": "Para trocar a pasta padrão, acesse as ",
|
||||
"hydra_settings": "Configurações do Hydra",
|
||||
"download_now": "Baixe agora"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
|
@ -10,7 +10,7 @@ const addGameToLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectID: string,
|
||||
title: string,
|
||||
gameShop: GameShop,
|
||||
gameShop: GameShop
|
||||
) => {
|
||||
const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID));
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { GameStatus } from "@main/constants";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { getImageBase64 } from "@main/helpers";
|
||||
import { In } from "typeorm";
|
||||
|
||||
@ -14,7 +13,8 @@ const startGameDownload = async (
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
gameShop: GameShop
|
||||
gameShop: GameShop,
|
||||
downloadPath: string
|
||||
) => {
|
||||
const [game, repack] = await Promise.all([
|
||||
gameRepository.findOne({
|
||||
@ -37,7 +37,7 @@ const startGameDownload = async (
|
||||
|
||||
writePipe.write({ action: "pause" });
|
||||
|
||||
const downloadsPath = game?.downloadPath ?? (await getDownloadsPath());
|
||||
const downloadsPath = game?.downloadPath ?? downloadPath;
|
||||
|
||||
await gameRepository.update(
|
||||
{
|
||||
|
@ -15,8 +15,17 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
) => ipcRenderer.invoke("startGameDownload", repackId, objectID, title, shop),
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"startGameDownload",
|
||||
repackId,
|
||||
objectID,
|
||||
title,
|
||||
shop,
|
||||
downloadPath
|
||||
),
|
||||
cancelGameDownload: (gameId: number) =>
|
||||
ipcRenderer.invoke("cancelGameDownload", gameId),
|
||||
pauseGameDownload: (gameId: number) =>
|
||||
|
File diff suppressed because one or more lines are too long
3
src/renderer/declaration.d.ts
vendored
3
src/renderer/declaration.d.ts
vendored
@ -22,7 +22,8 @@ declare global {
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) => Promise<Game>;
|
||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||
|
@ -28,10 +28,11 @@ export function useDownload() {
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) =>
|
||||
window.electron
|
||||
.startGameDownload(repackId, objectID, title, shop)
|
||||
.startGameDownload(repackId, objectID, title, shop, downloadPath)
|
||||
.then((game) => {
|
||||
dispatch(clearDownload());
|
||||
updateLibrary();
|
||||
|
@ -138,12 +138,16 @@ export function GameDetails() {
|
||||
};
|
||||
}, [game?.id, isGamePlaying, getGame]);
|
||||
|
||||
const handleStartDownload = async (repackId: number) => {
|
||||
const handleStartDownload = async (
|
||||
repackId: number,
|
||||
downloadPath: string
|
||||
) => {
|
||||
return startDownload(
|
||||
repackId,
|
||||
gameDetails.objectID,
|
||||
gameDetails.name,
|
||||
shop as GameShop
|
||||
shop as GameShop,
|
||||
downloadPath
|
||||
).then(() => {
|
||||
getGame();
|
||||
setShowRepacksModal(false);
|
||||
|
@ -10,11 +10,12 @@ 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 { SelectFolderModal } from "./select-folder-modal";
|
||||
|
||||
export interface RepacksModalProps {
|
||||
visible: boolean;
|
||||
gameDetails: ShopDetails;
|
||||
startDownload: (repackId: number) => Promise<void>;
|
||||
startDownload: (repackId: number, downloadPath: string) => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
@ -24,9 +25,10 @@ export function RepacksModal({
|
||||
startDownload,
|
||||
onClose,
|
||||
}: RepacksModalProps) {
|
||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||
const [openSelectFolderModal, setOpenSelectFolderModal] = useState(false);
|
||||
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace>(null);
|
||||
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
|
||||
const [repack, setRepack] = useState<GameRepack>(null);
|
||||
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
@ -45,10 +47,8 @@ export function RepacksModal({
|
||||
}, [visible]);
|
||||
|
||||
const handleRepackClick = (repack: GameRepack) => {
|
||||
setDownloadStarting(true);
|
||||
startDownload(repack.id).finally(() => {
|
||||
setDownloadStarting(false);
|
||||
});
|
||||
setRepack(repack);
|
||||
setOpenSelectFolderModal(true);
|
||||
};
|
||||
|
||||
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
@ -70,6 +70,13 @@ export function RepacksModal({
|
||||
})}
|
||||
onClose={onClose}
|
||||
>
|
||||
<SelectFolderModal
|
||||
visible={openSelectFolderModal}
|
||||
onClose={() => setOpenSelectFolderModal(false)}
|
||||
gameDetails={gameDetails}
|
||||
startDownload={startDownload}
|
||||
repack={repack}
|
||||
/>
|
||||
<div style={{ marginBottom: `${SPACING_UNIT * 2}px` }}>
|
||||
<TextField placeholder={t("filter")} onChange={handleFilter} />
|
||||
</div>
|
||||
@ -80,7 +87,6 @@ export function RepacksModal({
|
||||
key={repack.id}
|
||||
theme="dark"
|
||||
onClick={() => handleRepackClick(repack)}
|
||||
disabled={downloadStarting}
|
||||
className={styles.repackButton}
|
||||
>
|
||||
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
||||
|
19
src/renderer/pages/game-details/select-folder-modal.css.tsx
Normal file
19
src/renderer/pages/game-details/select-folder-modal.css.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
|
||||
export const container = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const downloadsPathField = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
||||
export const hintText = style({
|
||||
fontSize: 12,
|
||||
color: vars.color.bodyText,
|
||||
});
|
103
src/renderer/pages/game-details/select-folder-modal.tsx
Normal file
103
src/renderer/pages/game-details/select-folder-modal.tsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { Button, Modal, TextField } from "@renderer/components";
|
||||
import { GameRepack, ShopDetails } from "@types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import * as styles from "./select-folder-modal.css";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export interface SelectFolderModalProps {
|
||||
visible: boolean;
|
||||
gameDetails: ShopDetails;
|
||||
onClose: () => void;
|
||||
startDownload: (repackId: number, downloadPath: string) => Promise<void>;
|
||||
repack: GameRepack;
|
||||
}
|
||||
|
||||
export function SelectFolderModal({
|
||||
visible,
|
||||
gameDetails,
|
||||
onClose,
|
||||
startDownload,
|
||||
repack,
|
||||
}: SelectFolderModalProps) {
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
const [selectedPath, setSelectedPath] = useState({
|
||||
downloadsPath: "",
|
||||
});
|
||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
window.electron.getDefaultDownloadsPath(),
|
||||
window.electron.getUserPreferences(),
|
||||
]).then(([path, userPreferences]) => {
|
||||
setSelectedPath({
|
||||
downloadsPath: userPreferences?.downloadsPath || path,
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
defaultPath: selectedPath.downloadsPath,
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
setSelectedPath({ downloadsPath: path });
|
||||
}
|
||||
};
|
||||
|
||||
const handleStartClick = () => {
|
||||
setDownloadStarting(true);
|
||||
startDownload(repack.id, selectedPath.downloadsPath).finally(() => {
|
||||
setDownloadStarting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={`${gameDetails.name} Installation folder`}
|
||||
description={t("select_folder_description")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={selectedPath.downloadsPath}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
<p className={styles.hintText}>
|
||||
{t("select_folder_hint")}{" "}
|
||||
<Link
|
||||
to="/settings"
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "#C0C1C7",
|
||||
}}
|
||||
>
|
||||
{t("hydra_settings")}
|
||||
</Link>
|
||||
</p>
|
||||
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
||||
{t("download_now")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user