feat: (preferences) hook react display settings into user settings

This commit is contained in:
Grady Hallenbeck 2023-10-19 12:02:54 -07:00
parent ce4c7aed5e
commit 3dd26c7785
13 changed files with 530 additions and 46 deletions

View File

@ -3,15 +3,26 @@ import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
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 from 'react';
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 { screensavers } = useScreensavers();
const { themes } = useServerThemes();
export function DisplayPreferences() {
return (
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
@ -19,7 +30,12 @@ export function DisplayPreferences() {
<FormControl fullWidth>
<Select
aria-describedby='display-settings-layout-description'
inputProps={{
name: 'layout'
}}
label={globalize.translate('LabelDisplayMode')}
onChange={onChange}
value={values.layout}
>
<MenuItem value='auto'>{globalize.translate('Auto')}</MenuItem>
<MenuItem value='desktop'>{globalize.translate('Desktop')}</MenuItem>
@ -34,15 +50,31 @@ export function DisplayPreferences() {
</FormControl>
<FormControl fullWidth>
<Select label={globalize.translate('LabelTheme')}>
<Select
inputProps={{
name: 'theme'
}}
label={globalize.translate('LabelTheme')}
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 />}
control={
<Checkbox
checked={values.disableCustomCss}
onChange={onChange}
/>
}
label={globalize.translate('DisableCustomCss')}
name='disableCustomCss'
/>
<FormHelperText id='display-settings-disable-css-description'>
{globalize.translate('LabelDisableCustomCss')}
@ -52,8 +84,11 @@ export function DisplayPreferences() {
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-custom-css-description'
defaultValue={values.customCss}
label={globalize.translate('LabelCustomCss')}
multiline
name='customCss'
onChange={onChange}
/>
<FormHelperText id='display-settings-custom-css-description'>
{globalize.translate('LabelLocalCustomCss')}
@ -64,7 +99,18 @@ export function DisplayPreferences() {
{/* Server Dashboard Theme */}
<FormControl fullWidth>
<Select label={globalize.translate('LabelScreensaver')}></Select>
<Select
inputProps={{
name: 'screensaver'
}}
label={globalize.translate('LabelScreensaver')}
onChange={onChange}
value={values.screensaver}
>
{ ...screensavers.map(({ id, name }) => (
<MenuItem key={id} value={id}>{name}</MenuItem>
))}
</Select>
</FormControl>
{/* TODO: There are some extra options here related to screensavers */}
@ -72,8 +118,14 @@ export function DisplayPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-faster-animations-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableFasterAnimation}
onChange={onChange}
/>
}
label={globalize.translate('EnableFasterAnimations')}
name='enableFasterAnimation'
/>
<FormHelperText id='display-settings-faster-animations-description'>
{globalize.translate('EnableFasterAnimationsHelp')}
@ -83,8 +135,14 @@ export function DisplayPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-blurhash-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableBlurHash}
onChange={onChange}
/>
}
label={globalize.translate('EnableBlurHash')}
name='enableBlurHash'
/>
<FormHelperText id='display-settings-blurhash-description'>
{globalize.translate('EnableBlurHashHelp')}

View File

@ -7,17 +7,29 @@ import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
export function ItemDetailPreferences() {
interface ItemDetailPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function ItemDetailPreferences({ onChange, values }: Readonly<ItemDetailPreferencesProps>) {
return (
<Stack spacing={3}>
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('ItemDetails')}</Typography>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-item-details-banner-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableItemDetailsBanner}
onChange={onChange}
/>
}
label={globalize.translate('EnableDetailsBanner')}
name='enableItemDetailsBanner'
/>
<FormHelperText id='display-settings-item-details-banner-description'>
{globalize.translate('EnableDetailsBannerHelp')}

View File

@ -8,10 +8,16 @@ import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
export function LibraryPreferences() {
interface LibraryPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function LibraryPreferences({ onChange, values }: Readonly<LibraryPreferencesProps>) {
return (
<Stack spacing={3}>
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('HeaderLibraries')}</Typography>
<FormControl fullWidth>
@ -26,7 +32,10 @@ export function LibraryPreferences() {
required: true,
step: '1'
}}
defaultValue={values.libraryPageSize}
label={globalize.translate('LabelLibraryPageSize')}
name='libraryPageSize'
onChange={onChange}
/>
<FormHelperText id='display-settings-lib-pagesize-description'>
{globalize.translate('LabelLibraryPageSizeHelp')}
@ -36,8 +45,14 @@ export function LibraryPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-backdrops-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableLibraryBackdrops}
onChange={onChange}
/>
}
label={globalize.translate('Backdrops')}
name='enableLibraryBackdrops'
/>
<FormHelperText id='display-settings-lib-backdrops-description'>
{globalize.translate('EnableBackdropsHelp')}
@ -47,8 +62,14 @@ export function LibraryPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-theme-songs-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableLibraryThemeSongs}
onChange={onChange}
/>
}
label={globalize.translate('ThemeSongs')}
name='enableLibraryThemeSongs'
/>
<FormHelperText id='display-settings-lib-theme-songs-description'>
{globalize.translate('EnableThemeSongsHelp')}
@ -58,8 +79,14 @@ export function LibraryPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-theme-videos-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableLibraryThemeVideos}
onChange={onChange}
/>
}
label={globalize.translate('ThemeVideos')}
name='enableLibraryThemeVideos'
/>
<FormHelperText id='display-settings-lib-theme-videos-description'>
{globalize.translate('EnableThemeVideosHelp')}
@ -69,8 +96,14 @@ export function LibraryPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-show-missing-episodes-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.displayMissingEpisodes}
onChange={onChange}
/>
}
label={globalize.translate('DisplayMissingEpisodesWithinSeasons')}
name='displayMissingEpisodes'
/>
<FormHelperText id='display-settings-show-missing-episodes-description'>
{globalize.translate('DisplayMissingEpisodesWithinSeasonsHelp')}

View File

@ -2,23 +2,34 @@ import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import Link from '@mui/material/Link';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from './constants';
import { DisplaySettingsValues } from './types';
export function LocalizationPreferences() {
interface LocalizationPreferencesProps {
onChange: (event: SelectChangeEvent) => void;
values: DisplaySettingsValues;
}
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
return (
<Stack spacing={3}>
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
<FormControl fullWidth>
<Select
aria-describedby='display-settings-language-description'
inputProps={{
name: 'language'
}}
label={globalize.translate('LabelDisplayLanguage')}
onChange={onChange}
value={values.language}
>
{ ...LANGUAGE_OPTIONS.map(({ value, label }) => (
<MenuItem key={value } value={value}>{ label }</MenuItem>
@ -37,7 +48,14 @@ export function LocalizationPreferences() {
</FormControl>
<FormControl fullWidth>
<Select label={globalize.translate('LabelDateTimeLocale')}>
<Select
inputProps={{
name: 'dateTimeLocale'
}}
label={globalize.translate('LabelDateTimeLocale')}
onChange={onChange}
value={values.dateTimeLocale}
>
{...DATE_LOCALE_OPTIONS.map(({ value, label }) => (
<MenuItem key={value} value={value}>{label}</MenuItem>
))}

View File

@ -8,15 +8,22 @@ import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
export function NextUpPreferences() {
interface NextUpPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function NextUpPreferences({ onChange, values }: Readonly<NextUpPreferencesProps>) {
return (
<Stack spacing={3}>
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('NextUp')}</Typography>
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-max-days-next-up-description'
defaultValue={values.maxDaysForNextUp}
inputProps={{
type: 'number',
inputMode: 'numeric',
@ -27,6 +34,8 @@ export function NextUpPreferences() {
step: '1'
}}
label={globalize.translate('LabelMaxDaysForNextUp')}
name='maxDaysForNextUp'
onChange={onChange}
/>
<FormHelperText id='display-settings-max-days-next-up-description'>
{globalize.translate('LabelMaxDaysForNextUpHelp')}
@ -36,8 +45,14 @@ export function NextUpPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-next-up-rewatching-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.enableRewatchingInNextUp}
onChange={onChange}
/>
}
label={globalize.translate('EnableRewatchingNextUp')}
name='enableRewatchingInNextUp'
/>
<FormHelperText id='display-settings-next-up-rewatching-description'>
{globalize.translate('EnableRewatchingNextUpHelp')}
@ -47,8 +62,14 @@ export function NextUpPreferences() {
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-next-up-images-description'
control={<Checkbox />}
control={
<Checkbox
checked={values.episodeImagesInNextUp}
onChange={onChange}
/>
}
label={globalize.translate('UseEpisodeImagesInNextUp')}
name='episodeImagesInNextUp'
/>
<FormHelperText id='display-settings-next-up-images-description'>
{globalize.translate('UseEpisodeImagesInNextUpHelp')}

View File

@ -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
};
}

View File

@ -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;
}

View File

@ -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 ?? []
};
}

View File

@ -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
};
}

View File

@ -1,16 +1,50 @@
import Button from '@mui/material/Button';
import { SelectChangeEvent } from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import React from 'react';
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.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'
@ -18,12 +52,31 @@ export default function UserDisplayPreferences() {
title={globalize.translate('Display')}
>
<div className='settingsContainer padded-left padded-right padded-bottom-page'>
<form style={{ margin: 'auto' }}>
<form
onSubmit={handleSubmitForm}
style={{ margin: 'auto' }}
>
<Stack spacing={4}>
<LocalizationPreferences />
<DisplayPreferences />
<LibraryPreferences />
<NextUpPreferences />
<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'

View 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;
}

View File

@ -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) {

View File

@ -1,5 +1,6 @@
interface Theme {
export interface Theme {
name: string
default?: boolean;
id: string
color: string
}