mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	chore: disabling resuming real debrid downloads when real debrid is not set
This commit is contained in:
		
							parent
							
								
									4d61b8586d
								
							
						
					
					
						commit
						a34a774fb7
					
				
					 17 changed files with 83 additions and 311 deletions
				
			
		|  | @ -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", | ||||
|  |  | |||
|  | @ -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ções</0>", | ||||
|     "select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Configurações</0>", | ||||
|     "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", | ||||
|  |  | |||
|  | @ -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, | ||||
|     }, | ||||
|   }); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ i18n | |||
|   }) | ||||
|   .then(() => { | ||||
|     window.electron.updateUserPreferences({ language: i18n.language }); | ||||
|     i18n.changeLanguage("pt-BR"); | ||||
|   }); | ||||
| 
 | ||||
| ReactDOM.createRoot(document.getElementById("root")!).render( | ||||
|  |  | |||
|  | @ -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`, | ||||
| }); | ||||
|  | @ -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 ( | ||||
|     <Modal | ||||
|       visible={visible} | ||||
|       title={t("delete_modal_title")} | ||||
|       description={t("delete_modal_description")} | ||||
|       onClose={onClose} | ||||
|     > | ||||
|       <div className={styles.deleteActionsButtonsCtn}> | ||||
|         <Button onClick={handleDeleteGame} theme="outline"> | ||||
|           {t("delete")} | ||||
|         </Button> | ||||
| 
 | ||||
|         <Button onClick={onClose} theme="primary"> | ||||
|           {t("cancel")} | ||||
|         </Button> | ||||
|       </div> | ||||
|     </Modal> | ||||
|   ); | ||||
| } | ||||
|  | @ -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<number | null>(null); | ||||
|  | @ -171,7 +175,14 @@ export function Downloads() { | |||
|     if (game.status === "paused") { | ||||
|       return ( | ||||
|         <> | ||||
|           <Button onClick={() => resumeDownload(game.id)} theme="outline"> | ||||
|           <Button | ||||
|             onClick={() => resumeDownload(game.id)} | ||||
|             theme="outline" | ||||
|             disabled={ | ||||
|               game.downloader === Downloader.RealDebrid && | ||||
|               !userPreferences?.realDebridApiToken | ||||
|             } | ||||
|           > | ||||
|             {t("resume")} | ||||
|           </Button> | ||||
|           <Button onClick={() => 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)} | ||||
|       /> | ||||
|       <DeleteModal | ||||
| 
 | ||||
|       <DeleteGameModal | ||||
|         visible={showDeleteModal} | ||||
|         onClose={() => setShowDeleteModal(false)} | ||||
|         deleteGame={handleDeleteGame} | ||||
|  |  | |||
|  | @ -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")} | ||||
|         </Button> | ||||
|  |  | |||
|  | @ -1,3 +1,3 @@ | |||
| export * from "./installation-guides"; | ||||
| export * from "./repacks-modal"; | ||||
| export * from "./select-folder-modal"; | ||||
| export * from "./download-settings-modal"; | ||||
|  |  | |||
|  | @ -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 ( | ||||
|     <> | ||||
|       <SelectFolderModal | ||||
|       <DownloadSettingsModal | ||||
|         visible={showSelectFolderModal} | ||||
|         onClose={() => setShowSelectFolderModal(false)} | ||||
|         startDownload={startDownload} | ||||
|  |  | |||
|  | @ -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`, | ||||
| }); | ||||
|  | @ -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<void>; | ||||
|   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<DiskSpace | null>(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 ( | ||||
|     <Modal | ||||
|       visible={visible} | ||||
|       title="Download options" | ||||
|       description={t("space_left_on_disk", { | ||||
|         space: formatBytes(diskFreeSpace?.free ?? 0), | ||||
|       })} | ||||
|       onClose={onClose} | ||||
|     > | ||||
|       <div className={styles.container}> | ||||
|         <div> | ||||
|           <span | ||||
|             style={{ | ||||
|               marginBottom: `${SPACING_UNIT}px`, | ||||
|               display: "block", | ||||
|             }} | ||||
|           > | ||||
|             Method | ||||
|           </span> | ||||
| 
 | ||||
|           <div className={styles.downloaders}> | ||||
|             {downloaders.map((downloader) => ( | ||||
|               <Button | ||||
|                 key={downloader} | ||||
|                 className={styles.downloaderOption} | ||||
|                 theme={ | ||||
|                   selectedDownloader === downloader ? "primary" : "outline" | ||||
|                 } | ||||
|                 onClick={() => setSelectedDownloader(downloader)} | ||||
|               > | ||||
|                 {selectedDownloader === downloader && ( | ||||
|                   <CheckCircleFillIcon className={styles.downloaderIcon} /> | ||||
|                 )} | ||||
|                 {DOWNLOADER_NAME[downloader]} | ||||
|               </Button> | ||||
|             ))} | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <div> | ||||
|           <div className={styles.downloadsPathField}> | ||||
|             <TextField value={selectedPath} readOnly disabled label="Path" /> | ||||
| 
 | ||||
|             <Button | ||||
|               style={{ alignSelf: "flex-end" }} | ||||
|               theme="outline" | ||||
|               onClick={handleChooseDownloadsPath} | ||||
|               disabled={downloadStarting} | ||||
|             > | ||||
|               {t("change")} | ||||
|             </Button> | ||||
|           </div> | ||||
| 
 | ||||
|           <p className={styles.hintText}> | ||||
|             <Trans i18nKey="select_folder_hint" ns="game_details"> | ||||
|               <Link to="/settings" /> | ||||
|             </Trans> | ||||
|           </p> | ||||
|         </div> | ||||
| 
 | ||||
|         <Button onClick={handleStartClick} disabled={downloadStarting}> | ||||
|           <DownloadIcon /> | ||||
|           {t("download_now")} | ||||
|         </Button> | ||||
|       </div> | ||||
|     </Modal> | ||||
|   ); | ||||
| } | ||||
|  | @ -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<UserPreferences>) => void; | ||||
| } | ||||
| 
 | ||||
| export function SettingsBehavior({ | ||||
|   updateUserPreferences, | ||||
|   userPreferences, | ||||
| }: SettingsBehaviorProps) { | ||||
|   const userPreferences = useAppSelector( | ||||
|     (state) => state.userPreferences.value | ||||
|   ); | ||||
| 
 | ||||
|   const [form, setForm] = useState({ | ||||
|     preferQuitInsteadOfHiding: false, | ||||
|     runAtStartup: false, | ||||
|  |  | |||
|  | @ -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<UserPreferences>) => void; | ||||
| } | ||||
| 
 | ||||
| export function SettingsGeneral({ | ||||
|   userPreferences, | ||||
|   updateUserPreferences, | ||||
| }: SettingsGeneralProps) { | ||||
|   const userPreferences = useAppSelector( | ||||
|     (state) => state.userPreferences.value | ||||
|   ); | ||||
| 
 | ||||
|   const [form, setForm] = useState({ | ||||
|     downloadsPath: "", | ||||
|     downloadNotificationsEnabled: false, | ||||
|  |  | |||
|  | @ -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<UserPreferences>) => 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, | ||||
|  |  | |||
|  | @ -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<UserPreferences | null>(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<UserPreferences> | ||||
|   ) => { | ||||
|     await window.electron.updateUserPreferences(values); | ||||
|     window.electron.getUserPreferences().then((userPreferences) => { | ||||
|       setUserPreferences(userPreferences); | ||||
|       dispatch(setUserPreferences(userPreferences)); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   const renderCategory = () => { | ||||
|     if (currentCategoryIndex === 0) { | ||||
|       return ( | ||||
|         <SettingsGeneral | ||||
|           userPreferences={userPreferences} | ||||
|           updateUserPreferences={handleUpdateUserPreferences} | ||||
|         /> | ||||
|         <SettingsGeneral updateUserPreferences={handleUpdateUserPreferences} /> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (currentCategoryIndex === 1) { | ||||
|       return ( | ||||
|         <SettingsBehavior | ||||
|           userPreferences={userPreferences} | ||||
|           updateUserPreferences={handleUpdateUserPreferences} | ||||
|         /> | ||||
|         <SettingsBehavior updateUserPreferences={handleUpdateUserPreferences} /> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <SettingsRealDebrid | ||||
|         userPreferences={userPreferences} | ||||
|         updateUserPreferences={handleUpdateUserPreferences} | ||||
|       /> | ||||
|       <SettingsRealDebrid updateUserPreferences={handleUpdateUserPreferences} /> | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue