mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-03-09 15:40:26 +00:00
fix: fixing add game to library
This commit is contained in:
commit
3bd8662b18
34 changed files with 555 additions and 221 deletions
|
@ -32,7 +32,7 @@ export function App({ children }: AppProps) {
|
|||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const { updateLibrary } = useLibrary();
|
||||
|
||||
const { clearDownload, addPacket } = useDownload();
|
||||
const { clearDownload, setLastPacket } = useDownload();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
|
@ -64,14 +64,14 @@ export function App({ children }: AppProps) {
|
|||
return;
|
||||
}
|
||||
|
||||
addPacket(downloadProgress);
|
||||
setLastPacket(downloadProgress);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [clearDownload, addPacket, updateLibrary]);
|
||||
}, [clearDownload, setLastPacket, updateLibrary]);
|
||||
|
||||
const handleSearch = useCallback(
|
||||
(query: string) => {
|
||||
|
|
|
@ -64,7 +64,7 @@ export function BottomPanel() {
|
|||
<small>{status}</small>
|
||||
</button>
|
||||
|
||||
<small>
|
||||
<small tabIndex={0}>
|
||||
v{version} "{VERSION_CODENAME}"
|
||||
</small>
|
||||
</footer>
|
||||
|
|
|
@ -19,6 +19,7 @@ const base = style({
|
|||
":disabled": {
|
||||
opacity: vars.opacity.disabled,
|
||||
pointerEvents: "none",
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from "./modal/modal";
|
|||
export * from "./sidebar/sidebar";
|
||||
export * from "./text-field/text-field";
|
||||
export * from "./checkbox-field/checkbox-field";
|
||||
export * from "./link/link";
|
||||
|
|
9
src/renderer/src/components/link/link.css.ts
Normal file
9
src/renderer/src/components/link/link.css.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
export const link = style({
|
||||
textDecoration: "none",
|
||||
color: "#C0C1C7",
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
});
|
33
src/renderer/src/components/link/link.tsx
Normal file
33
src/renderer/src/components/link/link.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { Link as ReactRouterDomLink, LinkProps } from "react-router-dom";
|
||||
import cn from "classnames";
|
||||
import * as styles from "./link.css";
|
||||
|
||||
export function Link({ children, to, className, ...props }: LinkProps) {
|
||||
const openExternal = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
window.electron.openExternal(to as string);
|
||||
};
|
||||
|
||||
if (typeof to === "string" && to.startsWith("http")) {
|
||||
return (
|
||||
<a
|
||||
href={to}
|
||||
className={cn(styles.link, className)}
|
||||
onClick={openExternal}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ReactRouterDomLink
|
||||
className={cn(styles.link, className)}
|
||||
to={to}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</ReactRouterDomLink>
|
||||
);
|
||||
}
|
|
@ -2,6 +2,13 @@ import { SPACING_UNIT, vars } from "../../theme.css";
|
|||
import { style } from "@vanilla-extract/css";
|
||||
import { recipe } from "@vanilla-extract/recipes";
|
||||
|
||||
export const textFieldContainer = style({
|
||||
flex: "1",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
});
|
||||
|
||||
export const textField = recipe({
|
||||
base: {
|
||||
display: "inline-flex",
|
||||
|
@ -50,9 +57,3 @@ export const textFieldInput = style({
|
|||
cursor: "text",
|
||||
},
|
||||
});
|
||||
|
||||
export const label = style({
|
||||
marginBottom: `${SPACING_UNIT}px`,
|
||||
display: "block",
|
||||
color: vars.color.bodyText,
|
||||
});
|
||||
|
|
|
@ -9,28 +9,31 @@ export interface TextFieldProps
|
|||
> {
|
||||
theme?: NonNullable<RecipeVariants<typeof styles.textField>>["theme"];
|
||||
label?: string | React.ReactNode;
|
||||
hint?: string | React.ReactNode;
|
||||
textFieldProps?: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
containerProps?: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
>;
|
||||
}
|
||||
|
||||
export function TextField({
|
||||
theme = "primary",
|
||||
label,
|
||||
hint,
|
||||
textFieldProps,
|
||||
containerProps,
|
||||
...props
|
||||
}: TextFieldProps) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<div style={{ flex: 1 }}>
|
||||
{label && (
|
||||
<label htmlFor={id} className={styles.label} tabIndex={0}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<div className={styles.textFieldContainer} {...containerProps}>
|
||||
{label && <label tabIndex={0}>{label}</label>}
|
||||
|
||||
<div
|
||||
className={styles.textField({ focused: isFocused, theme })}
|
||||
|
@ -45,6 +48,8 @@ export function TextField({
|
|||
{...props}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hint && <small tabIndex={0}>{hint}</small>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,13 +3,13 @@ import type { PayloadAction } from "@reduxjs/toolkit";
|
|||
import type { TorrentProgress } from "@types";
|
||||
|
||||
interface DownloadState {
|
||||
packets: TorrentProgress[];
|
||||
lastPacket: TorrentProgress | null;
|
||||
gameId: number | null;
|
||||
gamesWithDeletionInProgress: number[];
|
||||
}
|
||||
|
||||
const initialState: DownloadState = {
|
||||
packets: [],
|
||||
lastPacket: null,
|
||||
gameId: null,
|
||||
gamesWithDeletionInProgress: [],
|
||||
};
|
||||
|
@ -18,12 +18,12 @@ export const downloadSlice = createSlice({
|
|||
name: "download",
|
||||
initialState,
|
||||
reducers: {
|
||||
addPacket: (state, action: PayloadAction<TorrentProgress>) => {
|
||||
state.packets = [...state.packets, action.payload];
|
||||
setLastPacket: (state, action: PayloadAction<TorrentProgress>) => {
|
||||
state.lastPacket = action.payload;
|
||||
if (!state.gameId) state.gameId = action.payload.game.id;
|
||||
},
|
||||
clearDownload: (state) => {
|
||||
state.packets = [];
|
||||
state.lastPacket = null;
|
||||
state.gameId = null;
|
||||
},
|
||||
setGameDeleting: (state, action: PayloadAction<number>) => {
|
||||
|
@ -42,7 +42,7 @@ export const downloadSlice = createSlice({
|
|||
});
|
||||
|
||||
export const {
|
||||
addPacket,
|
||||
setLastPacket,
|
||||
clearDownload,
|
||||
setGameDeleting,
|
||||
removeGameFromDeleting,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { formatDistance } from "date-fns";
|
||||
import type { FormatDistanceOptions } from "date-fns";
|
||||
import { ptBR, enUS, es, fr } from "date-fns/locale";
|
||||
import { ptBR, enUS, es, fr, pl, hu, tr, ru, it } from "date-fns/locale";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function useDate() {
|
||||
|
@ -10,6 +10,11 @@ export function useDate() {
|
|||
if (i18n.language.startsWith("pt")) return ptBR;
|
||||
if (i18n.language.startsWith("es")) return es;
|
||||
if (i18n.language.startsWith("fr")) return fr;
|
||||
if (i18n.language.startsWith("hu")) return hu;
|
||||
if (i18n.language.startsWith("pl")) return pl;
|
||||
if (i18n.language.startsWith("tr")) return tr;
|
||||
if (i18n.language.startsWith("ru")) return ru;
|
||||
if (i18n.language.startsWith("it")) return it;
|
||||
|
||||
return enUS;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { formatDownloadProgress } from "@renderer/helpers";
|
|||
import { useLibrary } from "./use-library";
|
||||
import { useAppDispatch, useAppSelector } from "./redux";
|
||||
import {
|
||||
addPacket,
|
||||
setLastPacket,
|
||||
clearDownload,
|
||||
setGameDeleting,
|
||||
removeGameFromDeleting,
|
||||
|
@ -18,13 +18,11 @@ export function useDownload() {
|
|||
const { updateLibrary } = useLibrary();
|
||||
const { formatDistance } = useDate();
|
||||
|
||||
const { packets, gamesWithDeletionInProgress } = useAppSelector(
|
||||
const { lastPacket, gamesWithDeletionInProgress } = useAppSelector(
|
||||
(state) => state.download
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const lastPacket = packets.at(-1);
|
||||
|
||||
const startDownload = (
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
|
@ -128,6 +126,6 @@ export function useDownload() {
|
|||
deleteGame,
|
||||
isGameDeleting,
|
||||
clearDownload: () => dispatch(clearDownload()),
|
||||
addPacket: (packet: TorrentProgress) => dispatch(addPacket(packet)),
|
||||
setLastPacket: (packet: TorrentProgress) => dispatch(setLastPacket(packet)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ export const downloaderName = style({
|
|||
borderRadius: "4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
alignSelf: "flex-start",
|
||||
});
|
||||
|
||||
export const downloads = style({
|
||||
|
|
|
@ -266,12 +266,11 @@ export function Downloads() {
|
|||
>
|
||||
{game.title}
|
||||
</button>
|
||||
|
||||
<small className={styles.downloaderName}>
|
||||
{downloaderName[game?.downloader]}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<small className={styles.downloaderName}>
|
||||
{downloaderName[game?.downloader]}
|
||||
</small>
|
||||
{getGameInfo(game)}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
|||
return gameDetails.screenshots.length;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export function HeroPanelActions({
|
|||
filters: [
|
||||
{
|
||||
name: "Game executable",
|
||||
extensions: window.electron.platform === "win32" ? ["exe"] : [],
|
||||
extensions: ["exe"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
|
|
@ -36,7 +36,7 @@ export function RepacksModal({
|
|||
|
||||
useEffect(() => {
|
||||
setFilteredRepacks(gameDetails.repacks);
|
||||
}, [gameDetails.repacks]);
|
||||
}, [gameDetails.repacks, visible]);
|
||||
|
||||
const handleRepackClick = (repack: GameRepack) => {
|
||||
setRepack(repack);
|
||||
|
|
|
@ -17,11 +17,3 @@ export const hintText = style({
|
|||
fontSize: "12px",
|
||||
color: vars.color.bodyText,
|
||||
});
|
||||
|
||||
export const settingsLink = style({
|
||||
textDecoration: "none",
|
||||
color: "#C0C1C7",
|
||||
":hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Button, Modal, TextField } from "@renderer/components";
|
||||
import { Button, Link, Modal, TextField } from "@renderer/components";
|
||||
import { GameRepack, ShopDetails } from "@types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { DiskSpace } from "check-disk-space";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as styles from "./select-folder-modal.css";
|
||||
import { DownloadIcon } from "@primer/octicons-react";
|
||||
|
||||
|
@ -100,10 +99,9 @@ export function SelectFolderModal({
|
|||
</Button>
|
||||
</div>
|
||||
<p className={styles.hintText}>
|
||||
{t("select_folder_hint")}{" "}
|
||||
<Link to="/settings" className={styles.settingsLink}>
|
||||
{t("settings")}
|
||||
</Link>
|
||||
<Trans i18nKey="select_folder_hint" ns="game_details">
|
||||
<Link to="/settings" />
|
||||
</Trans>
|
||||
</p>
|
||||
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
||||
<DownloadIcon />
|
||||
|
|
7
src/renderer/src/pages/settings/settings-general.css.ts
Normal file
7
src/renderer/src/pages/settings/settings-general.css.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
import { SPACING_UNIT } from "../../theme.css";
|
||||
|
||||
export const downloadsPathField = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
115
src/renderer/src/pages/settings/settings-general.tsx
Normal file
115
src/renderer/src/pages/settings/settings-general.tsx
Normal file
|
@ -0,0 +1,115 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
import { TextField, Button, CheckboxField } from "@renderer/components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import * as styles from "./settings-general.css";
|
||||
import type { UserPreferences } from "@types";
|
||||
|
||||
export interface SettingsGeneralProps {
|
||||
userPreferences: UserPreferences | null;
|
||||
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
||||
}
|
||||
|
||||
export function SettingsGeneral({
|
||||
userPreferences,
|
||||
updateUserPreferences,
|
||||
}: SettingsGeneralProps) {
|
||||
const [form, setForm] = useState({
|
||||
downloadsPath: "",
|
||||
downloadNotificationsEnabled: false,
|
||||
repackUpdatesNotificationsEnabled: false,
|
||||
telemetryEnabled: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (userPreferences) {
|
||||
const {
|
||||
downloadsPath,
|
||||
downloadNotificationsEnabled,
|
||||
repackUpdatesNotificationsEnabled,
|
||||
telemetryEnabled,
|
||||
} = userPreferences;
|
||||
|
||||
window.electron.getDefaultDownloadsPath().then((defaultDownloadsPath) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
downloadsPath: downloadsPath ?? defaultDownloadsPath,
|
||||
downloadNotificationsEnabled,
|
||||
repackUpdatesNotificationsEnabled,
|
||||
telemetryEnabled,
|
||||
}));
|
||||
});
|
||||
}
|
||||
}, [userPreferences]);
|
||||
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
defaultPath: form.downloadsPath,
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
updateUserPreferences({ downloadsPath: path });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={form.downloadsPath}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h3>{t("notifications")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences({
|
||||
downloadNotificationsEnabled: !form.downloadNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences({
|
||||
repackUpdatesNotificationsEnabled:
|
||||
!form.repackUpdatesNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<h3>{t("telemetry")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("telemetry_description")}
|
||||
checked={form.telemetryEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences({
|
||||
telemetryEnabled: !form.telemetryEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { style } from "@vanilla-extract/css";
|
||||
|
||||
import { SPACING_UNIT } from "../../theme.css";
|
||||
|
||||
export const form = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
83
src/renderer/src/pages/settings/settings-real-debrid.tsx
Normal file
83
src/renderer/src/pages/settings/settings-real-debrid.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
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";
|
||||
|
||||
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 [form, setForm] = useState({
|
||||
useRealDebrid: false,
|
||||
realDebridApiToken: null as string | null,
|
||||
});
|
||||
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
useEffect(() => {
|
||||
if (userPreferences) {
|
||||
setForm({
|
||||
useRealDebrid: Boolean(userPreferences.realDebridApiToken),
|
||||
realDebridApiToken: userPreferences.realDebridApiToken ?? null,
|
||||
});
|
||||
}
|
||||
}, [userPreferences]);
|
||||
|
||||
const handleFormSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
||||
event.preventDefault();
|
||||
updateUserPreferences({ realDebridApiToken: form.realDebridApiToken });
|
||||
};
|
||||
|
||||
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
|
||||
|
||||
return (
|
||||
<form className={styles.form} onSubmit={handleFormSubmit}>
|
||||
<CheckboxField
|
||||
label={t("enable_real_debrid")}
|
||||
checked={form.useRealDebrid}
|
||||
onChange={() =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
useRealDebrid: !form.useRealDebrid,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
|
||||
{form.useRealDebrid && (
|
||||
<TextField
|
||||
label={t("real_debrid_api_token_description")}
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) =>
|
||||
setForm({ ...form, realDebridApiToken: event.target.value })
|
||||
}
|
||||
placeholder="API Token"
|
||||
containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }}
|
||||
hint={
|
||||
<Trans i18nKey="real_debrid_api_token_hint" ns="settings">
|
||||
<Link to={REAL_DEBRID_API_TOKEN_URL} />
|
||||
</Trans>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
|
@ -20,11 +20,6 @@ export const content = style({
|
|||
flexDirection: "column",
|
||||
});
|
||||
|
||||
export const downloadsPathField = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
});
|
||||
|
||||
export const settingsCategories = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
|
|
|
@ -1,138 +1,46 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Button, CheckboxField, TextField } from "@renderer/components";
|
||||
import { Button, CheckboxField } from "@renderer/components";
|
||||
|
||||
import * as styles from "./settings.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UserPreferences } from "@types";
|
||||
import { SettingsRealDebrid } from "./settings-real-debrid";
|
||||
import { SettingsGeneral } from "./settings-general";
|
||||
|
||||
const categories = ["general", "behavior", "real_debrid"];
|
||||
|
||||
export function Settings() {
|
||||
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
|
||||
|
||||
const [form, setForm] = useState({
|
||||
downloadsPath: "",
|
||||
downloadNotificationsEnabled: false,
|
||||
repackUpdatesNotificationsEnabled: false,
|
||||
telemetryEnabled: false,
|
||||
realDebridApiToken: null as string | null,
|
||||
preferQuitInsteadOfHiding: false,
|
||||
runAtStartup: false,
|
||||
});
|
||||
const [userPreferences, setUserPreferences] =
|
||||
useState<UserPreferences | null>(null);
|
||||
|
||||
const { t } = useTranslation("settings");
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
window.electron.getDefaultDownloadsPath(),
|
||||
window.electron.getUserPreferences(),
|
||||
]).then(([path, userPreferences]) => {
|
||||
setForm({
|
||||
downloadsPath: userPreferences?.downloadsPath || path,
|
||||
downloadNotificationsEnabled:
|
||||
userPreferences?.downloadNotificationsEnabled ?? false,
|
||||
repackUpdatesNotificationsEnabled:
|
||||
userPreferences?.repackUpdatesNotificationsEnabled ?? false,
|
||||
telemetryEnabled: userPreferences?.telemetryEnabled ?? false,
|
||||
realDebridApiToken: userPreferences?.realDebridApiToken ?? null,
|
||||
preferQuitInsteadOfHiding:
|
||||
userPreferences?.preferQuitInsteadOfHiding ?? false,
|
||||
runAtStartup: userPreferences?.runAtStartup ?? false,
|
||||
});
|
||||
window.electron.getUserPreferences().then((userPreferences) => {
|
||||
setUserPreferences(userPreferences);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const updateUserPreferences = <T extends keyof UserPreferences>(
|
||||
field: T,
|
||||
value: UserPreferences[T]
|
||||
) => {
|
||||
setForm((prev) => ({ ...prev, [field]: value }));
|
||||
|
||||
window.electron.updateUserPreferences({
|
||||
[field]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
defaultPath: form.downloadsPath,
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
updateUserPreferences("downloadsPath", path);
|
||||
}
|
||||
const handleUpdateUserPreferences = (values: Partial<UserPreferences>) => {
|
||||
window.electron.updateUserPreferences(values);
|
||||
};
|
||||
|
||||
const renderCategory = () => {
|
||||
if (currentCategory === "general") {
|
||||
return (
|
||||
<>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={form.downloadsPath}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<h3>{t("notifications")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_download_notifications")}
|
||||
checked={form.downloadNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"downloadNotificationsEnabled",
|
||||
!form.downloadNotificationsEnabled
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_repack_list_notifications")}
|
||||
checked={form.repackUpdatesNotificationsEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences(
|
||||
"repackUpdatesNotificationsEnabled",
|
||||
!form.repackUpdatesNotificationsEnabled
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<h3>{t("telemetry")}</h3>
|
||||
|
||||
<CheckboxField
|
||||
label={t("telemetry_description")}
|
||||
checked={form.telemetryEnabled}
|
||||
onChange={() =>
|
||||
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
|
||||
}
|
||||
/>
|
||||
</>
|
||||
<SettingsGeneral
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentCategory === "real_debrid") {
|
||||
return (
|
||||
<TextField
|
||||
label={t("real_debrid_api_token_description")}
|
||||
value={form.realDebridApiToken ?? ""}
|
||||
type="password"
|
||||
onChange={(event) => {
|
||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
||||
}}
|
||||
placeholder="API Token"
|
||||
<SettingsRealDebrid
|
||||
userPreferences={userPreferences}
|
||||
updateUserPreferences={handleUpdateUserPreferences}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -177,7 +85,7 @@ export function Settings() {
|
|||
))}
|
||||
</section>
|
||||
|
||||
<h3>{t(currentCategory)}</h3>
|
||||
<h2>{t(currentCategory)}</h2>
|
||||
{renderCategory()}
|
||||
</div>
|
||||
</section>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue