Add extension settings screen (#557)
This commit is contained in:
@@ -33,9 +33,9 @@ import { LibrarySettings } from '@/screens/settings/LibrarySettings';
|
|||||||
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
|
||||||
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
|
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
|
||||||
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
|
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
|
||||||
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
|
|
||||||
import { ServerUpdateChecker } from '@/components/settings/ServerUpdateChecker.tsx';
|
import { ServerUpdateChecker } from '@/components/settings/ServerUpdateChecker.tsx';
|
||||||
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { ExtensionSettings } from '@/screens/settings/ExtensionSettings.tsx';
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
// Adds messages only in a dev environment
|
// Adds messages only in a dev environment
|
||||||
@@ -98,7 +98,7 @@ export const App: React.FC = () => (
|
|||||||
<Route path="downloadSettings" element={<DownloadSettings />} />
|
<Route path="downloadSettings" element={<DownloadSettings />} />
|
||||||
<Route path="backup" element={<Backup />} />
|
<Route path="backup" element={<Backup />} />
|
||||||
<Route path="server" element={<ServerSettings />} />
|
<Route path="server" element={<ServerSettings />} />
|
||||||
<Route path="webUI" element={<WebUISettings />} />
|
<Route path="extensionSettings" element={<ExtensionSettings />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Manga Routes */}
|
{/* Manga Routes */}
|
||||||
|
|||||||
@@ -255,7 +255,8 @@
|
|||||||
"title": "Extension repositories"
|
"title": "Extension repositories"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"title": "Extension Settings"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"label": {
|
"label": {
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ export function Extensions() {
|
|||||||
return (
|
return (
|
||||||
<Stack sx={{ paddingTop: '20px' }} alignItems="center" justifyContent="center" rowGap="10px">
|
<Stack sx={{ paddingTop: '20px' }} alignItems="center" justifyContent="center" rowGap="10px">
|
||||||
<Typography>{t('extension.label.add_repository_info')}</Typography>
|
<Typography>{t('extension.label.add_repository_info')}</Typography>
|
||||||
<Button component={Link} variant="contained" to="/settings/server">
|
<Button component={Link} variant="contained" to="/settings/extensionSettings">
|
||||||
{t('settings.title')}
|
{t('settings.title')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import ListItem from '@mui/material/ListItem';
|
|||||||
import ListItemIcon from '@mui/material/ListItemIcon';
|
import ListItemIcon from '@mui/material/ListItemIcon';
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
import Switch from '@mui/material/Switch';
|
import Switch from '@mui/material/Switch';
|
||||||
import FavoriteIcon from '@mui/icons-material/Favorite';
|
|
||||||
import { Link, ListItemButton, MenuItem, Select } from '@mui/material';
|
import { Link, ListItemButton, MenuItem, Select } from '@mui/material';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import LanguageIcon from '@mui/icons-material/Language';
|
import LanguageIcon from '@mui/icons-material/Language';
|
||||||
@@ -27,6 +26,7 @@ import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
|||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import WebIcon from '@mui/icons-material/Web';
|
import WebIcon from '@mui/icons-material/Web';
|
||||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||||
|
import ExploreOutlinedIcon from '@mui/icons-material/ExploreOutlined';
|
||||||
import { langCodeToName } from '@/util/language';
|
import { langCodeToName } from '@/util/language';
|
||||||
import { useLocalStorage } from '@/util/useLocalStorage';
|
import { useLocalStorage } from '@/util/useLocalStorage';
|
||||||
import { ListItemLink } from '@/components/util/ListItemLink';
|
import { ListItemLink } from '@/components/util/ListItemLink';
|
||||||
@@ -46,7 +46,6 @@ export function Settings() {
|
|||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
const { darkTheme, setDarkTheme } = useContext(DarkTheme);
|
||||||
const [showNsfw, setShowNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
|
||||||
|
|
||||||
const DEFAULT_ITEM_WIDTH = 300;
|
const DEFAULT_ITEM_WIDTH = 300;
|
||||||
const itemWidthIcon = useMemo(() => <ViewModuleIcon />, []);
|
const itemWidthIcon = useMemo(() => <ViewModuleIcon />, []);
|
||||||
@@ -116,16 +115,6 @@ export function Settings() {
|
|||||||
showSlider
|
showSlider
|
||||||
handleUpdate={setItemWidth}
|
handleUpdate={setItemWidth}
|
||||||
/>
|
/>
|
||||||
<ListItem>
|
|
||||||
<ListItemIcon>
|
|
||||||
<FavoriteIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText
|
|
||||||
primary={t('settings.label.show_nsfw')}
|
|
||||||
secondary={t('settings.label.show_nsfw_description')}
|
|
||||||
/>
|
|
||||||
<Switch edge="end" checked={showNsfw} onChange={() => setShowNsfw(!showNsfw)} />
|
|
||||||
</ListItem>
|
|
||||||
<ListItem>
|
<ListItem>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LanguageIcon />
|
<LanguageIcon />
|
||||||
@@ -162,6 +151,12 @@ export function Settings() {
|
|||||||
secondary={t('settings.clear_cache.label.description')}
|
secondary={t('settings.clear_cache.label.description')}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
<ListItemLink to="/settings/extensionSettings">
|
||||||
|
<ListItemIcon>
|
||||||
|
<ExploreOutlinedIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary={t('global.label.browse')} />
|
||||||
|
</ListItemLink>
|
||||||
<ListItemLink to="/settings/webUI">
|
<ListItemLink to="/settings/webUI">
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<WebIcon />
|
<WebIcon />
|
||||||
|
|||||||
114
src/screens/settings/ExtensionSettings.tsx
Normal file
114
src/screens/settings/ExtensionSettings.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import { useContext, useEffect } from 'react';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import Switch from '@mui/material/Switch';
|
||||||
|
import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx';
|
||||||
|
import { requestManager } from '@/lib/requests/RequestManager.ts';
|
||||||
|
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||||
|
import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx';
|
||||||
|
import { ServerSettings as GqlServerSettings } from '@/typings.ts';
|
||||||
|
import { TextSetting } from '@/components/settings/text/TextSetting.tsx';
|
||||||
|
import { useLocalStorage } from '@/util/useLocalStorage.tsx';
|
||||||
|
|
||||||
|
type ExtensionsSettings = Pick<GqlServerSettings, 'maxSourcesInParallel' | 'localSourcePath' | 'extensionRepos'>;
|
||||||
|
|
||||||
|
const extractExtensionSettings = (settings: GqlServerSettings): ExtensionsSettings => ({
|
||||||
|
maxSourcesInParallel: settings.maxSourcesInParallel,
|
||||||
|
localSourcePath: settings.localSourcePath,
|
||||||
|
extensionRepos: settings.extensionRepos,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ExtensionSettings = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { setTitle, setAction } = useContext(NavBarContext);
|
||||||
|
|
||||||
|
useSetDefaultBackTo('settings');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTitle(t('extension.settings.title'));
|
||||||
|
setAction(null);
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const [showNsfw, setShowNsfw] = useLocalStorage<boolean>('showNsfw', true);
|
||||||
|
|
||||||
|
const { data } = requestManager.useGetServerSettings();
|
||||||
|
const serverSettings = data ? extractExtensionSettings(data.settings) : undefined;
|
||||||
|
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||||
|
|
||||||
|
const updateSetting = <Setting extends keyof ExtensionsSettings>(
|
||||||
|
setting: Setting,
|
||||||
|
value: ExtensionsSettings[Setting],
|
||||||
|
) => {
|
||||||
|
mutateSettings({ variables: { input: { settings: { [setting]: value } } } });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
<ListItem>
|
||||||
|
<ListItemText
|
||||||
|
primary={t('settings.label.show_nsfw')}
|
||||||
|
secondary={t('settings.label.show_nsfw_description')}
|
||||||
|
/>
|
||||||
|
<Switch edge="end" checked={showNsfw} onChange={() => setShowNsfw(!showNsfw)} />
|
||||||
|
</ListItem>
|
||||||
|
<NumberSetting
|
||||||
|
settingTitle={t('settings.server.requests.sources.parallel.label.title')}
|
||||||
|
settingValue={t('settings.server.requests.sources.parallel.label.value', {
|
||||||
|
value: serverSettings?.maxSourcesInParallel,
|
||||||
|
count: serverSettings?.maxSourcesInParallel,
|
||||||
|
})}
|
||||||
|
valueUnit={t('source.title')}
|
||||||
|
value={serverSettings?.maxSourcesInParallel ?? 6}
|
||||||
|
defaultValue={6}
|
||||||
|
minValue={1}
|
||||||
|
maxValue={20}
|
||||||
|
showSlider
|
||||||
|
stepSize={1}
|
||||||
|
dialogTitle={t('settings.server.requests.sources.parallel.label.title')}
|
||||||
|
handleUpdate={(parallelSources) => updateSetting('maxSourcesInParallel', parallelSources)}
|
||||||
|
/>
|
||||||
|
<MutableListSetting
|
||||||
|
settingName={t('extension.settings.repositories.custom.label.title')}
|
||||||
|
description={t('extension.settings.repositories.custom.label.description')}
|
||||||
|
dialogDisclaimer={
|
||||||
|
<Trans i18nKey="extension.settings.repositories.custom.label.disclaimer">
|
||||||
|
<strong>Suwayomi does not provide any support for 3rd party repositories or extensions!</strong>
|
||||||
|
<br />
|
||||||
|
Use with caution as there could be malicious actors making those repositories.
|
||||||
|
<br />
|
||||||
|
You as the user need to verify the security and that you trust any repository or extension.
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
handleChange={(repos) => {
|
||||||
|
updateSetting('extensionRepos', repos);
|
||||||
|
requestManager.clearExtensionCache();
|
||||||
|
}}
|
||||||
|
values={serverSettings?.extensionRepos}
|
||||||
|
addItemButtonTitle={t('extension.settings.repositories.custom.dialog.action.button.add')}
|
||||||
|
placeholder="https://github.com/MY_ACCOUNT/MY_REPO/tree/repo"
|
||||||
|
validateItem={(repo) =>
|
||||||
|
!!repo.match(
|
||||||
|
/https:\/\/(www\.|raw\.)?(github|githubusercontent)\.com\/([^/]+)\/([^/]+)((\/tree|\/blob)?\/([^/\n]*))?(\/([^/\n]*\.json)?)?/g,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
invalidItemError={t('extension.settings.repositories.custom.error.label.invalid_url')}
|
||||||
|
/>
|
||||||
|
<TextSetting
|
||||||
|
settingName={t('settings.server.local_source.path.label.title')}
|
||||||
|
dialogDescription={t('settings.server.local_source.path.label.description')}
|
||||||
|
value={serverSettings?.localSourcePath}
|
||||||
|
handleChange={(path) => updateSetting('localSourcePath', path)}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useTranslation, Trans } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
import { List, ListItem, ListItemText, Switch } from '@mui/material';
|
import { List, ListItem, ListItemText, Switch } from '@mui/material';
|
||||||
import ListSubheader from '@mui/material/ListSubheader';
|
import ListSubheader from '@mui/material/ListSubheader';
|
||||||
@@ -16,7 +16,6 @@ import { useLocalStorage } from '@/util/useLocalStorage.tsx';
|
|||||||
import { TextSetting } from '@/components/settings/text/TextSetting.tsx';
|
import { TextSetting } from '@/components/settings/text/TextSetting.tsx';
|
||||||
import { ServerSettings as GqlServerSettings } from '@/typings.ts';
|
import { ServerSettings as GqlServerSettings } from '@/typings.ts';
|
||||||
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
|
||||||
import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx';
|
|
||||||
|
|
||||||
type ServerSettingsType = Pick<
|
type ServerSettingsType = Pick<
|
||||||
GqlServerSettings,
|
GqlServerSettings,
|
||||||
@@ -31,12 +30,9 @@ type ServerSettingsType = Pick<
|
|||||||
| 'basicAuthEnabled'
|
| 'basicAuthEnabled'
|
||||||
| 'basicAuthUsername'
|
| 'basicAuthUsername'
|
||||||
| 'basicAuthPassword'
|
| 'basicAuthPassword'
|
||||||
| 'maxSourcesInParallel'
|
|
||||||
| 'localSourcePath'
|
|
||||||
| 'extensionRepos'
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsType => ({
|
const extractServerSettings = (settings: GqlServerSettings): ServerSettingsType => ({
|
||||||
ip: settings.ip,
|
ip: settings.ip,
|
||||||
port: settings.port,
|
port: settings.port,
|
||||||
socksProxyEnabled: settings.socksProxyEnabled,
|
socksProxyEnabled: settings.socksProxyEnabled,
|
||||||
@@ -48,9 +44,6 @@ const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsTyp
|
|||||||
basicAuthEnabled: settings.basicAuthEnabled,
|
basicAuthEnabled: settings.basicAuthEnabled,
|
||||||
basicAuthUsername: settings.basicAuthUsername,
|
basicAuthUsername: settings.basicAuthUsername,
|
||||||
basicAuthPassword: settings.basicAuthPassword,
|
basicAuthPassword: settings.basicAuthPassword,
|
||||||
maxSourcesInParallel: settings.maxSourcesInParallel,
|
|
||||||
localSourcePath: settings.localSourcePath,
|
|
||||||
extensionRepos: settings.extensionRepos,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ServerSettings = () => {
|
export const ServerSettings = () => {
|
||||||
@@ -65,7 +58,7 @@ export const ServerSettings = () => {
|
|||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
const { data } = requestManager.useGetServerSettings();
|
const { data } = requestManager.useGetServerSettings();
|
||||||
const serverSettings = data ? extractDownloadSettings(data.settings) : undefined;
|
const serverSettings = data ? extractServerSettings(data.settings) : undefined;
|
||||||
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
const [mutateSettings] = requestManager.useUpdateServerSettings();
|
||||||
|
|
||||||
const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', '');
|
const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', '');
|
||||||
@@ -99,80 +92,6 @@ export const ServerSettings = () => {
|
|||||||
placeholder="http://localhost:4567"
|
placeholder="http://localhost:4567"
|
||||||
/>
|
/>
|
||||||
</List>
|
</List>
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<ListSubheader component="div" id="server-settings-requests">
|
|
||||||
{t('settings.server.requests.title')}
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<NumberSetting
|
|
||||||
settingTitle={t('settings.server.requests.sources.parallel.label.title')}
|
|
||||||
settingValue={t('settings.server.requests.sources.parallel.label.value', {
|
|
||||||
value: serverSettings?.maxSourcesInParallel,
|
|
||||||
count: serverSettings?.maxSourcesInParallel,
|
|
||||||
})}
|
|
||||||
valueUnit={t('source.title')}
|
|
||||||
value={serverSettings?.maxSourcesInParallel ?? 6}
|
|
||||||
defaultValue={6}
|
|
||||||
minValue={1}
|
|
||||||
maxValue={20}
|
|
||||||
showSlider
|
|
||||||
stepSize={1}
|
|
||||||
dialogTitle={t('settings.server.requests.sources.parallel.label.title')}
|
|
||||||
handleUpdate={(parallelSources) => updateSetting('maxSourcesInParallel', parallelSources)}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<ListSubheader component="div" id="server-settings-extension-repos">
|
|
||||||
{t('extension.title')}
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MutableListSetting
|
|
||||||
settingName={t('extension.settings.repositories.custom.label.title')}
|
|
||||||
description={t('extension.settings.repositories.custom.label.description')}
|
|
||||||
dialogDisclaimer={
|
|
||||||
<Trans i18nKey="extension.settings.repositories.custom.label.disclaimer">
|
|
||||||
<strong>
|
|
||||||
Suwayomi does not provide any support for 3rd party repositories or extensions!
|
|
||||||
</strong>
|
|
||||||
<br />
|
|
||||||
Use with caution as there could be malicious actors making those repositories.
|
|
||||||
<br />
|
|
||||||
You as the user need to verify the security and that you trust any repository or extension.
|
|
||||||
</Trans>
|
|
||||||
}
|
|
||||||
handleChange={(repos) => {
|
|
||||||
updateSetting('extensionRepos', repos);
|
|
||||||
requestManager.clearExtensionCache();
|
|
||||||
}}
|
|
||||||
values={serverSettings?.extensionRepos}
|
|
||||||
addItemButtonTitle={t('extension.settings.repositories.custom.dialog.action.button.add')}
|
|
||||||
placeholder="https://github.com/MY_ACCOUNT/MY_REPO/tree/repo"
|
|
||||||
validateItem={(repo) =>
|
|
||||||
!!repo.match(
|
|
||||||
/https:\/\/(www\.|raw\.)?(github|githubusercontent)\.com\/([^/]+)\/([^/]+)((\/tree|\/blob)?\/([^/\n]*))?(\/([^/\n]*\.json)?)?/g,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
invalidItemError={t('extension.settings.repositories.custom.error.label.invalid_url')}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
<List
|
|
||||||
subheader={
|
|
||||||
<ListSubheader component="div" id="server-settings-requests">
|
|
||||||
{t('source.local_source.title')}
|
|
||||||
</ListSubheader>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TextSetting
|
|
||||||
settingName={t('settings.server.local_source.path.label.title')}
|
|
||||||
dialogDescription={t('settings.server.local_source.path.label.description')}
|
|
||||||
value={serverSettings?.localSourcePath}
|
|
||||||
handleChange={(path) => updateSetting('localSourcePath', path)}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
<List
|
<List
|
||||||
subheader={
|
subheader={
|
||||||
<ListSubheader component="div" id="server-settings-server-address">
|
<ListSubheader component="div" id="server-settings-server-address">
|
||||||
|
|||||||
Reference in New Issue
Block a user