Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add extension settings screen #557

Merged
merged 1 commit into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ import { LibrarySettings } from '@/screens/settings/LibrarySettings';
import { DefaultNavBar } from '@/components/navbar/DefaultNavBar';
import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
import { ServerUpdateChecker } from '@/components/settings/ServerUpdateChecker.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
import { ExtensionSettings } from '@/screens/settings/ExtensionSettings.tsx';

if (process.env.NODE_ENV !== 'production') {
// Adds messages only in a dev environment
Expand Down Expand Up @@ -98,7 +98,7 @@ export const App: React.FC = () => (
<Route path="downloadSettings" element={<DownloadSettings />} />
<Route path="backup" element={<Backup />} />
<Route path="server" element={<ServerSettings />} />
<Route path="webUI" element={<WebUISettings />} />
<Route path="extensionSettings" element={<ExtensionSettings />} />
</Route>

{/* Manga Routes */}
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,8 @@
"title": "Extension repositories"
}
}
}
},
"title": "Extension Settings"
},
"state": {
"label": {
Expand Down
2 changes: 1 addition & 1 deletion src/screens/Extensions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function Extensions() {
return (
<Stack sx={{ paddingTop: '20px' }} alignItems="center" justifyContent="center" rowGap="10px">
<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')}
</Button>
</Stack>
Expand Down
19 changes: 7 additions & 12 deletions src/screens/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import ListItem from '@mui/material/ListItem';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Switch from '@mui/material/Switch';
import FavoriteIcon from '@mui/icons-material/Favorite';
import { Link, ListItemButton, MenuItem, Select } from '@mui/material';
import { useTranslation } from 'react-i18next';
import LanguageIcon from '@mui/icons-material/Language';
Expand All @@ -27,6 +26,7 @@ import ViewModuleIcon from '@mui/icons-material/ViewModule';
import DnsIcon from '@mui/icons-material/Dns';
import WebIcon from '@mui/icons-material/Web';
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import ExploreOutlinedIcon from '@mui/icons-material/ExploreOutlined';
import { langCodeToName } from '@/util/language';
import { useLocalStorage } from '@/util/useLocalStorage';
import { ListItemLink } from '@/components/util/ListItemLink';
Expand All @@ -46,7 +46,6 @@ export function Settings() {
}, [t]);

const { darkTheme, setDarkTheme } = useContext(DarkTheme);
const [showNsfw, setShowNsfw] = useLocalStorage<boolean>('showNsfw', true);

const DEFAULT_ITEM_WIDTH = 300;
const itemWidthIcon = useMemo(() => <ViewModuleIcon />, []);
Expand Down Expand Up @@ -116,16 +115,6 @@ export function Settings() {
showSlider
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>
<ListItemIcon>
<LanguageIcon />
Expand Down Expand Up @@ -162,6 +151,12 @@ export function Settings() {
secondary={t('settings.clear_cache.label.description')}
/>
</ListItemButton>
<ListItemLink to="/settings/extensionSettings">
<ListItemIcon>
<ExploreOutlinedIcon />
</ListItemIcon>
<ListItemText primary={t('global.label.browse')} />
</ListItemLink>
<ListItemLink to="/settings/webUI">
<ListItemIcon>
<WebIcon />
Expand Down
114 changes: 114 additions & 0 deletions src/screens/settings/ExtensionSettings.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
};
87 changes: 3 additions & 84 deletions src/screens/settings/ServerSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* 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 { List, ListItem, ListItemText, Switch } from '@mui/material';
import ListSubheader from '@mui/material/ListSubheader';
Expand All @@ -16,7 +16,6 @@ import { useLocalStorage } from '@/util/useLocalStorage.tsx';
import { TextSetting } from '@/components/settings/text/TextSetting.tsx';
import { ServerSettings as GqlServerSettings } from '@/typings.ts';
import { NumberSetting } from '@/components/settings/NumberSetting.tsx';
import { MutableListSetting } from '@/components/settings/MutableListSetting.tsx';

type ServerSettingsType = Pick<
GqlServerSettings,
Expand All @@ -31,12 +30,9 @@ type ServerSettingsType = Pick<
| 'basicAuthEnabled'
| 'basicAuthUsername'
| 'basicAuthPassword'
| 'maxSourcesInParallel'
| 'localSourcePath'
| 'extensionRepos'
>;

const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsType => ({
const extractServerSettings = (settings: GqlServerSettings): ServerSettingsType => ({
ip: settings.ip,
port: settings.port,
socksProxyEnabled: settings.socksProxyEnabled,
Expand All @@ -48,9 +44,6 @@ const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsTyp
basicAuthEnabled: settings.basicAuthEnabled,
basicAuthUsername: settings.basicAuthUsername,
basicAuthPassword: settings.basicAuthPassword,
maxSourcesInParallel: settings.maxSourcesInParallel,
localSourcePath: settings.localSourcePath,
extensionRepos: settings.extensionRepos,
});

export const ServerSettings = () => {
Expand All @@ -65,7 +58,7 @@ export const ServerSettings = () => {
}, [t]);

const { data } = requestManager.useGetServerSettings();
const serverSettings = data ? extractDownloadSettings(data.settings) : undefined;
const serverSettings = data ? extractServerSettings(data.settings) : undefined;
const [mutateSettings] = requestManager.useUpdateServerSettings();

const [serverAddress, setServerAddress] = useLocalStorage<string>('serverBaseURL', '');
Expand Down Expand Up @@ -99,80 +92,6 @@ export const ServerSettings = () => {
placeholder="http://localhost:4567"
/>
</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
subheader={
<ListSubheader component="div" id="server-settings-server-address">
Expand Down