diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 5cedf35d..c86a1f94 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -103,7 +103,9 @@ "previous_screenshot": "Previous screenshot", "next_screenshot": "Next screenshot", "screenshot": "Screenshot {{number}}", - "open_screenshot": "Open screenshot {{number}}" + "open_screenshot": "Open screenshot {{number}}", + "download_settings": "Download settings", + "downloader": "Downloader" }, "activation": { "title": "Activate Hydra", @@ -144,7 +146,7 @@ "telemetry": "Telemetry", "telemetry_description": "Enable anonymous usage statistics", "real_debrid_api_token_label": "Real-Debrid API token", - "quit_app_instead_hiding": "Quit Hydra instead of minimizing to tray", + "quit_app_instead_hiding": "Don't hide Hydra when closing", "launch_with_system": "Launch Hydra on system start-up", "general": "General", "behavior": "Behavior", diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index d1427a38..5bacf4c2 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -83,7 +83,7 @@ "playing_now": "Jogando agora", "change": "Mudar", "repacks_modal_description": "Escolha o repack do jogo que deseja baixar", - "select_folder_hint": "Para trocar a pasta padrão, acesse a <0>Tela de Configurações0>", + "select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Configurações0>", "download_now": "Iniciar download", "installation_instructions": "Instruções de Instalação", "installation_instructions_description": "Passos adicionais são necessários para instalar esse jogo", @@ -99,7 +99,9 @@ "previous_screenshot": "Captura de tela anterior", "next_screenshot": "Próxima captura de tela", "screenshot": "Captura de tela {{number}}", - "open_screenshot": "Ver captura de tela {{number}}" + "open_screenshot": "Ver captura de tela {{number}}", + "download_settings": "Ajustes do download", + "downloader": "Downloader" }, "activation": { "title": "Ativação", @@ -140,8 +142,8 @@ "telemetry": "Telemetria", "telemetry_description": "Habilitar estatísticas de uso anônimas", "real_debrid_api_token_label": "Token de API do Real-Debrid", - "quit_app_instead_hiding": "Fechar o aplicativo em vez de minimizá-lo", - "launch_with_system": "Iniciar aplicativo na inicialização do sistema", + "quit_app_instead_hiding": "Não manter o Hydra em segundo plano ao fechar", + "launch_with_system": "Iniciar o Hydra junto com o sistema", "general": "Geral", "behavior": "Comportamento", "real_debrid_api_token": "Token de API", diff --git a/src/main/events/library/delete-game-folder.ts b/src/main/events/library/delete-game-folder.ts index adfafefb..1e98e6fe 100644 --- a/src/main/events/library/delete-game-folder.ts +++ b/src/main/events/library/delete-game-folder.ts @@ -1,6 +1,8 @@ import path from "node:path"; import fs from "node:fs"; +import { In } from "typeorm"; + import { gameRepository } from "@main/repository"; import { getDownloadsPath } from "../helpers/get-downloads-path"; @@ -14,7 +16,7 @@ const deleteGameFolder = async ( const game = await gameRepository.findOne({ where: { id: gameId, - status: "removed", + status: In(["removed", "complete"]), isDeleted: false, }, }); diff --git a/src/renderer/src/hooks/use-download.ts b/src/renderer/src/hooks/use-download.ts index 05a44afd..6505ec66 100644 --- a/src/renderer/src/hooks/use-download.ts +++ b/src/renderer/src/hooks/use-download.ts @@ -41,12 +41,11 @@ export function useDownload() { return updateLibrary(); }; - const cancelDownload = (gameId: number) => - window.electron.cancelGameDownload(gameId).then(() => { - dispatch(clearDownload()); - updateLibrary(); - deleteGame(gameId); - }); + const cancelDownload = async (gameId: number) => { + await window.electron.cancelGameDownload(gameId); + dispatch(clearDownload()); + updateLibrary(); + }; const removeGameFromLibrary = (gameId: number) => window.electron.removeGameFromLibrary(gameId).then(() => { @@ -67,18 +66,15 @@ export function useDownload() { } }; - const deleteGame = (gameId: number) => - window.electron - .cancelGameDownload(gameId) - .then(() => { - dispatch(setGameDeleting(gameId)); - return window.electron.deleteGameFolder(gameId); - }) - .catch(() => {}) - .finally(() => { - updateLibrary(); - dispatch(removeGameFromDeleting(gameId)); - }); + const deleteGame = async (gameId: number) => { + dispatch(setGameDeleting(gameId)); + + try { + await window.electron.deleteGameFolder(gameId); + } finally { + dispatch(removeGameFromDeleting(gameId)); + } + }; const isGameDeleting = (gameId: number) => { return gamesWithDeletionInProgress.includes(gameId); diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index 2303c062..a457592e 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -40,7 +40,6 @@ i18n }) .then(() => { window.electron.updateUserPreferences({ language: i18n.language }); - i18n.changeLanguage("pt-BR"); }); ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/src/renderer/src/pages/downloads/delete-modal.css.ts b/src/renderer/src/pages/downloads/delete-modal.css.ts deleted file mode 100644 index ef0ba179..00000000 --- a/src/renderer/src/pages/downloads/delete-modal.css.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SPACING_UNIT } from "../../theme.css"; -import { style } from "@vanilla-extract/css"; - -export const deleteActionsButtonsCtn = style({ - display: "flex", - width: "100%", - justifyContent: "end", - alignItems: "center", - gap: `${SPACING_UNIT}px`, -}); diff --git a/src/renderer/src/pages/downloads/delete-modal.tsx b/src/renderer/src/pages/downloads/delete-modal.tsx deleted file mode 100644 index 953d94cd..00000000 --- a/src/renderer/src/pages/downloads/delete-modal.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { Button, Modal } from "@renderer/components"; - -import * as styles from "./delete-modal.css"; - -interface DeleteModalProps { - visible: boolean; - onClose: () => void; - deleteGame: () => void; -} - -export function DeleteModal({ - onClose, - visible, - deleteGame, -}: DeleteModalProps) { - const { t } = useTranslation("downloads"); - - const handleDeleteGame = () => { - deleteGame(); - onClose(); - }; - - return ( - - - - {t("delete")} - - - - {t("cancel")} - - - - ); -} diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 74db0d52..028d3318 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -7,13 +7,13 @@ import { formatDownloadProgress, steamUrlBuilder, } from "@renderer/helpers"; -import { useDownload, useLibrary } from "@renderer/hooks"; +import { useAppSelector, useDownload, useLibrary } from "@renderer/hooks"; import type { Game } from "@types"; import { useEffect, useMemo, useRef, useState } from "react"; import { BinaryNotFoundModal } from "../shared-modals/binary-not-found-modal"; import * as styles from "./downloads.css"; -import { DeleteModal } from "./delete-modal"; +import { DeleteGameModal } from "./delete-game-modal"; import { Downloader, formatBytes } from "@shared"; import { DOWNLOADER_NAME } from "@renderer/constants"; @@ -22,6 +22,10 @@ export function Downloads() { const { t } = useTranslation("downloads"); + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const navigate = useNavigate(); const gameToBeDeleted = useRef(null); @@ -171,7 +175,14 @@ export function Downloads() { if (game.status === "paused") { return ( <> - resumeDownload(game.id)} theme="outline"> + resumeDownload(game.id)} + theme="outline" + disabled={ + game.downloader === Downloader.RealDebrid && + !userPreferences?.realDebridApiToken + } + > {t("resume")} cancelDownload(game.id)} theme="outline"> @@ -212,9 +223,10 @@ export function Downloads() { ); }; - const handleDeleteGame = () => { + const handleDeleteGame = async () => { if (gameToBeDeleted.current) { - deleteGame(gameToBeDeleted.current).then(updateLibrary); + await deleteGame(gameToBeDeleted.current); + await removeGameFromLibrary(gameToBeDeleted.current); } }; @@ -224,7 +236,8 @@ export function Downloads() { visible={showBinaryNotFoundModal} onClose={() => setShowBinaryNotFoundModal(false)} /> - setShowDeleteModal(false)} deleteGame={handleDeleteGame} 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 a2467b4a..0aaec621 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 @@ -3,12 +3,13 @@ import { NoEntryIcon, PlusCircleIcon } from "@primer/octicons-react"; import { BinaryNotFoundModal } from "../../shared-modals/binary-not-found-modal"; import { Button } from "@renderer/components"; -import { useDownload, useLibrary } from "@renderer/hooks"; +import { useAppSelector, 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 { Downloader } from "@shared"; export function HeroPanelActions() { const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] = @@ -33,12 +34,15 @@ export function HeroPanelActions() { updateGame, } = useContext(gameDetailsContext); + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const { updateLibrary } = useLibrary(); const { t } = useTranslation("game_details"); const getDownloadsPath = async () => { - const userPreferences = await window.electron.getUserPreferences(); if (userPreferences?.downloadsPath) return userPreferences.downloadsPath; return window.electron.getDefaultDownloadsPath(); }; @@ -202,6 +206,10 @@ export function HeroPanelActions() { onClick={() => resumeDownload(game.id).then(updateGame)} theme="outline" className={styles.heroPanelAction} + disabled={ + game.downloader === Downloader.RealDebrid && + !userPreferences?.realDebridApiToken + } > {t("resume")} diff --git a/src/renderer/src/pages/game-details/modals/index.ts b/src/renderer/src/pages/game-details/modals/index.ts index 7934029b..d04fd07f 100644 --- a/src/renderer/src/pages/game-details/modals/index.ts +++ b/src/renderer/src/pages/game-details/modals/index.ts @@ -1,3 +1,3 @@ export * from "./installation-guides"; export * from "./repacks-modal"; -export * from "./select-folder-modal"; +export * from "./download-settings-modal"; diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 8b52721e..5b415172 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -8,7 +8,7 @@ import * as styles from "./repacks-modal.css"; import { SPACING_UNIT } from "../../../theme.css"; import { format } from "date-fns"; -import { SelectFolderModal } from "./select-folder-modal"; +import { DownloadSettingsModal } from "./download-settings-modal"; import { gameDetailsContext } from "../game-details.context"; import { Downloader } from "@shared"; @@ -61,7 +61,7 @@ export function RepacksModal({ return ( <> - setShowSelectFolderModal(false)} startDownload={startDownload} diff --git a/src/renderer/src/pages/game-details/modals/select-folder-modal.css.tsx b/src/renderer/src/pages/game-details/modals/select-folder-modal.css.tsx deleted file mode 100644 index 65ca2f19..00000000 --- a/src/renderer/src/pages/game-details/modals/select-folder-modal.css.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { style } from "@vanilla-extract/css"; -import { SPACING_UNIT, vars } from "../../../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}px`, -}); - -export const hintText = style({ - fontSize: "12px", - color: vars.color.body, -}); - -export const downloaders = style({ - display: "flex", - gap: `${SPACING_UNIT}px`, -}); - -export const downloaderOption = style({ - flex: "1", - position: "relative", -}); - -export const downloaderIcon = style({ - position: "absolute", - left: `${SPACING_UNIT * 2}px`, -}); diff --git a/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx b/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx deleted file mode 100644 index bd04eff8..00000000 --- a/src/renderer/src/pages/game-details/modals/select-folder-modal.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { useEffect, useState } from "react"; -import { Trans, useTranslation } from "react-i18next"; - -import { DiskSpace } from "check-disk-space"; -import * as styles from "./select-folder-modal.css"; -import { Button, Link, Modal, TextField } from "@renderer/components"; -import { CheckCircleFillIcon, DownloadIcon } from "@primer/octicons-react"; -import { Downloader, formatBytes } from "@shared"; - -import type { GameRepack } from "@types"; -import { SPACING_UNIT } from "@renderer/theme.css"; -import { DOWNLOADER_NAME } from "@renderer/constants"; - -export interface SelectFolderModalProps { - visible: boolean; - onClose: () => void; - startDownload: ( - repack: GameRepack, - downloader: Downloader, - downloadPath: string - ) => Promise; - repack: GameRepack | null; -} - -const downloaders = [Downloader.Torrent, Downloader.RealDebrid]; - -export function SelectFolderModal({ - visible, - onClose, - startDownload, - repack, -}: SelectFolderModalProps) { - const { t } = useTranslation("game_details"); - - const [diskFreeSpace, setDiskFreeSpace] = useState(null); - const [selectedPath, setSelectedPath] = useState(""); - const [downloadStarting, setDownloadStarting] = useState(false); - const [selectedDownloader, setSelectedDownloader] = useState( - Downloader.Torrent - ); - - useEffect(() => { - if (visible) { - getDiskFreeSpace(selectedPath); - } - }, [visible, selectedPath]); - - useEffect(() => { - Promise.all([ - window.electron.getDefaultDownloadsPath(), - window.electron.getUserPreferences(), - ]).then(([path, userPreferences]) => { - setSelectedPath(userPreferences?.downloadsPath || path); - - if (userPreferences?.realDebridApiToken) { - setSelectedDownloader(Downloader.RealDebrid); - } - }); - }, []); - - const getDiskFreeSpace = (path: string) => { - window.electron.getDiskFreeSpace(path).then((result) => { - setDiskFreeSpace(result); - }); - }; - - const handleChooseDownloadsPath = async () => { - const { filePaths } = await window.electron.showOpenDialog({ - defaultPath: selectedPath, - properties: ["openDirectory"], - }); - - if (filePaths && filePaths.length > 0) { - const path = filePaths[0]; - setSelectedPath(path); - } - }; - - const handleStartClick = () => { - if (repack) { - setDownloadStarting(true); - - startDownload(repack, selectedDownloader, selectedPath).finally(() => { - setDownloadStarting(false); - onClose(); - }); - } - }; - - return ( - - - - - Method - - - - {downloaders.map((downloader) => ( - setSelectedDownloader(downloader)} - > - {selectedDownloader === downloader && ( - - )} - {DOWNLOADER_NAME[downloader]} - - ))} - - - - - - - - - {t("change")} - - - - - - - - - - - - - {t("download_now")} - - - - ); -} diff --git a/src/renderer/src/pages/settings/settings-behavior.tsx b/src/renderer/src/pages/settings/settings-behavior.tsx index a8b74d35..4f8245a6 100644 --- a/src/renderer/src/pages/settings/settings-behavior.tsx +++ b/src/renderer/src/pages/settings/settings-behavior.tsx @@ -4,16 +4,19 @@ import { useTranslation } from "react-i18next"; import type { UserPreferences } from "@types"; import { CheckboxField } from "@renderer/components"; +import { useAppSelector } from "@renderer/hooks"; export interface SettingsBehaviorProps { - userPreferences: UserPreferences | null; updateUserPreferences: (values: Partial) => void; } export function SettingsBehavior({ updateUserPreferences, - userPreferences, }: SettingsBehaviorProps) { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const [form, setForm] = useState({ preferQuitInsteadOfHiding: false, runAtStartup: false, diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index cfd4e0e1..8e2ee6c5 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -5,16 +5,19 @@ import { useTranslation } from "react-i18next"; import * as styles from "./settings-general.css"; import type { UserPreferences } from "@types"; +import { useAppSelector } from "@renderer/hooks"; export interface SettingsGeneralProps { - userPreferences: UserPreferences | null; updateUserPreferences: (values: Partial) => void; } export function SettingsGeneral({ - userPreferences, updateUserPreferences, }: SettingsGeneralProps) { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const [form, setForm] = useState({ downloadsPath: "", downloadNotificationsEnabled: false, diff --git a/src/renderer/src/pages/settings/settings-real-debrid.tsx b/src/renderer/src/pages/settings/settings-real-debrid.tsx index 0285663d..41d6295c 100644 --- a/src/renderer/src/pages/settings/settings-real-debrid.tsx +++ b/src/renderer/src/pages/settings/settings-real-debrid.tsx @@ -5,19 +5,21 @@ import { Button, CheckboxField, Link, TextField } from "@renderer/components"; import * as styles from "./settings-real-debrid.css"; import type { UserPreferences } from "@types"; import { SPACING_UNIT } from "@renderer/theme.css"; -import { useToast } from "@renderer/hooks"; +import { useAppSelector, useToast } from "@renderer/hooks"; const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken"; export interface SettingsRealDebridProps { - userPreferences: UserPreferences | null; updateUserPreferences: (values: Partial) => void; } export function SettingsRealDebrid({ - userPreferences, updateUserPreferences, }: SettingsRealDebridProps) { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const [isLoading, setIsLoading] = useState(false); const [form, setForm] = useState({ useRealDebrid: false, diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dd790275..7aa4e845 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Button } from "@renderer/components"; import * as styles from "./settings.css"; @@ -7,56 +7,42 @@ import { UserPreferences } from "@types"; import { SettingsRealDebrid } from "./settings-real-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; +import { useAppDispatch } from "@renderer/hooks"; +import { setUserPreferences } from "@renderer/features"; export function Settings() { - const [userPreferences, setUserPreferences] = - useState(null); - const { t } = useTranslation("settings"); + const dispatch = useAppDispatch(); + const categories = [t("general"), t("behavior"), "Real-Debrid"]; const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); - useEffect(() => { - window.electron.getUserPreferences().then((userPreferences) => { - setUserPreferences(userPreferences); - }); - }, []); - const handleUpdateUserPreferences = async ( values: Partial ) => { await window.electron.updateUserPreferences(values); window.electron.getUserPreferences().then((userPreferences) => { - setUserPreferences(userPreferences); + dispatch(setUserPreferences(userPreferences)); }); }; const renderCategory = () => { if (currentCategoryIndex === 0) { return ( - + ); } if (currentCategoryIndex === 1) { return ( - + ); } return ( - + ); };
- - - -