diff --git a/src/apps/dashboard/controllers/librarydisplay.html b/src/apps/dashboard/controllers/librarydisplay.html deleted file mode 100644 index d641e30c1e..0000000000 --- a/src/apps/dashboard/controllers/librarydisplay.html +++ /dev/null @@ -1,57 +0,0 @@ -
-
-
-
-
- -
${LabelDateAddedBehaviorHelp}
-
- -
- -
${OptionDisplayFolderViewHelp}
-
- - - -
- -
${LabelGroupMoviesIntoCollectionsHelp}
-
- -
- -
${OptionEnableExternalContentInSuggestionsHelp}
-
- -
- -
${OptionSaveMetadataAsHiddenHelp}
-
- -
- -
-
-
-
diff --git a/src/apps/dashboard/controllers/librarydisplay.js b/src/apps/dashboard/controllers/librarydisplay.js deleted file mode 100644 index 74be51e762..0000000000 --- a/src/apps/dashboard/controllers/librarydisplay.js +++ /dev/null @@ -1,52 +0,0 @@ -import loading from 'components/loading/loading'; -import 'elements/emby-checkbox/emby-checkbox'; -import 'elements/emby-button/emby-button'; -import Dashboard from 'utils/dashboard'; - -export default function(view) { - function loadData() { - ApiClient.getServerConfiguration().then(function(config) { - view.querySelector('.chkFolderView').checked = config.EnableFolderView; - view.querySelector('.chkGroupMoviesIntoCollections').checked = config.EnableGroupingIntoCollections; - view.querySelector('.chkDisplaySpecialsWithinSeasons').checked = config.DisplaySpecialsWithinSeasons; - view.querySelector('.chkExternalContentInSuggestions').checked = config.EnableExternalContentInSuggestions; - view.querySelector('#chkSaveMetadataHidden').checked = config.SaveMetadataHidden; - }); - ApiClient.getNamedConfiguration('metadata').then(function(metadata) { - view.querySelector('#selectDateAdded').selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; - }); - } - - view.querySelector('form').addEventListener('submit', function(e) { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function(config) { - config.EnableFolderView = form.querySelector('.chkFolderView').checked; - config.EnableGroupingIntoCollections = form.querySelector('.chkGroupMoviesIntoCollections').checked; - config.DisplaySpecialsWithinSeasons = form.querySelector('.chkDisplaySpecialsWithinSeasons').checked; - config.EnableExternalContentInSuggestions = form.querySelector('.chkExternalContentInSuggestions').checked; - config.SaveMetadataHidden = form.querySelector('#chkSaveMetadataHidden').checked; - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - ApiClient.getNamedConfiguration('metadata').then(function(config) { - config.UseFileCreationTimeForDateAdded = form.querySelector('#selectDateAdded').value === '1'; - ApiClient.updateNamedConfiguration('metadata', config); - }); - - e.preventDefault(); - loading.hide(); - return false; - }); - - view.addEventListener('viewshow', function() { - loadData(); - ApiClient.getSystemInfo().then(function(info) { - if (info.OperatingSystem === 'Windows') { - view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); - } else { - view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); - } - }); - }); -} - diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 19cf7ea1a5..700b175cbe 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -6,6 +6,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'branding', type: AppType.Dashboard }, { path: 'devices', type: AppType.Dashboard }, { path: 'keys', type: AppType.Dashboard }, + { path: 'libraries/display', type: AppType.Dashboard }, { path: 'logs', type: AppType.Dashboard }, { path: 'playback/resume', type: AppType.Dashboard }, { path: 'playback/streaming', type: AppType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index 848c242b67..c606c4f1e1 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -30,13 +30,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'library', view: 'library.html' } - }, { - path: 'libraries/display', - pageProps: { - appType: AppType.Dashboard, - controller: 'librarydisplay', - view: 'librarydisplay.html' - } }, { path: 'playback/transcoding', pageProps: { diff --git a/src/apps/dashboard/routes/libraries/display.tsx b/src/apps/dashboard/routes/libraries/display.tsx new file mode 100644 index 0000000000..697b448ce5 --- /dev/null +++ b/src/apps/dashboard/routes/libraries/display.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import Alert from '@mui/material/Alert'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +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 Stack from '@mui/material/Stack'; +import Switch from '@mui/material/Switch'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Loading from 'components/loading/LoadingComponent'; +import Page from 'components/Page'; +import ServerConnections from 'components/ServerConnections'; +import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; +import { useConfiguration } from 'hooks/useConfiguration'; +import { fetchNamedConfiguration, useNamedConfiguration } from 'hooks/useNamedConfiguration'; +import globalize from 'lib/globalize'; +import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom'; +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 formData = await request.formData(); + const data = Object.fromEntries(formData); + + const { data: config } = await getConfigurationApi(api).getConfiguration(); + const namedConfig = await fetchNamedConfiguration(api, 'metadata'); + + namedConfig.UseFileCreationTimeForDateAdded = data.DateAddedBehavior.toString() === '1'; + config.EnableFolderView = data.DisplayFolderView?.toString() === 'on'; + config.DisplaySpecialsWithinSeasons = data.DisplaySpecialsWithinSeasons?.toString() === 'on'; + config.EnableGroupingIntoCollections = data.GroupMoviesIntoCollections?.toString() === 'on'; + config.EnableExternalContentInSuggestions = data.EnableExternalContentInSuggestions?.toString() === 'on'; + + await getConfigurationApi(api) + .updateConfiguration({ serverConfiguration: config }); + + await getConfigurationApi(api) + .updateNamedConfiguration({ key: 'metadata', body: namedConfig }); + + return { + isSaved: true + }; +}; + +export const Component = () => { + const { + data: config, + isPending: isConfigPending, + isError: isConfigError + } = useConfiguration(); + const { + data: namedConfig, + isPending: isNamedConfigPending, + isError: isNamedConfigError + } = useNamedConfiguration('metadata'); + + const navigation = useNavigation(); + const actionData = useActionData() as ActionData | undefined; + const isSubmitting = navigation.state === 'submitting'; + + if (isConfigPending || isNamedConfigPending) { + return ; + } + + return ( + + +
+ + {isConfigError || isNamedConfigError ? ( + {globalize.translate('DisplayLoadError')} + ) : ( + <> + {!isSubmitting && actionData?.isSaved && ( + + {globalize.translate('SettingsSaved')} + + )} + {globalize.translate('Display')} + + {globalize.translate('OptionDateAddedImportTime')} + {globalize.translate('OptionDateAddedFileTime')} + + + + + } + label={globalize.translate('OptionDisplayFolderView')} + /> + {globalize.translate('OptionDisplayFolderViewHelp')} + + + + + } + label={globalize.translate('LabelDisplaySpecialsWithinSeasons')} + /> + + + + + } + label={globalize.translate('LabelGroupMoviesIntoCollections')} + /> + {globalize.translate('LabelGroupMoviesIntoCollectionsHelp')} + + + + + } + label={globalize.translate('OptionEnableExternalContentInSuggestions')} + /> + {globalize.translate('OptionEnableExternalContentInSuggestionsHelp')} + + + + + )} + +
+
+
+ ); +}; + +Component.displayName = 'DisplayPage'; diff --git a/src/hooks/useConfiguration.ts b/src/hooks/useConfiguration.ts index f1b9d47c3d..ea6d865c92 100644 --- a/src/hooks/useConfiguration.ts +++ b/src/hooks/useConfiguration.ts @@ -6,12 +6,7 @@ import type { AxiosRequestConfig } from 'axios'; export const QUERY_KEY = 'Configuration'; -export const fetchConfiguration = async (api?: Api, options?: AxiosRequestConfig) => { - if (!api) { - console.error('[useLogOptions] No API instance available'); - return; - } - +export const fetchConfiguration = async (api: Api, options?: AxiosRequestConfig) => { const response = await getConfigurationApi(api).getConfiguration(options); return response.data; @@ -22,7 +17,7 @@ export const useConfiguration = () => { return useQuery({ queryKey: [QUERY_KEY], - queryFn: ({ signal }) => fetchConfiguration(api, { signal }), + queryFn: ({ signal }) => fetchConfiguration(api!, { signal }), enabled: !!api }); }; diff --git a/src/hooks/useNamedConfiguration.ts b/src/hooks/useNamedConfiguration.ts new file mode 100644 index 0000000000..66652b510a --- /dev/null +++ b/src/hooks/useNamedConfiguration.ts @@ -0,0 +1,27 @@ +import { Api } from '@jellyfin/sdk'; +import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; +import { useQuery } from '@tanstack/react-query'; +import { useApi } from 'hooks/useApi'; +import type { AxiosRequestConfig } from 'axios'; + +export const QUERY_KEY = 'NamedConfiguration'; + +interface NamedConfiguration { + [key: string]: unknown; +} + +export const fetchNamedConfiguration = async (api: Api, key: string, options?: AxiosRequestConfig) => { + const response = await getConfigurationApi(api).getNamedConfiguration({ key }, options); + + return response.data as unknown as NamedConfiguration; +}; + +export const useNamedConfiguration = (key: string) => { + const { api } = useApi(); + + return useQuery({ + queryKey: [ QUERY_KEY ], + queryFn: ({ signal }) => fetchNamedConfiguration(api!, key, { signal }), + enabled: !!api + }); +}; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 5377cbd88a..6f71ba9898 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -241,6 +241,7 @@ "Display": "Display", "DisplayInMyMedia": "Display on home screen", "DisplayInOtherHomeScreenSections": "Display in home screen sections such as 'Recently Added Media' and 'Continue Watching'", + "DisplayLoadError": "An error occurred while loading display configuration data.", "DisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons", "DisplayMissingEpisodesWithinSeasonsHelp": "This must also be enabled for TV libraries in the server configuration.", "DisplayModeHelp": "Select the layout style you want for the interface.",