mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #5076 from grhallenbeck/dev-react-display-settings
feat: (preferences) migrate user display settings to react
This commit is contained in:
commit
4a36f7571b
18 changed files with 1007 additions and 23 deletions
|
@ -8,5 +8,6 @@ export const ASYNC_USER_ROUTES: AsyncRoute[] = [
|
|||
{ path: 'movies.html', page: 'movies', type: AsyncRouteType.Experimental },
|
||||
{ path: 'tv.html', page: 'shows', type: AsyncRouteType.Experimental },
|
||||
{ path: 'music.html', page: 'music', type: AsyncRouteType.Experimental },
|
||||
{ path: 'livetv.html', page: 'livetv', type: AsyncRouteType.Experimental }
|
||||
{ path: 'livetv.html', page: 'livetv', type: AsyncRouteType.Experimental },
|
||||
{ path: 'mypreferencesdisplay.html', page: 'user/display', type: AsyncRouteType.Experimental }
|
||||
];
|
||||
|
|
|
@ -25,12 +25,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
|
|||
controller: 'user/controls/index',
|
||||
view: 'user/controls/index.html'
|
||||
}
|
||||
}, {
|
||||
path: 'mypreferencesdisplay.html',
|
||||
pageProps: {
|
||||
controller: 'user/display/index',
|
||||
view: 'user/display/index.html'
|
||||
}
|
||||
}, {
|
||||
path: 'mypreferenceshome.html',
|
||||
pageProps: {
|
||||
|
|
203
src/apps/experimental/routes/user/display/DisplayPreferences.tsx
Normal file
203
src/apps/experimental/routes/user/display/DisplayPreferences.tsx
Normal file
|
@ -0,0 +1,203 @@
|
|||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
import { useScreensavers } from './hooks/useScreensavers';
|
||||
import { useServerThemes } from './hooks/useServerThemes';
|
||||
|
||||
interface DisplayPreferencesProps {
|
||||
onChange: (event: SelectChangeEvent | React.SyntheticEvent) => void;
|
||||
values: DisplaySettingsValues;
|
||||
}
|
||||
|
||||
export function DisplayPreferences({ onChange, values }: Readonly<DisplayPreferencesProps>) {
|
||||
const { user } = useApi();
|
||||
const { screensavers } = useScreensavers();
|
||||
const { themes } = useServerThemes();
|
||||
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
|
||||
|
||||
{ appHost.supports('displaymode') && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-layout-label'>{globalize.translate('LabelDisplayMode')}</InputLabel>
|
||||
<Select
|
||||
aria-describedby='display-settings-layout-description'
|
||||
inputProps={{
|
||||
name: 'layout'
|
||||
}}
|
||||
labelId='display-settings-layout-label'
|
||||
onChange={onChange}
|
||||
value={values.layout}
|
||||
>
|
||||
<MenuItem value='auto'>{globalize.translate('Auto')}</MenuItem>
|
||||
<MenuItem value='desktop'>{globalize.translate('Desktop')}</MenuItem>
|
||||
<MenuItem value='mobile'>{globalize.translate('Mobile')}</MenuItem>
|
||||
<MenuItem value='tv'>{globalize.translate('TV')}</MenuItem>
|
||||
<MenuItem value='experimental'>{globalize.translate('Experimental')}</MenuItem>
|
||||
</Select>
|
||||
<FormHelperText component={Stack} id='display-settings-layout-description'>
|
||||
<span>{globalize.translate('DisplayModeHelp')}</span>
|
||||
<span>{globalize.translate('LabelPleaseRestart')}</span>
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) }
|
||||
|
||||
{ themes.length > 0 && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-theme-label'>{globalize.translate('LabelTheme')}</InputLabel>
|
||||
<Select
|
||||
inputProps={{
|
||||
name: 'theme'
|
||||
}}
|
||||
labelId='display-settings-theme-label'
|
||||
onChange={onChange}
|
||||
value={values.theme}
|
||||
>
|
||||
{ ...themes.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>{name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) }
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-disable-css-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.disableCustomCss}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('DisableCustomCss')}
|
||||
name='disableCustomCss'
|
||||
/>
|
||||
<FormHelperText id='display-settings-disable-css-description'>
|
||||
{globalize.translate('LabelDisableCustomCss')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
aria-describedby='display-settings-custom-css-description'
|
||||
value={values.customCss}
|
||||
label={globalize.translate('LabelCustomCss')}
|
||||
multiline
|
||||
name='customCss'
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormHelperText id='display-settings-custom-css-description'>
|
||||
{globalize.translate('LabelLocalCustomCss')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
{ themes.length > 0 && user?.Policy?.IsAdministrator && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-dashboard-theme-label'>{globalize.translate('LabelDashboardTheme')}</InputLabel>
|
||||
<Select
|
||||
inputProps={{
|
||||
name: 'dashboardTheme'
|
||||
}}
|
||||
labelId='display-settings-dashboard-theme-label'
|
||||
onChange={ onChange }
|
||||
value={ values.dashboardTheme }
|
||||
>
|
||||
{ ...themes.map(({ id, name }) => (
|
||||
<MenuItem key={ id } value={ id }>{ name }</MenuItem>
|
||||
)) }
|
||||
</Select>
|
||||
</FormControl>
|
||||
) }
|
||||
|
||||
{ screensavers.length > 0 && appHost.supports('screensaver') && (
|
||||
<Fragment>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-screensaver-label'>{globalize.translate('LabelScreensaver')}</InputLabel>
|
||||
<Select
|
||||
inputProps={{
|
||||
name: 'screensaver'
|
||||
}}
|
||||
labelId='display-settings-screensaver-label'
|
||||
onChange={onChange}
|
||||
value={values.screensaver}
|
||||
>
|
||||
{ ...screensavers.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>{name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
aria-describedby='display-settings-screensaver-interval-description'
|
||||
value={values.screensaverInterval}
|
||||
inputProps={{
|
||||
inputMode: 'numeric',
|
||||
max: '3600',
|
||||
min: '1',
|
||||
pattern: '[0-9]',
|
||||
required: true,
|
||||
step: '1',
|
||||
type: 'number'
|
||||
}}
|
||||
label={globalize.translate('LabelBackdropScreensaverInterval')}
|
||||
name='screensaverInterval'
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormHelperText id='display-settings-screensaver-interval-description'>
|
||||
{globalize.translate('LabelBackdropScreensaverIntervalHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Fragment>
|
||||
) }
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-faster-animations-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableFasterAnimation}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableFasterAnimations')}
|
||||
name='enableFasterAnimation'
|
||||
/>
|
||||
<FormHelperText id='display-settings-faster-animations-description'>
|
||||
{globalize.translate('EnableFasterAnimationsHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-blurhash-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableBlurHash}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableBlurHash')}
|
||||
name='enableBlurHash'
|
||||
/>
|
||||
<FormHelperText id='display-settings-blurhash-description'>
|
||||
{globalize.translate('EnableBlurHashHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
|
||||
interface ItemDetailPreferencesProps {
|
||||
onChange: (event: React.SyntheticEvent) => void;
|
||||
values: DisplaySettingsValues;
|
||||
}
|
||||
|
||||
export function ItemDetailPreferences({ onChange, values }: Readonly<ItemDetailPreferencesProps>) {
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Typography variant='h2'>{globalize.translate('ItemDetails')}</Typography>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-item-details-banner-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableItemDetailsBanner}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableDetailsBanner')}
|
||||
name='enableItemDetailsBanner'
|
||||
/>
|
||||
<FormHelperText id='display-settings-item-details-banner-description'>
|
||||
{globalize.translate('EnableDetailsBannerHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
);
|
||||
}
|
114
src/apps/experimental/routes/user/display/LibraryPreferences.tsx
Normal file
114
src/apps/experimental/routes/user/display/LibraryPreferences.tsx
Normal file
|
@ -0,0 +1,114 @@
|
|||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
|
||||
interface LibraryPreferencesProps {
|
||||
onChange: (event: React.SyntheticEvent) => void;
|
||||
values: DisplaySettingsValues;
|
||||
}
|
||||
|
||||
export function LibraryPreferences({ onChange, values }: Readonly<LibraryPreferencesProps>) {
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('HeaderLibraries')}</Typography>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
aria-describedby='display-settings-lib-pagesize-description'
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
inputMode: 'numeric',
|
||||
max: '1000',
|
||||
min: '0',
|
||||
pattern: '[0-9]',
|
||||
required: true,
|
||||
step: '1'
|
||||
}}
|
||||
value={values.libraryPageSize}
|
||||
label={globalize.translate('LabelLibraryPageSize')}
|
||||
name='libraryPageSize'
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormHelperText id='display-settings-lib-pagesize-description'>
|
||||
{globalize.translate('LabelLibraryPageSizeHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-lib-backdrops-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableLibraryBackdrops}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('Backdrops')}
|
||||
name='enableLibraryBackdrops'
|
||||
/>
|
||||
<FormHelperText id='display-settings-lib-backdrops-description'>
|
||||
{globalize.translate('EnableBackdropsHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-lib-theme-songs-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableLibraryThemeSongs}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ThemeSongs')}
|
||||
name='enableLibraryThemeSongs'
|
||||
/>
|
||||
<FormHelperText id='display-settings-lib-theme-songs-description'>
|
||||
{globalize.translate('EnableThemeSongsHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-lib-theme-videos-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableLibraryThemeVideos}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('ThemeVideos')}
|
||||
name='enableLibraryThemeVideos'
|
||||
/>
|
||||
<FormHelperText id='display-settings-lib-theme-videos-description'>
|
||||
{globalize.translate('EnableThemeVideosHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-show-missing-episodes-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.displayMissingEpisodes}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('DisplayMissingEpisodesWithinSeasons')}
|
||||
name='displayMissingEpisodes'
|
||||
/>
|
||||
<FormHelperText id='display-settings-show-missing-episodes-description'>
|
||||
{globalize.translate('DisplayMissingEpisodesWithinSeasonsHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import FormControl from '@mui/material/FormControl';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import Link from '@mui/material/Link';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Select, { SelectChangeEvent } from '@mui/material/Select';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import datetime from 'scripts/datetime';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from './constants';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
|
||||
interface LocalizationPreferencesProps {
|
||||
onChange: (event: SelectChangeEvent) => void;
|
||||
values: DisplaySettingsValues;
|
||||
}
|
||||
|
||||
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
|
||||
if (!appHost.supports('displaylanguage') && !datetime.supportsLocalization()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
|
||||
|
||||
{ appHost.supports('displaylanguage') && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-language-label'>{globalize.translate('LabelDisplayLanguage')}</InputLabel>
|
||||
<Select
|
||||
aria-describedby='display-settings-language-description'
|
||||
inputProps={{
|
||||
name: 'language'
|
||||
}}
|
||||
labelId='display-settings-language-label'
|
||||
onChange={onChange}
|
||||
value={values.language}
|
||||
>
|
||||
{ ...LANGUAGE_OPTIONS.map(({ value, label }) => (
|
||||
<MenuItem key={value } value={value}>{ label }</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<FormHelperText component={Stack} id='display-settings-language-description'>
|
||||
<span>{globalize.translate('LabelDisplayLanguageHelp')}</span>
|
||||
{ appHost.supports('externallinks') && (
|
||||
<Link
|
||||
href='https://github.com/jellyfin/jellyfin'
|
||||
rel='noopener noreferrer'
|
||||
target='_blank'
|
||||
>
|
||||
{globalize.translate('LearnHowYouCanContribute')}
|
||||
</Link>
|
||||
) }
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
) }
|
||||
|
||||
{ datetime.supportsLocalization() && (
|
||||
<FormControl fullWidth>
|
||||
<InputLabel id='display-settings-locale-label'>{globalize.translate('LabelDateTimeLocale')}</InputLabel>
|
||||
<Select
|
||||
inputProps={{
|
||||
name: 'dateTimeLocale'
|
||||
}}
|
||||
labelId='display-settings-locale-label'
|
||||
onChange={onChange}
|
||||
value={values.dateTimeLocale}
|
||||
>
|
||||
{...DATE_LOCALE_OPTIONS.map(({ value, label }) => (
|
||||
<MenuItem key={value} value={value}>{label}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
) }
|
||||
</Stack>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
import Checkbox from '@mui/material/Checkbox';
|
||||
import FormControl from '@mui/material/FormControl';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import React from 'react';
|
||||
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
|
||||
interface NextUpPreferencesProps {
|
||||
onChange: (event: React.SyntheticEvent) => void;
|
||||
values: DisplaySettingsValues;
|
||||
}
|
||||
|
||||
export function NextUpPreferences({ onChange, values }: Readonly<NextUpPreferencesProps>) {
|
||||
return (
|
||||
<Stack spacing={3}>
|
||||
<Typography variant='h2'>{globalize.translate('NextUp')}</Typography>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<TextField
|
||||
aria-describedby='display-settings-max-days-next-up-description'
|
||||
value={values.maxDaysForNextUp}
|
||||
inputProps={{
|
||||
type: 'number',
|
||||
inputMode: 'numeric',
|
||||
max: '1000',
|
||||
min: '0',
|
||||
pattern: '[0-9]',
|
||||
required: true,
|
||||
step: '1'
|
||||
}}
|
||||
label={globalize.translate('LabelMaxDaysForNextUp')}
|
||||
name='maxDaysForNextUp'
|
||||
onChange={onChange}
|
||||
/>
|
||||
<FormHelperText id='display-settings-max-days-next-up-description'>
|
||||
{globalize.translate('LabelMaxDaysForNextUpHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-next-up-rewatching-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.enableRewatchingInNextUp}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('EnableRewatchingNextUp')}
|
||||
name='enableRewatchingInNextUp'
|
||||
/>
|
||||
<FormHelperText id='display-settings-next-up-rewatching-description'>
|
||||
{globalize.translate('EnableRewatchingNextUpHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<FormControlLabel
|
||||
aria-describedby='display-settings-next-up-images-description'
|
||||
control={
|
||||
<Checkbox
|
||||
checked={values.episodeImagesInNextUp}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
label={globalize.translate('UseEpisodeImagesInNextUp')}
|
||||
name='episodeImagesInNextUp'
|
||||
/>
|
||||
<FormHelperText id='display-settings-next-up-images-description'>
|
||||
{globalize.translate('UseEpisodeImagesInNextUpHelp')}
|
||||
</FormHelperText>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
);
|
||||
}
|
79
src/apps/experimental/routes/user/display/constants.ts
Normal file
79
src/apps/experimental/routes/user/display/constants.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import globalize from 'scripts/globalize';
|
||||
|
||||
export const LANGUAGE_OPTIONS = [
|
||||
{ value: 'auto', label: globalize.translate('Auto') },
|
||||
{ value: 'af', label: 'Afrikaans' },
|
||||
{ value: 'ar', label: 'العربية' },
|
||||
{ value: 'be-BY', label: 'Беларуская' },
|
||||
{ value: 'bg-BG', label: 'Български' },
|
||||
{ value: 'bn_BD', label: 'বাংলা (বাংলাদেশ)' },
|
||||
{ value: 'ca', label: 'Català' },
|
||||
{ value: 'cs', label: 'Čeština' },
|
||||
{ value: 'cy', label: 'Cymraeg' },
|
||||
{ value: 'da', label: 'Dansk' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
{ value: 'el', label: 'Ελληνικά' },
|
||||
{ value: 'en-GB', label: 'English (United Kingdom)' },
|
||||
{ value: 'en-US', label: 'English' },
|
||||
{ value: 'eo', label: 'Esperanto' },
|
||||
{ value: 'es', label: 'Español' },
|
||||
{ value: 'es_419', label: 'Español americano' },
|
||||
{ value: 'es-AR', label: 'Español (Argentina)' },
|
||||
{ value: 'es_DO', label: 'Español (Dominicana)' },
|
||||
{ value: 'es-MX', label: 'Español (México)' },
|
||||
{ value: 'et', label: 'Eesti' },
|
||||
{ value: 'eu', label: 'Euskara' },
|
||||
{ value: 'fa', label: 'فارسی' },
|
||||
{ value: 'fi', label: 'Suomi' },
|
||||
{ value: 'fil', label: 'Filipino' },
|
||||
{ value: 'fr', label: 'Français' },
|
||||
{ value: 'fr-CA', label: 'Français (Canada)' },
|
||||
{ value: 'gl', label: 'Galego' },
|
||||
{ value: 'gsw', label: 'Schwiizerdütsch' },
|
||||
{ value: 'he', label: 'עִבְרִית' },
|
||||
{ value: 'hi-IN', label: 'हिन्दी' },
|
||||
{ value: 'hr', label: 'Hrvatski' },
|
||||
{ value: 'hu', label: 'Magyar' },
|
||||
{ value: 'id', label: 'Bahasa Indonesia' },
|
||||
{ value: 'is-IS', label: 'Íslenska' },
|
||||
{ value: 'it', label: 'Italiano' },
|
||||
{ value: 'ja', label: '日本語' },
|
||||
{ value: 'kk', label: 'Qazaqşa' },
|
||||
{ value: 'ko', label: '한국어' },
|
||||
{ value: 'lt-LT', label: 'Lietuvių' },
|
||||
{ value: 'lv', label: 'Latviešu' },
|
||||
{ value: 'mk', label: 'Македонски' },
|
||||
{ value: 'ml', label: 'മലയാളം' },
|
||||
{ value: 'mr', label: 'मराठी' },
|
||||
{ value: 'ms', label: 'Bahasa Melayu' },
|
||||
{ value: 'nb', label: 'Norsk bokmål' },
|
||||
{ value: 'ne', label: 'नेपाली' },
|
||||
{ value: 'nl', label: 'Nederlands' },
|
||||
{ value: 'nn', label: 'Norsk nynorsk' },
|
||||
{ value: 'pa', label: 'ਪੰਜਾਬੀ' },
|
||||
{ value: 'pl', label: 'Polski' },
|
||||
{ value: 'pr', label: 'Pirate' },
|
||||
{ value: 'pt', label: 'Português' },
|
||||
{ value: 'pt-BR', label: 'Português (Brasil)' },
|
||||
{ value: 'pt-PT', label: 'Português (Portugal)' },
|
||||
{ value: 'ro', label: 'Românește' },
|
||||
{ value: 'ru', label: 'Русский' },
|
||||
{ value: 'sk', label: 'Slovenčina' },
|
||||
{ value: 'sl-SI', label: 'Slovenščina' },
|
||||
{ value: 'sq', label: 'Shqip' },
|
||||
{ value: 'sr', label: 'Српски' },
|
||||
{ value: 'sv', label: 'Svenska' },
|
||||
{ value: 'ta', label: 'தமிழ்' },
|
||||
{ value: 'te', label: 'తెలుగు' },
|
||||
{ value: 'th', label: 'ภาษาไทย' },
|
||||
{ value: 'tr', label: 'Türkçe' },
|
||||
{ value: 'uk', label: 'Українська' },
|
||||
{ value: 'ur_PK', label: ' اُردُو' },
|
||||
{ value: 'vi', label: 'Tiếng Việt' },
|
||||
{ value: 'zh-CN', label: '汉语 (简化字)' },
|
||||
{ value: 'zh-TW', label: '漢語 (繁体字)' },
|
||||
{ value: 'zh-HK', label: '廣東話 (香港)' }
|
||||
];
|
||||
|
||||
// NOTE: Option `Euskara` (eu) does not exist in legacy date locale options.
|
||||
export const DATE_LOCALE_OPTIONS = LANGUAGE_OPTIONS.filter(({ value }) => value !== 'eu');
|
|
@ -0,0 +1,46 @@
|
|||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import toast from 'components/toast/toast';
|
||||
import globalize from 'scripts/globalize';
|
||||
import { DisplaySettingsValues } from '../types';
|
||||
import { useDisplaySettings } from './useDisplaySettings';
|
||||
|
||||
export function useDisplaySettingForm() {
|
||||
const [urlParams] = useSearchParams();
|
||||
const {
|
||||
displaySettings,
|
||||
loading,
|
||||
saveDisplaySettings
|
||||
} = useDisplaySettings({ userId: urlParams.get('userId') });
|
||||
const [formValues, setFormValues] = useState<DisplaySettingsValues>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && displaySettings && !formValues) {
|
||||
setFormValues(displaySettings);
|
||||
}
|
||||
}, [formValues, loading, displaySettings]);
|
||||
|
||||
const updateField = useCallback(({ name, value }) => {
|
||||
if (formValues) {
|
||||
setFormValues({
|
||||
...formValues,
|
||||
[name]: value
|
||||
});
|
||||
}
|
||||
}, [formValues, setFormValues]);
|
||||
|
||||
const submitChanges = useCallback(async () => {
|
||||
if (formValues) {
|
||||
await saveDisplaySettings(formValues);
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
}
|
||||
}, [formValues, saveDisplaySettings]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
values: formValues,
|
||||
submitChanges,
|
||||
updateField
|
||||
};
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
import { UserDto } from '@jellyfin/sdk/lib/generated-client';
|
||||
import { ApiClient } from 'jellyfin-apiclient';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { appHost } from 'components/apphost';
|
||||
import layoutManager from 'components/layoutManager';
|
||||
import { useApi } from 'hooks/useApi';
|
||||
import themeManager from 'scripts/themeManager';
|
||||
import { currentSettings, UserSettings } from 'scripts/settings/userSettings';
|
||||
import { DisplaySettingsValues } from '../types';
|
||||
|
||||
interface UseDisplaySettingsParams {
|
||||
userId?: string | null;
|
||||
}
|
||||
|
||||
export function useDisplaySettings({ userId }: UseDisplaySettingsParams) {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [userSettings, setUserSettings] = useState<UserSettings>();
|
||||
const [displaySettings, setDisplaySettings] = useState<DisplaySettingsValues>();
|
||||
const { __legacyApiClient__, user: currentUser } = useApi();
|
||||
|
||||
useEffect(() => {
|
||||
if (!userId || !currentUser || !__legacyApiClient__) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
void (async () => {
|
||||
const loadedSettings = await loadDisplaySettings({ api: __legacyApiClient__, currentUser, userId });
|
||||
|
||||
setDisplaySettings(loadedSettings.displaySettings);
|
||||
setUserSettings(loadedSettings.userSettings);
|
||||
|
||||
setLoading(false);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
setLoading(false);
|
||||
};
|
||||
}, [__legacyApiClient__, currentUser, userId]);
|
||||
|
||||
const saveSettings = useCallback(async (newSettings: DisplaySettingsValues) => {
|
||||
if (!userId || !userSettings || !__legacyApiClient__) {
|
||||
return;
|
||||
}
|
||||
return saveDisplaySettings({
|
||||
api: __legacyApiClient__,
|
||||
newDisplaySettings: newSettings,
|
||||
userSettings,
|
||||
userId
|
||||
});
|
||||
}, [__legacyApiClient__, userSettings, userId]);
|
||||
|
||||
return {
|
||||
displaySettings,
|
||||
loading,
|
||||
saveDisplaySettings: saveSettings
|
||||
};
|
||||
}
|
||||
|
||||
interface LoadDisplaySettingsParams {
|
||||
currentUser: UserDto;
|
||||
userId?: string;
|
||||
api: ApiClient;
|
||||
}
|
||||
|
||||
async function loadDisplaySettings({
|
||||
currentUser,
|
||||
userId,
|
||||
api
|
||||
}: LoadDisplaySettingsParams) {
|
||||
const settings = (!userId || userId === currentUser?.Id) ? currentSettings : new UserSettings();
|
||||
const user = (!userId || userId === currentUser?.Id) ? currentUser : await api.getUser(userId);
|
||||
|
||||
await settings.setUserInfo(userId, api);
|
||||
|
||||
const displaySettings = {
|
||||
customCss: settings.customCss(),
|
||||
dashboardTheme: settings.dashboardTheme() || 'auto',
|
||||
dateTimeLocale: settings.dateTimeLocale() || 'auto',
|
||||
disableCustomCss: Boolean(settings.disableCustomCss()),
|
||||
displayMissingEpisodes: user?.Configuration?.DisplayMissingEpisodes ?? false,
|
||||
enableBlurHash: Boolean(settings.enableBlurhash()),
|
||||
enableFasterAnimation: Boolean(settings.enableFastFadein()),
|
||||
enableItemDetailsBanner: Boolean(settings.detailsBanner()),
|
||||
enableLibraryBackdrops: Boolean(settings.enableBackdrops()),
|
||||
enableLibraryThemeSongs: Boolean(settings.enableThemeSongs()),
|
||||
enableLibraryThemeVideos: Boolean(settings.enableThemeVideos()),
|
||||
enableRewatchingInNextUp: Boolean(settings.enableRewatchingInNextUp()),
|
||||
episodeImagesInNextUp: Boolean(settings.useEpisodeImagesInNextUpAndResume()),
|
||||
language: settings.language() || 'auto',
|
||||
layout: layoutManager.getSavedLayout() || 'auto',
|
||||
libraryPageSize: settings.libraryPageSize(),
|
||||
maxDaysForNextUp: settings.maxDaysForNextUp(),
|
||||
screensaver: settings.screensaver() || 'none',
|
||||
screensaverInterval: settings.backdropScreensaverInterval(),
|
||||
theme: settings.theme()
|
||||
};
|
||||
|
||||
return {
|
||||
displaySettings,
|
||||
userSettings: settings
|
||||
};
|
||||
}
|
||||
|
||||
interface SaveDisplaySettingsParams {
|
||||
api: ApiClient;
|
||||
newDisplaySettings: DisplaySettingsValues
|
||||
userSettings: UserSettings;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
async function saveDisplaySettings({
|
||||
api,
|
||||
newDisplaySettings,
|
||||
userSettings,
|
||||
userId
|
||||
}: SaveDisplaySettingsParams) {
|
||||
const user = await api.getUser(userId);
|
||||
|
||||
if (appHost.supports('displaylanguage')) {
|
||||
userSettings.language(normalizeValue(newDisplaySettings.language));
|
||||
}
|
||||
userSettings.customCss(normalizeValue(newDisplaySettings.customCss));
|
||||
userSettings.dashboardTheme(normalizeValue(newDisplaySettings.dashboardTheme));
|
||||
userSettings.dateTimeLocale(normalizeValue(newDisplaySettings.dateTimeLocale));
|
||||
userSettings.disableCustomCss(newDisplaySettings.disableCustomCss);
|
||||
userSettings.enableBlurhash(newDisplaySettings.enableBlurHash);
|
||||
userSettings.enableFastFadein(newDisplaySettings.enableFasterAnimation);
|
||||
userSettings.detailsBanner(newDisplaySettings.enableItemDetailsBanner);
|
||||
userSettings.enableBackdrops(newDisplaySettings.enableLibraryBackdrops);
|
||||
userSettings.enableThemeSongs(newDisplaySettings.enableLibraryThemeSongs);
|
||||
userSettings.enableThemeVideos(newDisplaySettings.enableLibraryThemeVideos);
|
||||
userSettings.enableRewatchingInNextUp(newDisplaySettings.enableRewatchingInNextUp);
|
||||
userSettings.useEpisodeImagesInNextUpAndResume(newDisplaySettings.episodeImagesInNextUp);
|
||||
userSettings.libraryPageSize(newDisplaySettings.libraryPageSize);
|
||||
userSettings.maxDaysForNextUp(newDisplaySettings.maxDaysForNextUp);
|
||||
userSettings.screensaver(normalizeValue(newDisplaySettings.screensaver));
|
||||
userSettings.backdropScreensaverInterval(newDisplaySettings.screensaverInterval);
|
||||
userSettings.theme(newDisplaySettings.theme);
|
||||
|
||||
layoutManager.setLayout(normalizeValue(newDisplaySettings.layout));
|
||||
|
||||
const promises = [
|
||||
themeManager.setTheme(userSettings.theme())
|
||||
];
|
||||
|
||||
if (user.Id && user.Configuration) {
|
||||
user.Configuration.DisplayMissingEpisodes = newDisplaySettings.displayMissingEpisodes;
|
||||
promises.push(api.updateUserConfiguration(user.Id, user.Configuration));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
function normalizeValue(value: string) {
|
||||
return /^(auto|none)$/.test(value) ? '' : value;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { pluginManager } from 'components/pluginManager';
|
||||
import { Plugin, PluginType } from 'types/plugin';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
export function useScreensavers() {
|
||||
const screensavers = useMemo<Plugin[]>(() => {
|
||||
const installedScreensaverPlugins = pluginManager
|
||||
.ofType(PluginType.Screensaver)
|
||||
.map((plugin: Plugin) => ({
|
||||
...plugin,
|
||||
name: globalize.translate(plugin.name) as string
|
||||
}));
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'none',
|
||||
name: globalize.translate('None') as string,
|
||||
type: PluginType.Screensaver
|
||||
},
|
||||
...installedScreensaverPlugins
|
||||
];
|
||||
}, []);
|
||||
|
||||
return {
|
||||
screensavers: screensavers ?? []
|
||||
};
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import themeManager from 'scripts/themeManager';
|
||||
import { Theme } from 'types/webConfig';
|
||||
|
||||
export function useServerThemes() {
|
||||
const [themes, setThemes] = useState<Theme[]>();
|
||||
|
||||
useEffect(() => {
|
||||
async function getServerThemes() {
|
||||
const loadedThemes = await themeManager.getThemes();
|
||||
|
||||
setThemes(loadedThemes ?? []);
|
||||
}
|
||||
|
||||
if (!themes) {
|
||||
void getServerThemes();
|
||||
}
|
||||
// We've intentionally left the dependency array here to ensure that the effect happens only once.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const defaultTheme = useMemo(() => {
|
||||
if (!themes) return null;
|
||||
return themes.find((theme) => theme.default);
|
||||
}, [themes]);
|
||||
|
||||
return {
|
||||
themes: themes ?? [],
|
||||
defaultTheme
|
||||
};
|
||||
}
|
96
src/apps/experimental/routes/user/display/index.tsx
Normal file
96
src/apps/experimental/routes/user/display/index.tsx
Normal file
|
@ -0,0 +1,96 @@
|
|||
import Button from '@mui/material/Button';
|
||||
import { SelectChangeEvent } from '@mui/material/Select';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import Page from 'components/Page';
|
||||
import globalize from 'scripts/globalize';
|
||||
import theme from 'themes/theme';
|
||||
import { DisplayPreferences } from './DisplayPreferences';
|
||||
import { ItemDetailPreferences } from './ItemDetailPreferences';
|
||||
import { LibraryPreferences } from './LibraryPreferences';
|
||||
import { LocalizationPreferences } from './LocalizationPreferences';
|
||||
import { NextUpPreferences } from './NextUpPreferences';
|
||||
import { useDisplaySettingForm } from './hooks/useDisplaySettingForm';
|
||||
import { DisplaySettingsValues } from './types';
|
||||
import LoadingComponent from 'components/loading/LoadingComponent';
|
||||
|
||||
export default function UserDisplayPreferences() {
|
||||
const {
|
||||
loading,
|
||||
submitChanges,
|
||||
updateField,
|
||||
values
|
||||
} = useDisplaySettingForm();
|
||||
|
||||
const handleSubmitForm = useCallback((e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
void submitChanges();
|
||||
}, [submitChanges]);
|
||||
|
||||
const handleFieldChange = useCallback((e: SelectChangeEvent | React.SyntheticEvent) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const fieldName = target.name as keyof DisplaySettingsValues;
|
||||
const fieldValue = target.type === 'checkbox' ? target.checked : target.value;
|
||||
|
||||
if (values?.[fieldName] !== fieldValue) {
|
||||
updateField({
|
||||
name: fieldName,
|
||||
value: fieldValue
|
||||
});
|
||||
}
|
||||
}, [updateField, values]);
|
||||
|
||||
if (loading || !values) {
|
||||
return <LoadingComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page
|
||||
className='libraryPage userPreferencesPage noSecondaryNavPage'
|
||||
id='displayPreferencesPage'
|
||||
title={globalize.translate('Display')}
|
||||
>
|
||||
<div className='settingsContainer padded-left padded-right padded-bottom-page'>
|
||||
<form
|
||||
onSubmit={handleSubmitForm}
|
||||
style={{ margin: 'auto' }}
|
||||
>
|
||||
<Stack spacing={4}>
|
||||
<LocalizationPreferences
|
||||
onChange={handleFieldChange}
|
||||
values={values}
|
||||
/>
|
||||
<DisplayPreferences
|
||||
onChange={handleFieldChange}
|
||||
values={values}
|
||||
/>
|
||||
<LibraryPreferences
|
||||
onChange={handleFieldChange}
|
||||
values={values}
|
||||
/>
|
||||
<NextUpPreferences
|
||||
onChange={handleFieldChange}
|
||||
values={values}
|
||||
/>
|
||||
<ItemDetailPreferences
|
||||
onChange={handleFieldChange}
|
||||
values={values}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: theme.typography.htmlFontSize,
|
||||
fontWeight: theme.typography.fontWeightBold
|
||||
}}
|
||||
>
|
||||
{globalize.translate('Save')}
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
22
src/apps/experimental/routes/user/display/types.ts
Normal file
22
src/apps/experimental/routes/user/display/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
export interface DisplaySettingsValues {
|
||||
customCss: string;
|
||||
dashboardTheme: string;
|
||||
dateTimeLocale: string;
|
||||
disableCustomCss: boolean;
|
||||
displayMissingEpisodes: boolean;
|
||||
enableBlurHash: boolean;
|
||||
enableFasterAnimation: boolean;
|
||||
enableItemDetailsBanner: boolean;
|
||||
enableLibraryBackdrops: boolean;
|
||||
enableLibraryThemeSongs: boolean;
|
||||
enableLibraryThemeVideos: boolean;
|
||||
enableRewatchingInNextUp: boolean;
|
||||
episodeImagesInNextUp: boolean;
|
||||
language: string;
|
||||
layout: string;
|
||||
libraryPageSize: number;
|
||||
maxDaysForNextUp: number;
|
||||
screensaver: string;
|
||||
screensaverInterval: number;
|
||||
theme: string;
|
||||
}
|
|
@ -199,7 +199,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Theme Songs' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Theme Songs' or undefined.
|
||||
* @return {boolean} 'Theme Songs' state.
|
||||
*/
|
||||
enableThemeSongs(val) {
|
||||
|
@ -212,7 +212,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Theme Videos' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Theme Videos' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Theme Videos' or undefined.
|
||||
* @return {boolean} 'Theme Videos' state.
|
||||
*/
|
||||
enableThemeVideos(val) {
|
||||
|
@ -225,7 +225,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Fast Fade-in' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Fast Fade-in' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Fast Fade-in' or undefined.
|
||||
* @return {boolean} 'Fast Fade-in' state.
|
||||
*/
|
||||
enableFastFadein(val) {
|
||||
|
@ -238,7 +238,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Blurhash' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Blurhash' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Blurhash' or undefined.
|
||||
* @return {boolean} 'Blurhash' state.
|
||||
*/
|
||||
enableBlurhash(val) {
|
||||
|
@ -251,7 +251,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Backdrops' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Backdrops' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Backdrops' or undefined.
|
||||
* @return {boolean} 'Backdrops' state.
|
||||
*/
|
||||
enableBackdrops(val) {
|
||||
|
@ -264,7 +264,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'disableCustomCss' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'disableCustomCss' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'disableCustomCss' or undefined.
|
||||
* @return {boolean} 'disableCustomCss' state.
|
||||
*/
|
||||
disableCustomCss(val) {
|
||||
|
@ -277,7 +277,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set customCss.
|
||||
* @param {string|undefined} val - Language.
|
||||
* @param {string|undefined} [val] - Language.
|
||||
* @return {string} Language.
|
||||
*/
|
||||
customCss(val) {
|
||||
|
@ -290,7 +290,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set 'Details Banner' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined.
|
||||
* @param {boolean|undefined} [val] - Flag to enable 'Details Banner' or undefined.
|
||||
* @return {boolean} 'Details Banner' state.
|
||||
*/
|
||||
detailsBanner(val) {
|
||||
|
@ -316,7 +316,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set language.
|
||||
* @param {string|undefined} val - Language.
|
||||
* @param {string|undefined} [val] - Language.
|
||||
* @return {string} Language.
|
||||
*/
|
||||
language(val) {
|
||||
|
@ -329,7 +329,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set datetime locale.
|
||||
* @param {string|undefined} val - Datetime locale.
|
||||
* @param {string|undefined} [val] - Datetime locale.
|
||||
* @return {string} Datetime locale.
|
||||
*/
|
||||
dateTimeLocale(val) {
|
||||
|
@ -368,7 +368,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set theme for Dashboard.
|
||||
* @param {string|undefined} val - Theme for Dashboard.
|
||||
* @param {string|undefined} [val] - Theme for Dashboard.
|
||||
* @return {string} Theme for Dashboard.
|
||||
*/
|
||||
dashboardTheme(val) {
|
||||
|
@ -394,7 +394,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set main theme.
|
||||
* @param {string|undefined} val - Main theme.
|
||||
* @param {string|undefined} [val] - Main theme.
|
||||
* @return {string} Main theme.
|
||||
*/
|
||||
theme(val) {
|
||||
|
@ -407,7 +407,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set screensaver.
|
||||
* @param {string|undefined} val - Screensaver.
|
||||
* @param {string|undefined} [val] - Screensaver.
|
||||
* @return {string} Screensaver.
|
||||
*/
|
||||
screensaver(val) {
|
||||
|
@ -420,7 +420,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set the interval between backdrops when using the backdrop screensaver.
|
||||
* @param {number|undefined} val - The interval between backdrops in seconds.
|
||||
* @param {number|undefined} [val] - The interval between backdrops in seconds.
|
||||
* @return {number} The interval between backdrops in seconds.
|
||||
*/
|
||||
backdropScreensaverInterval(val) {
|
||||
|
@ -433,7 +433,7 @@ export class UserSettings {
|
|||
|
||||
/**
|
||||
* Get or set library page size.
|
||||
* @param {number|undefined} val - Library page size.
|
||||
* @param {number|undefined} [val] - Library page size.
|
||||
* @return {number} Library page size.
|
||||
*/
|
||||
libraryPageSize(val) {
|
||||
|
|
|
@ -39,6 +39,7 @@ body {
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
contain: strict;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.layout-mobile,
|
||||
|
|
|
@ -62,6 +62,13 @@ const theme = createTheme({
|
|||
variant: 'filled'
|
||||
}
|
||||
},
|
||||
MuiFormHelperText: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
fontSize: '1rem'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiTextField: {
|
||||
defaultProps: {
|
||||
variant: 'filled'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
interface Theme {
|
||||
export interface Theme {
|
||||
name: string
|
||||
default?: boolean;
|
||||
id: string
|
||||
color: string
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue