import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import IconButton from '@mui/material/IconButton'; import MenuItem from '@mui/material/MenuItem'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { useLocalizationOptions } from 'apps/dashboard/features/settings/api/useLocalizationOptions'; import Loading from 'components/loading/LoadingComponent'; import Page from 'components/Page'; import { QUERY_KEY, useConfiguration } from 'hooks/useConfiguration'; import { useSystemInfo } from 'hooks/useSystemInfo'; import globalize from 'lib/globalize'; import React, { useCallback, useEffect, useState } from 'react'; import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom'; import SearchIcon from '@mui/icons-material/Search'; import InputAdornment from '@mui/material/InputAdornment'; import FormControl from '@mui/material/FormControl'; import FormControlLabel from '@mui/material/FormControlLabel'; import Checkbox from '@mui/material/Checkbox'; import Button from '@mui/material/Button'; import Link from '@mui/material/Link'; import DirectoryBrowser from 'components/directorybrowser/directorybrowser'; import ServerConnections from 'components/ServerConnections'; import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; import { queryClient } from 'utils/query/queryClient'; import { ActionData } from 'types/actionData'; export const action = async ({ request }: ActionFunctionArgs) => { const api = ServerConnections.getCurrentApi(); if (!api) throw new Error('No Api instance available'); const { data: config } = await getConfigurationApi(api).getConfiguration(); const formData = await request.formData(); config.ServerName = formData.get('ServerName')?.toString(); config.UICulture = formData.get('UICulture')?.toString(); config.CachePath = formData.get('CachePath')?.toString(); config.MetadataPath = formData.get('MetadataPath')?.toString(); config.QuickConnectAvailable = formData.get('QuickConnectAvailable')?.toString() === 'on'; config.LibraryScanFanoutConcurrency = parseInt(formData.get('LibraryScanFanoutConcurrency')?.toString() || '0', 10); config.ParallelImageEncodingLimit = parseInt(formData.get('ParallelImageEncodingLimit')?.toString() || '0', 10); await getConfigurationApi(api) .updateConfiguration({ serverConfiguration: config }); void queryClient.invalidateQueries({ queryKey: [ QUERY_KEY ] }); return { isSaved: true }; }; export const Component = () => { const { data: config, isPending: isConfigPending, isError: isConfigError } = useConfiguration(); const { data: languageOptions, isPending: isLocalizationOptionsPending, isError: isLocalizationOptionsError } = useLocalizationOptions(); const { data: systemInfo, isPending: isSystemInfoPending, isError: isSystemInfoError } = useSystemInfo(); const navigation = useNavigation(); const actionData = useActionData() as ActionData | undefined; const isSubmitting = navigation.state === 'submitting'; const [ cachePath, setCachePath ] = useState(''); const [ metadataPath, setMetadataPath ] = useState(''); const onCachePathChange = useCallback((event: React.ChangeEvent) => { setCachePath(event.target.value); }, []); const onMetadataPathChange = useCallback((event: React.ChangeEvent) => { setMetadataPath(event.target.value); }, []); const showCachePathPicker = useCallback(() => { const picker = new DirectoryBrowser(); picker.show({ callback: function (path: string) { if (path) { setCachePath(path); } picker.close(); }, validateWriteable: true, header: globalize.translate('HeaderSelectServerCachePath'), instruction: globalize.translate('HeaderSelectServerCachePathHelp') }); }, []); const showMetadataPathPicker = useCallback(() => { const picker = new DirectoryBrowser(); picker.show({ path: metadataPath, callback: function (path: string) { if (path) { setMetadataPath(path); } picker.close(); }, validateWriteable: true, header: globalize.translate('HeaderSelectMetadataPath'), instruction: globalize.translate('HeaderSelectMetadataPathHelp') }); }, [metadataPath]); useEffect(() => { if (!isSystemInfoPending && !isSystemInfoError) { setCachePath(systemInfo.CachePath); setMetadataPath(systemInfo.InternalMetadataPath); } }, [systemInfo, isSystemInfoPending, isSystemInfoError]); if (isConfigPending || isLocalizationOptionsPending || isSystemInfoPending) { return ; } return ( {isConfigError || isLocalizationOptionsError || isSystemInfoError ? ( {globalize.translate('SettingsPageLoadError')} ) : (
{globalize.translate('Settings')} {!isSubmitting && actionData?.isSaved && ( {globalize.translate('SettingsSaved')} )} {globalize.translate('LabelDisplayLanguageHelp')} {globalize.translate('LearnHowYouCanContribute')} )} defaultValue={config.UICulture} slotProps={{ formHelperText: { component: Stack } }} > {languageOptions.map((language) => {language.Name} )} {globalize.translate('HeaderPaths')} ) } }} /> ) } }} /> {globalize.translate('QuickConnect')} } label={globalize.translate('EnableQuickConnect')} /> {globalize.translate('HeaderPerformance')}
)}
); }; Component.displayName = 'SettingsPage';