From 10d731e69710b053fc0108b3955cca475113eeea Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:46:54 +0300 Subject: [PATCH 1/3] Migrate NFO Settings to React --- .../dashboard/controllers/metadatanfo.html | 49 ----- src/apps/dashboard/controllers/metadatanfo.js | 61 ------ src/apps/dashboard/routes/_asyncRoutes.ts | 1 + src/apps/dashboard/routes/_legacyRoutes.ts | 7 - .../dashboard/routes/libraries/display.tsx | 8 +- src/apps/dashboard/routes/libraries/nfo.tsx | 197 ++++++++++++++++++ src/components/SimpleAlert.tsx | 36 ++++ src/strings/en-us.json | 1 + 8 files changed, 240 insertions(+), 120 deletions(-) delete mode 100644 src/apps/dashboard/controllers/metadatanfo.html delete mode 100644 src/apps/dashboard/controllers/metadatanfo.js create mode 100644 src/apps/dashboard/routes/libraries/nfo.tsx create mode 100644 src/components/SimpleAlert.tsx diff --git a/src/apps/dashboard/controllers/metadatanfo.html b/src/apps/dashboard/controllers/metadatanfo.html deleted file mode 100644 index 62f18b2641..0000000000 --- a/src/apps/dashboard/controllers/metadatanfo.html +++ /dev/null @@ -1,49 +0,0 @@ -
- -
- -
-
- -

${HeaderKodiMetadataHelp}

-
-
- -
${LabelKodiMetadataUserHelp}
-
- -
- -
${LabelKodiMetadataDateFormatHelp}
-
-
- -
${LabelKodiMetadataSaveImagePathsHelp}
-
-
- -
-
${LabelKodiMetadataEnablePathSubstitutionHelp}
-
-
-
- -
${LabelKodiMetadataEnableExtraThumbsHelp}
-
-
-
-
- -
-
diff --git a/src/apps/dashboard/controllers/metadatanfo.js b/src/apps/dashboard/controllers/metadatanfo.js deleted file mode 100644 index 51b7eee36b..0000000000 --- a/src/apps/dashboard/controllers/metadatanfo.js +++ /dev/null @@ -1,61 +0,0 @@ -import escapeHtml from 'escape-html'; -import 'jquery'; - -import loading from 'components/loading/loading'; -import globalize from 'lib/globalize'; -import Dashboard from 'utils/dashboard'; -import alert from 'components/alert'; - -function loadPage(page, config, users) { - let html = ''; - html += users.map(function (user) { - return ''; - }).join(''); - const elem = page.querySelector('#selectUser'); - elem.innerHTML = html; - elem.value = config.UserId || ''; - page.querySelector('#selectReleaseDateFormat').value = config.ReleaseDateFormat; - page.querySelector('#chkSaveImagePaths').checked = config.SaveImagePathsInNfo; - page.querySelector('#chkEnablePathSubstitution').checked = config.EnablePathSubstitution; - page.querySelector('#chkEnableExtraThumbs').checked = config.EnableExtraThumbsDuplication; - loading.hide(); -} - -function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration(metadataKey).then(function (config) { - config.UserId = form.querySelector('#selectUser').value || null; - config.ReleaseDateFormat = form.querySelector('#selectReleaseDateFormat').value; - config.SaveImagePathsInNfo = form.querySelector('#chkSaveImagePaths').checked; - config.EnablePathSubstitution = form.querySelector('#chkEnablePathSubstitution').checked; - config.EnableExtraThumbsDuplication = form.querySelector('#chkEnableExtraThumbs').checked; - ApiClient.updateNamedConfiguration(metadataKey, config).then(function () { - Dashboard.processServerConfigurationUpdateResult(); - showConfirmMessage(); - }); - }); - return false; -} - -function showConfirmMessage() { - const msg = []; - msg.push(globalize.translate('MetadataSettingChangeHelp')); - alert({ - text: msg.join('

') - }); -} - -const metadataKey = 'xbmcmetadata'; -$(document).on('pageinit', '#metadataNfoPage', function () { - $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); -}).on('pageshow', '#metadataNfoPage', function () { - loading.show(); - const page = this; - const promise1 = ApiClient.getUsers(); - const promise2 = ApiClient.getNamedConfiguration(metadataKey); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[1], responses[0]); - }); -}); - diff --git a/src/apps/dashboard/routes/_asyncRoutes.ts b/src/apps/dashboard/routes/_asyncRoutes.ts index 5a7398da39..1a11b608b4 100644 --- a/src/apps/dashboard/routes/_asyncRoutes.ts +++ b/src/apps/dashboard/routes/_asyncRoutes.ts @@ -8,6 +8,7 @@ export const ASYNC_ADMIN_ROUTES: AsyncRoute[] = [ { path: 'keys', type: AppType.Dashboard }, { path: 'libraries/display', type: AppType.Dashboard }, { path: 'libraries/metadata', type: AppType.Dashboard }, + { path: 'libraries/nfo', type: AppType.Dashboard }, { path: 'logs', type: AppType.Dashboard }, { path: 'logs/:file', page: 'logs/file', type: AppType.Dashboard }, { path: 'playback/resume', type: AppType.Dashboard }, diff --git a/src/apps/dashboard/routes/_legacyRoutes.ts b/src/apps/dashboard/routes/_legacyRoutes.ts index b690b45440..2af71b7473 100644 --- a/src/apps/dashboard/routes/_legacyRoutes.ts +++ b/src/apps/dashboard/routes/_legacyRoutes.ts @@ -37,13 +37,6 @@ export const LEGACY_ADMIN_ROUTES: LegacyRoute[] = [ controller: 'encodingsettings', view: 'encodingsettings.html' } - }, { - path: 'libraries/nfo', - pageProps: { - appType: AppType.Dashboard, - controller: 'metadatanfo', - view: 'metadatanfo.html' - } }, { path: 'plugins/catalog', pageProps: { diff --git a/src/apps/dashboard/routes/libraries/display.tsx b/src/apps/dashboard/routes/libraries/display.tsx index 3c958bcb33..8da3f2453b 100644 --- a/src/apps/dashboard/routes/libraries/display.tsx +++ b/src/apps/dashboard/routes/libraries/display.tsx @@ -21,6 +21,8 @@ import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'rea import { ActionData } from 'types/actionData'; import { queryClient } from 'utils/query/queryClient'; +const CONFIG_KEY = 'metadata'; + export const action = async ({ request }: ActionFunctionArgs) => { const api = ServerConnections.getCurrentApi(); if (!api) throw new Error('No Api instance available'); @@ -43,13 +45,13 @@ export const action = async ({ request }: ActionFunctionArgs) => { .updateConfiguration({ serverConfiguration: config }); await getConfigurationApi(api) - .updateNamedConfiguration({ key: 'metadata', body: metadataConfig }); + .updateNamedConfiguration({ key: CONFIG_KEY, body: metadataConfig }); void queryClient.invalidateQueries({ queryKey: [ CONFIG_QUERY_KEY ] }); void queryClient.invalidateQueries({ - queryKey: [ NAMED_CONFIG_QUERY_KEY, 'metadata' ] + queryKey: [ NAMED_CONFIG_QUERY_KEY, CONFIG_KEY ] }); return { @@ -67,7 +69,7 @@ export const Component = () => { data: namedConfig, isPending: isNamedConfigPending, isError: isNamedConfigError - } = useNamedConfiguration('metadata'); + } = useNamedConfiguration(CONFIG_KEY); const navigation = useNavigation(); const actionData = useActionData() as ActionData | undefined; diff --git a/src/apps/dashboard/routes/libraries/nfo.tsx b/src/apps/dashboard/routes/libraries/nfo.tsx new file mode 100644 index 0000000000..c0dff0f336 --- /dev/null +++ b/src/apps/dashboard/routes/libraries/nfo.tsx @@ -0,0 +1,197 @@ +import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api'; +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 SimpleAlert from 'components/SimpleAlert'; +import { QUERY_KEY, useNamedConfiguration } from 'hooks/useNamedConfiguration'; +import { useUsers } from 'hooks/useUsers'; +import globalize from 'lib/globalize'; +import React, { useCallback, useState } from 'react'; +import { type ActionFunctionArgs, Form, useActionData, useNavigation } from 'react-router-dom'; +import { ActionData } from 'types/actionData'; +import { queryClient } from 'utils/query/queryClient'; + +const CONFIG_KEY = 'xbmcmetadata'; + +interface NFOSettingsConfig { + UserId?: string; + EnableExtraThumbsDuplication?: boolean; + EnablePathSubstitution?: boolean; + ReleaseDateFormat?: string; + SaveImagePathsInNfo?: boolean; +}; + +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 newConfig: NFOSettingsConfig = { + UserId: data.UserId?.toString(), + ReleaseDateFormat: data.ReleaseDateFormat?.toString(), + SaveImagePathsInNfo: data.SaveImagePathsInNfo?.toString() === 'on', + EnablePathSubstitution: data.EnablePathSubstitution?.toString() === 'on', + EnableExtraThumbsDuplication: data.EnableExtraThumbsDuplication?.toString() === 'on' + }; + + await getConfigurationApi(api) + .updateNamedConfiguration({ key: CONFIG_KEY, body: newConfig }); + + void queryClient.invalidateQueries({ + queryKey: [QUERY_KEY, CONFIG_KEY] + }); + + return { + isSaved: true + }; +}; + +export const Component = () => { + const { + data: config, + isPending: isConfigPending, + isError: isConfigError + } = useNamedConfiguration(CONFIG_KEY); + const { + data: users, + isPending: isUsersPending, + isError: isUsersError + } = useUsers(); + const navigation = useNavigation(); + const actionData = useActionData() as ActionData | undefined; + const isSubmitting = navigation.state === 'submitting'; + const [isAlertOpen, setIsAlertOpen] = useState(false); + + const nfoConfig = config as NFOSettingsConfig; + + const onAlertClose = useCallback(() => { + setIsAlertOpen(false); + }, []); + + const onSubmit = useCallback(() => { + setIsAlertOpen(true); + }, []); + + if (isConfigPending || isUsersPending) { + return ; + } + + return ( + + + + {isConfigError || isUsersError ? ( + {globalize.translate('MetadataNfoLoadError')} + ) : ( +
+ + {!isSubmitting && actionData?.isSaved && ( + + {globalize.translate('SettingsSaved')} + + )} + {globalize.translate('TabNfoSettings')} + {globalize.translate('HeaderKodiMetadataHelp')} + + + {globalize.translate('None')} + {users.map(user => + {user.Name} + )} + + + + yyyy-MM-dd + + + + + } + label={globalize.translate('LabelKodiMetadataSaveImagePaths')} + /> + {globalize.translate('LabelKodiMetadataSaveImagePathsHelp')} + + + + + } + label={globalize.translate('LabelKodiMetadataEnablePathSubstitution')} + /> + {globalize.translate('LabelKodiMetadataEnablePathSubstitutionHelp')} + + + + + } + label={globalize.translate('LabelKodiMetadataEnableExtraThumbs')} + /> + {globalize.translate('LabelKodiMetadataEnableExtraThumbsHelp')} + + + + +
+ )} +
+
+ ); +}; + +Component.displayName = 'NFOSettingsPage'; diff --git a/src/components/SimpleAlert.tsx b/src/components/SimpleAlert.tsx new file mode 100644 index 0000000000..c67465211f --- /dev/null +++ b/src/components/SimpleAlert.tsx @@ -0,0 +1,36 @@ +import Button from '@mui/material/Button'; +import Dialog, { type DialogProps } from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; +import globalize from 'lib/globalize'; +import React from 'react'; + +interface SimpleAlertDialog extends DialogProps { + title: string; + text: string; + onClose: () => void +}; + +const SimpleAlert = ({ open, title, text, onClose }: SimpleAlertDialog) => { + return ( + + + {title} + + + + {text} + + + + + + + ); +}; + +export default SimpleAlert; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 8007057b71..71de76a2ab 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1174,6 +1174,7 @@ "MessageUnsetContentHelp": "Content will be displayed as plain folders. For best results use the metadata manager to set the content types of sub-folders.", "MetadataImagesLoadError": "Failed to load metadata settings", "MetadataManager": "Metadata Manager", + "MetadataNfoLoadError": "Failed to load metadata NFO settings", "MetadataSettingChangeHelp": "Changing metadata settings will affect new content added going forward. To refresh existing content, open the detail screen and click the 'Refresh' button, or do bulk refreshes using the 'Metadata Manager'.", "MillisecondsUnit": "ms", "MinutesAfter": "minutes after", From 9dc9aadf98159bee4743b16fee3109da11e89b7c Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Thu, 6 Mar 2025 04:29:08 +0300 Subject: [PATCH 2/3] Make title optional for alert --- src/apps/dashboard/routes/libraries/nfo.tsx | 1 - src/components/SimpleAlert.tsx | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/apps/dashboard/routes/libraries/nfo.tsx b/src/apps/dashboard/routes/libraries/nfo.tsx index c0dff0f336..e7025f07e5 100644 --- a/src/apps/dashboard/routes/libraries/nfo.tsx +++ b/src/apps/dashboard/routes/libraries/nfo.tsx @@ -97,7 +97,6 @@ export const Component = () => { > diff --git a/src/components/SimpleAlert.tsx b/src/components/SimpleAlert.tsx index c67465211f..5322662d7d 100644 --- a/src/components/SimpleAlert.tsx +++ b/src/components/SimpleAlert.tsx @@ -8,7 +8,7 @@ import globalize from 'lib/globalize'; import React from 'react'; interface SimpleAlertDialog extends DialogProps { - title: string; + title?: string; text: string; onClose: () => void }; @@ -16,9 +16,11 @@ interface SimpleAlertDialog extends DialogProps { const SimpleAlert = ({ open, title, text, onClose }: SimpleAlertDialog) => { return ( - - {title} - + {title && ( + + {title} + + )} {text} From 730d79636fd66db6b41d91b210a216166f6500d4 Mon Sep 17 00:00:00 2001 From: viown <48097677+viown@users.noreply.github.com> Date: Thu, 6 Mar 2025 04:31:31 +0300 Subject: [PATCH 3/3] Remove release date format option --- src/apps/dashboard/routes/libraries/nfo.tsx | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/apps/dashboard/routes/libraries/nfo.tsx b/src/apps/dashboard/routes/libraries/nfo.tsx index e7025f07e5..661bc6dad4 100644 --- a/src/apps/dashboard/routes/libraries/nfo.tsx +++ b/src/apps/dashboard/routes/libraries/nfo.tsx @@ -41,7 +41,7 @@ export const action = async ({ request }: ActionFunctionArgs) => { const newConfig: NFOSettingsConfig = { UserId: data.UserId?.toString(), - ReleaseDateFormat: data.ReleaseDateFormat?.toString(), + ReleaseDateFormat: 'yyyy-MM-dd', SaveImagePathsInNfo: data.SaveImagePathsInNfo?.toString() === 'on', EnablePathSubstitution: data.EnablePathSubstitution?.toString() === 'on', EnableExtraThumbsDuplication: data.EnableExtraThumbsDuplication?.toString() === 'on' @@ -133,16 +133,6 @@ export const Component = () => { )} - - yyyy-MM-dd - -