diff --git a/src/App.tsx b/src/App.tsx index 62b198d2..6f011f1c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,6 +33,7 @@ import '@/i18n'; 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'; if (__DEV__) { // Adds messages only in a dev environment @@ -66,6 +67,7 @@ export const App: React.FC = () => ( } /> } /> } /> + } /> {/* Manga Routes */} diff --git a/src/components/settings/ServerDirSetting.tsx b/src/components/settings/ServerDirSetting.tsx deleted file mode 100644 index 00afa44b..00000000 --- a/src/components/settings/ServerDirSetting.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 { Button, Dialog, DialogTitle, ListItemText } from '@mui/material'; -import DialogContent from '@mui/material/DialogContent'; -import TextField from '@mui/material/TextField'; -import DialogActions from '@mui/material/DialogActions'; -import ListItemButton from '@mui/material/ListItemButton'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import DialogContentText from '@mui/material/DialogContentText'; - -export const ServerDirSetting = ({ - settingName, - dialogDescription, - dirPath, - handlePathChange, -}: { - settingName: string; - dialogDescription: string; - dirPath?: string; - handlePathChange: (path: string) => void; -}) => { - const { t } = useTranslation(); - - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [dialogDirPath, setDialogDirPath] = useState(dirPath ?? ''); - - useEffect(() => { - if (!dirPath) { - return; - } - - setDialogDirPath(dirPath); - }, [dirPath]); - - const closeDialog = () => { - setIsDialogOpen(false); - }; - - const updateSetting = () => { - closeDialog(); - handlePathChange(dialogDirPath); - }; - - return ( - <> - setIsDialogOpen(true)}> - - - - - - {settingName} - {dialogDescription} - setDialogDirPath(e.target.value)} - /> - - - - - - - - ); -}; diff --git a/src/components/settings/TextSetting.tsx b/src/components/settings/TextSetting.tsx new file mode 100644 index 00000000..dc3e9a3f --- /dev/null +++ b/src/components/settings/TextSetting.tsx @@ -0,0 +1,117 @@ +/* + * 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 { Button, Dialog, DialogTitle, InputAdornment, ListItemText } from '@mui/material'; +import DialogContent from '@mui/material/DialogContent'; +import TextField from '@mui/material/TextField'; +import DialogActions from '@mui/material/DialogActions'; +import ListItemButton from '@mui/material/ListItemButton'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import DialogContentText from '@mui/material/DialogContentText'; +import IconButton from '@mui/material/IconButton'; +import { Visibility, VisibilityOff } from '@mui/icons-material'; + +export const TextSetting = ({ + settingName, + dialogDescription, + value, + handleChange, + isPassword = false, + placeholder = '', +}: { + settingName: string; + dialogDescription?: string; + value?: string; + handleChange: (value: string) => void; + isPassword?: boolean; + placeholder?: string; +}) => { + const { t } = useTranslation(); + + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [dialogValue, setDialogValue] = useState(value ?? ''); + const [showPassword, setShowPassword] = useState(false); + + const handleClickShowPassword = () => setShowPassword((show) => !show); + + useEffect(() => { + if (!value) { + return; + } + + setDialogValue(value); + }, [value]); + + const closeDialog = (resetValue: boolean = true) => { + if (resetValue) { + setDialogValue(value ?? ''); + } + + setShowPassword(false); + setIsDialogOpen(false); + }; + + const updateSetting = () => { + closeDialog(false); + handleChange(dialogValue); + }; + + return ( + <> + setIsDialogOpen(true)}> + + + + closeDialog()} fullWidth> + + {settingName} + {!!dialogDescription && ( + {dialogDescription} + )} + setDialogValue(e.target.value)} + InputProps={{ + endAdornment: isPassword ? ( + + + {showPassword ? : } + + + ) : null, + }} + /> + + + + + + + + ); +}; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 1d72108d..4ad1808a 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -283,13 +283,16 @@ }, "label": { "browse": "Browse", + "client": "Client", "display": "Display", "filter": "Filter", "loading": "Loading…", "never": "Never", "none": "None", + "password": "Password", "sort": "Sort", - "unknown": "Unknown" + "unknown": "Unknown", + "username": "Username" }, "language": { "label": { @@ -488,7 +491,6 @@ "build_time": "Build time", "discord": "Discord", "github": "GitHub", - "server": "Server", "server_address": "Server address", "server_version": "Server version" }, @@ -534,11 +536,86 @@ "show_nsfw": "Show NSFW", "show_nsfw_description": "Hide NSFW extensions and sources" }, - "server_address": { - "dialog": { - "label": { - "enter_address": "Enter server address" + "server": { + "address": { + "client": { + "dialog": { + "label": { + "enter_address": "Enter server address" + } + }, + "label": { + "description": "The address the client will use to connect to the server" + } + }, + "server": { + "label": { + "ip": "IP", + "port": "Port" + }, + "title": "Server bindings" } + }, + "auth": { + "basic": { + "label": { + "enable": "Use basic authentication", + "password": "Basic authentication $t(global.label.password, lowercase)", + "username": "Basic authentication $t(global.label.username, lowercase)" + } + }, + "title": "Authentication" + }, + "local_source": { + "path": { + "label": { + "description": "The path to the directory on the server where local source files are saved in", + "title": "Local source location" + } + } + }, + "misc": { + "log_level": { + "graphql": { + "label": { + "description": "This includes logs with non privacy safe information", + "title": "Enable graphql debug logs" + } + }, + "label": { + "server": "Enable debug logs" + } + }, + "title": "Misc", + "tray_icon": { + "label": { + "description": "This icon will be shown on the system that is running the server", + "title": "Show icon in system tray" + } + } + }, + "requests": { + "sources": { + "parallel": { + "label": { + "title": "Parallel source requests", + "value": "{{value}} $t(source.title)" + } + } + }, + "title": "Requests" + }, + "socks_proxy": { + "label": { + "enable": "Use SOCKS5 proxy", + "host": "SOCKS5 host", + "port": "SOCKS5 port" + }, + "title": "SOCKS proxy" + }, + "title": { + "server": "Server", + "settings": "Server Settings" } }, "title": "Settings" diff --git a/src/lib/graphql/Fragments.ts b/src/lib/graphql/Fragments.ts index 03589f48..48a54814 100644 --- a/src/lib/graphql/Fragments.ts +++ b/src/lib/graphql/Fragments.ts @@ -455,5 +455,8 @@ export const SERVER_SETTINGS = gql` backupTime backupInterval backupTTL + + # local source + localSourcePath } `; diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index e1ac25dc..7c88c79a 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -2108,7 +2108,7 @@ export type WebuiUpdateInfoFragment = { __typename?: 'WebUIUpdateInfo', channel: export type WebuiUpdateStatusFragment = { __typename?: 'WebUIUpdateStatus', progress: number, state: UpdateState, info: { __typename?: 'WebUIUpdateInfo', channel: string, tag: string, updateAvailable: boolean } }; -export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number }; +export type ServerSettingsFragment = { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string }; export type CreateBackupMutationVariables = Exact<{ input: CreateBackupInput; @@ -2388,14 +2388,14 @@ export type ResetServerSettingsMutationVariables = Exact<{ }>; -export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } }; +export type ResetServerSettingsMutation = { __typename?: 'Mutation', resetSettings: { __typename?: 'ResetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } } }; export type UpdateServerSettingsMutationVariables = Exact<{ input: SetSettingsInput; }>; -export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } } }; +export type UpdateServerSettingsMutation = { __typename?: 'Mutation', setSettings: { __typename?: 'SetSettingsPayload', clientMutationId?: string | null, settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } } }; export type GetSourceMangasFetchMutationVariables = Exact<{ input: FetchSourceMangaInput; @@ -2591,7 +2591,7 @@ export type GetWebuiUpdateStatusQuery = { __typename?: 'Query', getWebUIUpdateSt export type GetServerSettingsQueryVariables = Exact<{ [key: string]: never; }>; -export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number } }; +export type GetServerSettingsQuery = { __typename?: 'Query', settings: { __typename?: 'SettingsType', ip: string, port: number, socksProxyEnabled: boolean, socksProxyHost: string, socksProxyPort: string, webUIFlavor: WebUiFlavor, initialOpenInBrowserEnabled: boolean, webUIInterface: WebUiInterface, electronPath: string, webUIChannel: WebUiChannel, webUIUpdateCheckInterval: number, downloadAsCbz: boolean, downloadsPath: string, autoDownloadNewChapters: boolean, excludeEntryWithUnreadChapters: boolean, autoDownloadAheadLimit: number, maxSourcesInParallel: number, excludeUnreadChapters: boolean, excludeNotStarted: boolean, excludeCompleted: boolean, globalUpdateInterval: number, updateMangas: boolean, basicAuthEnabled: boolean, basicAuthUsername: string, basicAuthPassword: string, debugLogsEnabled: boolean, gqlDebugLogsEnabled: boolean, systemTrayEnabled: boolean, backupPath: string, backupTime: string, backupInterval: number, backupTTL: number, localSourcePath: string } }; export type GetSourceQueryVariables = Exact<{ id: Scalars['LongString']['input']; diff --git a/src/screens/Settings.tsx b/src/screens/Settings.tsx index dbe240e8..783363da 100644 --- a/src/screens/Settings.tsx +++ b/src/screens/Settings.tsx @@ -6,14 +6,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import { useContext, useEffect, useMemo, useState } from 'react'; +import { useContext, useEffect, useMemo } from 'react'; import AutoStoriesIcon from '@mui/icons-material/AutoStories'; import List from '@mui/material/List'; import ListAltIcon from '@mui/icons-material/ListAlt'; import BackupIcon from '@mui/icons-material/Backup'; import Brightness6Icon from '@mui/icons-material/Brightness6'; -import DnsIcon from '@mui/icons-material/Dns'; -import EditIcon from '@mui/icons-material/Edit'; import InfoIcon from '@mui/icons-material/Info'; import CachedIcon from '@mui/icons-material/Cached'; import ListItem from '@mui/material/ListItem'; @@ -21,21 +19,14 @@ import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; import Switch from '@mui/material/Switch'; -import Button from '@mui/material/Button'; -import IconButton from '@mui/material/IconButton'; -import Dialog from '@mui/material/Dialog'; -import DialogActions from '@mui/material/DialogActions'; -import DialogContent from '@mui/material/DialogContent'; -import DialogContentText from '@mui/material/DialogContentText'; -import TextField from '@mui/material/TextField'; import FavoriteIcon from '@mui/icons-material/Favorite'; -import { Link, MenuItem, Select, Tooltip } from '@mui/material'; +import { Link, MenuItem, Select } from '@mui/material'; import { useTranslation } from 'react-i18next'; import LanguageIcon from '@mui/icons-material/Language'; import CollectionsOutlinedBookmarkIcon from '@mui/icons-material/CollectionsBookmarkOutlined'; import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined'; import ViewModuleIcon from '@mui/icons-material/ViewModule'; -import { requestManager } from '@/lib/requests/RequestManager.ts'; +import DnsIcon from '@mui/icons-material/Dns'; import { langCodeToName } from '@/util/language'; import { useLocalStorage } from '@/util/useLocalStorage'; import { ListItemLink } from '@/components/util/ListItemLink'; @@ -53,191 +44,133 @@ export function Settings() { }, [t]); const { darkTheme, setDarkTheme } = useContext(DarkTheme); - const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', ''); const [showNsfw, setShowNsfw] = useLocalStorage('showNsfw', true); const [useCache, setUseCache] = useLocalStorage('useCache', true); - const [dialogOpen, setDialogOpen] = useState(false); - const [dialogValue, setDialogValue] = useState(serverAddress); const DEFAULT_ITEM_WIDTH = 300; const itemWidthIcon = useMemo(() => , []); const [itemWidth, setItemWidth] = useLocalStorage('ItemWidth', DEFAULT_ITEM_WIDTH); - const handleDialogOpen = () => { - setDialogValue(serverAddress); - setDialogOpen(true); - }; - - const handleDialogCancel = () => { - setDialogOpen(false); - }; - - const handleDialogSubmit = () => { - setDialogOpen(false); - const serverBaseUrl = dialogValue.replaceAll(/(\/)+$/g, ''); - setServerAddress(serverBaseUrl); - requestManager.updateClient({ baseURL: serverBaseUrl }); - }; - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setDarkTheme(!darkTheme)} /> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setDarkTheme(!darkTheme)} /> + + + + + + + + - - - - - - - setShowNsfw(!showNsfw)} /> - - - - - - - - - setUseCache(!useCache)} /> - - - - - - - - {t('settings.label.language_description')} - - {t('global.language.title.weblate')} - - - } - /> - - - - - - - - - - - - { - handleDialogOpen(); - }} - size="large" - > - - - - - - - - - - - - - - - - {t('settings.server_address.dialog.label.enter_address')} - setDialogValue(e.target.value)} - /> - - - - - - - + + setShowNsfw(!showNsfw)} /> + + + + + + + + + setUseCache(!useCache)} /> + + + + + + + + {t('settings.label.language_description')} + + {t('global.language.title.weblate')} + + + } + /> + + + + + + + + + + + + + + + + + ); } diff --git a/src/screens/settings/About.tsx b/src/screens/settings/About.tsx index 85cad6e1..3d0f5fcc 100644 --- a/src/screens/settings/About.tsx +++ b/src/screens/settings/About.tsx @@ -45,7 +45,7 @@ export function About() { diff --git a/src/screens/settings/Backup.tsx b/src/screens/settings/Backup.tsx index c0b93392..47524625 100644 --- a/src/screens/settings/Backup.tsx +++ b/src/screens/settings/Backup.tsx @@ -21,7 +21,7 @@ import { ListItemLink } from '@/components/util/ListItemLink'; import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext'; import { BackupRestoreState } from '@/lib/graphql/generated/graphql.ts'; import { Progress } from '@/components/util/Progress.tsx'; -import { ServerDirSetting } from '@/components/settings/ServerDirSetting.tsx'; +import { TextSetting } from '@/components/settings/TextSetting.tsx'; import { NumberSetting } from '@/components/settings/NumberSetting.tsx'; import { TimeSetting } from '@/components/settings/TimeSetting.tsx'; import { ServerSettings } from '@/typings.ts'; @@ -188,11 +188,11 @@ export function Backup() { } > - updateSetting('backupPath', path)} + value={backupSettings?.backupPath} + handleChange={(path) => updateSetting('backupPath', path)} /> { return ( - updateSetting('downloadsPath', path)} + value={downloadSettings?.downloadsPath} + handleChange={(path) => updateSetting('downloadsPath', path)} /> diff --git a/src/screens/settings/ServerSettings.tsx b/src/screens/settings/ServerSettings.tsx new file mode 100644 index 00000000..58feb5a7 --- /dev/null +++ b/src/screens/settings/ServerSettings.tsx @@ -0,0 +1,272 @@ +/* + * 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 { useTranslation } from 'react-i18next'; +import { useContext, useEffect } from 'react'; +import { List, ListItem, ListItemText, Switch } from '@mui/material'; +import ListSubheader from '@mui/material/ListSubheader'; +import ListItemSecondaryAction from '@mui/material/ListItemSecondaryAction'; +import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; +import { requestManager } from '@/lib/requests/RequestManager.ts'; +import { useLocalStorage } from '@/util/useLocalStorage.tsx'; +import { TextSetting } from '@/components/settings/TextSetting.tsx'; +import { ServerSettings as GqlServerSettings } from '@/typings.ts'; +import { NumberSetting } from '@/components/settings/NumberSetting.tsx'; + +type ServerSettingsType = Pick< + GqlServerSettings, + | 'ip' + | 'port' + | 'socksProxyEnabled' + | 'socksProxyHost' + | 'socksProxyPort' + | 'debugLogsEnabled' + | 'gqlDebugLogsEnabled' + | 'systemTrayEnabled' + | 'basicAuthEnabled' + | 'basicAuthUsername' + | 'basicAuthPassword' + | 'maxSourcesInParallel' + | 'localSourcePath' +>; + +const extractDownloadSettings = (settings: GqlServerSettings): ServerSettingsType => ({ + ip: settings.ip, + port: settings.port, + socksProxyEnabled: settings.socksProxyEnabled, + socksProxyHost: settings.socksProxyHost, + socksProxyPort: settings.socksProxyPort, + debugLogsEnabled: settings.debugLogsEnabled, + gqlDebugLogsEnabled: settings.gqlDebugLogsEnabled, + systemTrayEnabled: settings.systemTrayEnabled, + basicAuthEnabled: settings.basicAuthEnabled, + basicAuthUsername: settings.basicAuthUsername, + basicAuthPassword: settings.basicAuthPassword, + maxSourcesInParallel: settings.maxSourcesInParallel, + localSourcePath: settings.localSourcePath, +}); + +export const ServerSettings = () => { + const { t } = useTranslation(); + const { setTitle, setAction } = useContext(NavBarContext); + + useSetDefaultBackTo('settings'); + + useEffect(() => { + setTitle(t('settings.server.title.settings')); + setAction(null); + }, [t]); + + const { data } = requestManager.useGetServerSettings(); + const serverSettings = data ? extractDownloadSettings(data.settings) : undefined; + const [mutateSettings] = requestManager.useUpdateServerSettings(); + + const [serverAddress, setServerAddress] = useLocalStorage('serverBaseURL', ''); + + const handleServerAddressChange = (address: string) => { + const serverBaseUrl = address.replaceAll(/(\/)+$/g, ''); + setServerAddress(serverBaseUrl); + requestManager.updateClient({ baseURL: serverBaseUrl }); + }; + + const updateSetting = ( + setting: Setting, + value: ServerSettingsType[Setting], + ) => { + mutateSettings({ variables: { input: { settings: { [setting]: value } } } }); + }; + + return ( + + + {t('global.label.client')} + + } + > + + + + {t('settings.server.requests.title')} + + } + > + updateSetting('maxSourcesInParallel', parallelSources)} + /> + + + {t('settings.server.requests.title')} + + } + > + updateSetting('localSourcePath', path)} + /> + + + {t('settings.server.address.server.title')} + + } + > + updateSetting('ip', ip)} + value={serverSettings?.ip} + placeholder="0.0.0.0" + /> + updateSetting('port', port)} + value={serverSettings?.port ?? 4567} + defaultValue={4567} + valueUnit={t('settings.server.address.server.label.port')} + /> + + + {t('settings.server.socks_proxy.title')} + + } + > + + + + updateSetting('socksProxyEnabled', e.target.checked)} + /> + + + {!!serverSettings?.socksProxyEnabled && ( + <> + updateSetting('socksProxyHost', proxyHost)} + /> + updateSetting('socksProxyPort', proxyPort)} + /> + + )} + + + {t('settings.server.auth.title')} + + } + > + + + + updateSetting('basicAuthEnabled', e.target.checked)} + /> + + + {!!serverSettings?.basicAuthEnabled && ( + <> + updateSetting('basicAuthUsername', authUsername)} + /> + updateSetting('basicAuthPassword', authPassword)} + /> + + )} + + + {t('settings.server.misc.title')} + + } + > + + + + updateSetting('debugLogsEnabled', e.target.checked)} + /> + + + + + + updateSetting('gqlDebugLogsEnabled', e.target.checked)} + /> + + + + + + updateSetting('systemTrayEnabled', e.target.checked)} + /> + + + + + ); +};