mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: adding deep linking to import sources
This commit is contained in:
parent
fbcacd7c39
commit
6d3b04fc3c
@ -5,7 +5,6 @@ export const downloadSourceSchema = z.object({
|
|||||||
downloads: z.array(
|
downloads: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
title: z.string().max(255),
|
title: z.string().max(255),
|
||||||
downloaders: z.array(z.enum(["real_debrid", "torrent"])),
|
|
||||||
uris: z.array(z.string()),
|
uris: z.array(z.string()),
|
||||||
uploadDate: z.string().max(255),
|
uploadDate: z.string().max(255),
|
||||||
fileSize: z.string().max(255),
|
fileSize: z.string().max(255),
|
||||||
|
@ -73,7 +73,16 @@ app.on("browser-window-created", (_, window) => {
|
|||||||
optimizer.watchWindowShortcuts(window);
|
optimizer.watchWindowShortcuts(window);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on("second-instance", (_event) => {
|
const handleDeepLinkPath = (uri?: string) => {
|
||||||
|
if (!uri) return;
|
||||||
|
const url = new URL(uri);
|
||||||
|
|
||||||
|
if (url.host === "install-source") {
|
||||||
|
WindowManager.redirect(`settings${url.search}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app.on("second-instance", (_event, commandLine) => {
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (WindowManager.mainWindow) {
|
if (WindowManager.mainWindow) {
|
||||||
if (WindowManager.mainWindow.isMinimized())
|
if (WindowManager.mainWindow.isMinimized())
|
||||||
@ -84,16 +93,12 @@ app.on("second-instance", (_event) => {
|
|||||||
WindowManager.createMainWindow();
|
WindowManager.createMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
// const [, path] = commandLine.pop()?.split("://") ?? [];
|
handleDeepLinkPath(commandLine.pop());
|
||||||
// if (path) {
|
|
||||||
// WindowManager.redirect(path);
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// app.on("open-url", (_event, url) => {
|
app.on("open-url", (_event, url) => {
|
||||||
// const [, path] = url.split("://");
|
handleDeepLinkPath(url);
|
||||||
// WindowManager.redirect(path);
|
});
|
||||||
// });
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
// for applications and their menu bar to stay active until the user quits
|
// for applications and their menu bar to stay active until the user quits
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
shell,
|
shell,
|
||||||
} from "electron";
|
} from "electron";
|
||||||
import { is } from "@electron-toolkit/utils";
|
import { is } from "@electron-toolkit/utils";
|
||||||
import { t } from "i18next";
|
import i18next, { t } from "i18next";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import icon from "@resources/icon.png?asset";
|
import icon from "@resources/icon.png?asset";
|
||||||
import trayIcon from "@resources/tray-icon.png?asset";
|
import trayIcon from "@resources/tray-icon.png?asset";
|
||||||
@ -100,7 +100,13 @@ export class WindowManager {
|
|||||||
|
|
||||||
authWindow.removeMenu();
|
authWindow.removeMenu();
|
||||||
|
|
||||||
authWindow.loadURL("https://auth.hydra.losbroxas.org/");
|
const searchParams = new URLSearchParams({
|
||||||
|
lng: i18next.language,
|
||||||
|
});
|
||||||
|
|
||||||
|
authWindow.loadURL(
|
||||||
|
`https://auth.hydra.losbroxas.org/?${searchParams.toString()}`
|
||||||
|
);
|
||||||
|
|
||||||
authWindow.once("ready-to-show", () => {
|
authWindow.once("ready-to-show", () => {
|
||||||
authWindow.show();
|
authWindow.show();
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from "./game-details/game-details.context";
|
export * from "./game-details/game-details.context";
|
||||||
|
export * from "./settings/settings.context";
|
||||||
|
73
src/renderer/src/context/settings/settings.context.tsx
Normal file
73
src/renderer/src/context/settings/settings.context.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { setUserPreferences } from "@renderer/features";
|
||||||
|
import { useAppDispatch } from "@renderer/hooks";
|
||||||
|
import type { UserPreferences } from "@types";
|
||||||
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export interface SettingsContext {
|
||||||
|
updateUserPreferences: (values: Partial<UserPreferences>) => Promise<void>;
|
||||||
|
setCurrentCategoryIndex: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
clearSourceUrl: () => void;
|
||||||
|
sourceUrl: string | null;
|
||||||
|
currentCategoryIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const settingsContext = createContext<SettingsContext>({
|
||||||
|
updateUserPreferences: async () => {},
|
||||||
|
setCurrentCategoryIndex: () => {},
|
||||||
|
clearSourceUrl: () => {},
|
||||||
|
sourceUrl: null,
|
||||||
|
currentCategoryIndex: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { Provider } = settingsContext;
|
||||||
|
export const { Consumer: SettingsContextConsumer } = settingsContext;
|
||||||
|
|
||||||
|
export interface SettingsContextProviderProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsContextProvider({
|
||||||
|
children,
|
||||||
|
}: SettingsContextProviderProps) {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const [sourceUrl, setSourceUrl] = useState<string | null>(null);
|
||||||
|
const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0);
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const defaultSourceUrl = searchParams.get("urls");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sourceUrl) setCurrentCategoryIndex(2);
|
||||||
|
}, [sourceUrl]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (defaultSourceUrl) {
|
||||||
|
setSourceUrl(defaultSourceUrl);
|
||||||
|
}
|
||||||
|
}, [defaultSourceUrl]);
|
||||||
|
|
||||||
|
const clearSourceUrl = () => setSourceUrl(null);
|
||||||
|
|
||||||
|
const updateUserPreferences = async (values: Partial<UserPreferences>) => {
|
||||||
|
await window.electron.updateUserPreferences(values);
|
||||||
|
window.electron.getUserPreferences().then((userPreferences) => {
|
||||||
|
dispatch(setUserPreferences(userPreferences));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Provider
|
||||||
|
value={{
|
||||||
|
updateUserPreferences,
|
||||||
|
setCurrentCategoryIndex,
|
||||||
|
clearSourceUrl,
|
||||||
|
currentCategoryIndex,
|
||||||
|
sourceUrl,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button, Modal, TextField } from "@renderer/components";
|
import { Button, Modal, TextField } from "@renderer/components";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
interface AddDownloadSourceModalProps {
|
interface AddDownloadSourceModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -23,24 +24,31 @@ export function AddDownloadSourceModal({
|
|||||||
downloadCount: number;
|
downloadCount: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValue("");
|
|
||||||
setIsLoading(false);
|
|
||||||
setValidationResult(null);
|
|
||||||
}, [visible]);
|
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
const handleValidateDownloadSource = async () => {
|
const { sourceUrl } = useContext(settingsContext);
|
||||||
|
|
||||||
|
const handleValidateDownloadSource = useCallback(async (url: string) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await window.electron.validateDownloadSource(value);
|
const result = await window.electron.validateDownloadSource(url);
|
||||||
setValidationResult(result);
|
setValidationResult(result);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue("");
|
||||||
|
setIsLoading(false);
|
||||||
|
setValidationResult(null);
|
||||||
|
|
||||||
|
if (sourceUrl) {
|
||||||
|
setValue(sourceUrl);
|
||||||
|
handleValidateDownloadSource(sourceUrl);
|
||||||
|
}
|
||||||
|
}, [visible, handleValidateDownloadSource, sourceUrl]);
|
||||||
|
|
||||||
const handleAddDownloadSource = async () => {
|
const handleAddDownloadSource = async () => {
|
||||||
await window.electron.addDownloadSource(value);
|
await window.electron.addDownloadSource(value);
|
||||||
@ -73,7 +81,7 @@ export function AddDownloadSourceModal({
|
|||||||
type="button"
|
type="button"
|
||||||
theme="outline"
|
theme="outline"
|
||||||
style={{ alignSelf: "flex-end" }}
|
style={{ alignSelf: "flex-end" }}
|
||||||
onClick={handleValidateDownloadSource}
|
onClick={() => handleValidateDownloadSource(value)}
|
||||||
disabled={isLoading || !value}
|
disabled={isLoading || !value}
|
||||||
>
|
>
|
||||||
{t("validate_download_source")}
|
{t("validate_download_source")}
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import type { UserPreferences } from "@types";
|
|
||||||
|
|
||||||
import { CheckboxField } from "@renderer/components";
|
import { CheckboxField } from "@renderer/components";
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
import { useAppSelector } from "@renderer/hooks";
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
export interface SettingsBehaviorProps {
|
export function SettingsBehavior() {
|
||||||
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsBehavior({
|
|
||||||
updateUserPreferences,
|
|
||||||
}: SettingsBehaviorProps) {
|
|
||||||
const userPreferences = useAppSelector(
|
const userPreferences = useAppSelector(
|
||||||
(state) => state.userPreferences.value
|
(state) => state.userPreferences.value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { updateUserPreferences } = useContext(settingsContext);
|
||||||
|
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
preferQuitInsteadOfHiding: false,
|
preferQuitInsteadOfHiding: false,
|
||||||
runAtStartup: false,
|
runAtStartup: false,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
import { TextField, Button, Badge } from "@renderer/components";
|
import { TextField, Button, Badge } from "@renderer/components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -10,6 +10,7 @@ import { AddDownloadSourceModal } from "./add-download-source-modal";
|
|||||||
import { useToast } from "@renderer/hooks";
|
import { useToast } from "@renderer/hooks";
|
||||||
import { DownloadSourceStatus } from "@shared";
|
import { DownloadSourceStatus } from "@shared";
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
export function SettingsDownloadSources() {
|
export function SettingsDownloadSources() {
|
||||||
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
||||||
@ -18,8 +19,9 @@ export function SettingsDownloadSources() {
|
|||||||
const [isSyncingDownloadSources, setIsSyncingDownloadSources] =
|
const [isSyncingDownloadSources, setIsSyncingDownloadSources] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { sourceUrl, clearSourceUrl } = useContext(settingsContext);
|
||||||
|
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
const { showSuccessToast } = useToast();
|
const { showSuccessToast } = useToast();
|
||||||
|
|
||||||
const getDownloadSources = async () => {
|
const getDownloadSources = async () => {
|
||||||
@ -32,6 +34,10 @@ export function SettingsDownloadSources() {
|
|||||||
getDownloadSources();
|
getDownloadSources();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sourceUrl) setShowAddDownloadSourceModal(true);
|
||||||
|
}, [sourceUrl]);
|
||||||
|
|
||||||
const handleRemoveSource = async (id: number) => {
|
const handleRemoveSource = async (id: number) => {
|
||||||
await window.electron.removeDownloadSource(id);
|
await window.electron.removeDownloadSource(id);
|
||||||
showSuccessToast(t("removed_download_source"));
|
showSuccessToast(t("removed_download_source"));
|
||||||
@ -63,11 +69,16 @@ export function SettingsDownloadSources() {
|
|||||||
[DownloadSourceStatus.Errored]: t("download_source_errored"),
|
[DownloadSourceStatus.Errored]: t("download_source_errored"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleModalClose = () => {
|
||||||
|
clearSourceUrl();
|
||||||
|
setShowAddDownloadSourceModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AddDownloadSourceModal
|
<AddDownloadSourceModal
|
||||||
visible={showAddDownloadSourceModal}
|
visible={showAddDownloadSourceModal}
|
||||||
onClose={() => setShowAddDownloadSourceModal(false)}
|
onClose={handleModalClose}
|
||||||
onAddDownloadSource={handleAddDownloadSource}
|
onAddDownloadSource={handleAddDownloadSource}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import ISO6391 from "iso-639-1";
|
import ISO6391 from "iso-639-1";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -8,27 +8,24 @@ import {
|
|||||||
SelectField,
|
SelectField,
|
||||||
} from "@renderer/components";
|
} from "@renderer/components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import type { UserPreferences } from "@types";
|
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
import { useAppSelector } from "@renderer/hooks";
|
||||||
|
|
||||||
import { changeLanguage } from "i18next";
|
import { changeLanguage } from "i18next";
|
||||||
import * as languageResources from "@locales";
|
import * as languageResources from "@locales";
|
||||||
import { orderBy } from "lodash-es";
|
import { orderBy } from "lodash-es";
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
interface LanguageOption {
|
interface LanguageOption {
|
||||||
option: string;
|
option: string;
|
||||||
nativeName: string;
|
nativeName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingsGeneralProps {
|
export function SettingsGeneral() {
|
||||||
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsGeneral({
|
|
||||||
updateUserPreferences,
|
|
||||||
}: SettingsGeneralProps) {
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
const { updateUserPreferences } = useContext(settingsContext);
|
||||||
|
|
||||||
const userPreferences = useAppSelector(
|
const userPreferences = useAppSelector(
|
||||||
(state) => state.userPreferences.value
|
(state) => state.userPreferences.value
|
||||||
);
|
);
|
||||||
|
@ -1,26 +1,23 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { Trans, useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { Button, CheckboxField, Link, TextField } from "@renderer/components";
|
import { Button, CheckboxField, Link, TextField } from "@renderer/components";
|
||||||
import * as styles from "./settings-real-debrid.css";
|
import * as styles from "./settings-real-debrid.css";
|
||||||
import type { UserPreferences } from "@types";
|
|
||||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||||
|
|
||||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
import { settingsContext } from "@renderer/context";
|
||||||
|
|
||||||
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
|
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
|
||||||
|
|
||||||
export interface SettingsRealDebridProps {
|
export function SettingsRealDebrid() {
|
||||||
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsRealDebrid({
|
|
||||||
updateUserPreferences,
|
|
||||||
}: SettingsRealDebridProps) {
|
|
||||||
const userPreferences = useAppSelector(
|
const userPreferences = useAppSelector(
|
||||||
(state) => state.userPreferences.value
|
(state) => state.userPreferences.value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { updateUserPreferences } = useContext(settingsContext);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
useRealDebrid: false,
|
useRealDebrid: false,
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Button } from "@renderer/components";
|
import { Button } from "@renderer/components";
|
||||||
|
|
||||||
import * as styles from "./settings.css";
|
import * as styles from "./settings.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UserPreferences } from "@types";
|
|
||||||
import { SettingsRealDebrid } from "./settings-real-debrid";
|
import { SettingsRealDebrid } from "./settings-real-debrid";
|
||||||
import { SettingsGeneral } from "./settings-general";
|
import { SettingsGeneral } from "./settings-general";
|
||||||
import { SettingsBehavior } from "./settings-behavior";
|
import { SettingsBehavior } from "./settings-behavior";
|
||||||
import { useAppDispatch } from "@renderer/hooks";
|
|
||||||
import { setUserPreferences } from "@renderer/features";
|
|
||||||
import { SettingsDownloadSources } from "./settings-download-sources";
|
import { SettingsDownloadSources } from "./settings-download-sources";
|
||||||
|
import {
|
||||||
|
SettingsContextConsumer,
|
||||||
|
SettingsContextProvider,
|
||||||
|
} from "@renderer/context";
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
t("general"),
|
t("general"),
|
||||||
t("behavior"),
|
t("behavior"),
|
||||||
@ -23,37 +22,24 @@ export function Settings() {
|
|||||||
"Real-Debrid",
|
"Real-Debrid",
|
||||||
];
|
];
|
||||||
|
|
||||||
const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0);
|
return (
|
||||||
|
<SettingsContextProvider>
|
||||||
const handleUpdateUserPreferences = async (
|
<SettingsContextConsumer>
|
||||||
values: Partial<UserPreferences>
|
{({ currentCategoryIndex, setCurrentCategoryIndex }) => {
|
||||||
) => {
|
|
||||||
await window.electron.updateUserPreferences(values);
|
|
||||||
window.electron.getUserPreferences().then((userPreferences) => {
|
|
||||||
dispatch(setUserPreferences(userPreferences));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderCategory = () => {
|
const renderCategory = () => {
|
||||||
if (currentCategoryIndex === 0) {
|
if (currentCategoryIndex === 0) {
|
||||||
return (
|
return <SettingsGeneral />;
|
||||||
<SettingsGeneral updateUserPreferences={handleUpdateUserPreferences} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentCategoryIndex === 1) {
|
if (currentCategoryIndex === 1) {
|
||||||
return (
|
return <SettingsBehavior />;
|
||||||
<SettingsBehavior updateUserPreferences={handleUpdateUserPreferences} />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentCategoryIndex === 2) {
|
if (currentCategoryIndex === 2) {
|
||||||
return <SettingsDownloadSources />;
|
return <SettingsDownloadSources />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <SettingsRealDebrid />;
|
||||||
<SettingsRealDebrid updateUserPreferences={handleUpdateUserPreferences} />
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -63,7 +49,9 @@ export function Settings() {
|
|||||||
{categories.map((category, index) => (
|
{categories.map((category, index) => (
|
||||||
<Button
|
<Button
|
||||||
key={category}
|
key={category}
|
||||||
theme={currentCategoryIndex === index ? "primary" : "outline"}
|
theme={
|
||||||
|
currentCategoryIndex === index ? "primary" : "outline"
|
||||||
|
}
|
||||||
onClick={() => setCurrentCategoryIndex(index)}
|
onClick={() => setCurrentCategoryIndex(index)}
|
||||||
>
|
>
|
||||||
{category}
|
{category}
|
||||||
@ -76,4 +64,8 @@ export function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
}}
|
||||||
|
</SettingsContextConsumer>
|
||||||
|
</SettingsContextProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user