From e2f805da19d4b67776acc74b1795fb4e7503213c Mon Sep 17 00:00:00 2001 From: thornbill Date: Fri, 17 May 2024 13:52:49 -0400 Subject: [PATCH] Backport pull request #5529 from jellyfin-web/release-10.9.z Restore library menu tabs functionality Original-merge: 7ce8c070b39354c836434283daed6112e8bf7e9a Merged-by: thornbill Backported-by: Joshua M. Boniface --- src/apps/dashboard/AppLayout.tsx | 5 +- src/apps/dashboard/AppOverrides.scss | 12 ++- src/apps/dashboard/components/AppTabs.tsx | 96 +++++++++++++++++++ .../dashboard/encodingsettings.html | 2 +- src/controllers/dashboard/encodingsettings.js | 18 ---- src/controllers/dashboard/library.html | 2 +- src/controllers/dashboard/library.js | 19 ---- src/controllers/dashboard/librarydisplay.html | 2 +- src/controllers/dashboard/librarydisplay.js | 19 ---- src/controllers/dashboard/metadataImages.js | 18 ---- src/controllers/dashboard/metadataimages.html | 2 +- src/controllers/dashboard/metadatanfo.html | 2 +- src/controllers/dashboard/metadatanfo.js | 18 ---- src/controllers/dashboard/playback.html | 2 +- src/controllers/dashboard/playback.js | 19 ---- .../dashboard/plugins/available/index.html | 2 +- .../dashboard/plugins/available/index.js | 15 --- .../dashboard/plugins/installed/index.html | 2 +- .../dashboard/plugins/installed/index.js | 15 --- .../dashboard/plugins/repositories/index.html | 2 +- .../dashboard/plugins/repositories/index.js | 15 --- src/controllers/dashboard/streaming.html | 2 +- src/controllers/dashboard/streaming.js | 19 ---- src/scripts/libraryMenu.js | 3 + src/styles/dashboard.scss | 21 ---- src/types/eventType.ts | 1 + 26 files changed, 124 insertions(+), 209 deletions(-) create mode 100644 src/apps/dashboard/components/AppTabs.tsx diff --git a/src/apps/dashboard/AppLayout.tsx b/src/apps/dashboard/AppLayout.tsx index ca3827f96..ff47e53cc 100644 --- a/src/apps/dashboard/AppLayout.tsx +++ b/src/apps/dashboard/AppLayout.tsx @@ -11,6 +11,7 @@ import ElevationScroll from 'components/ElevationScroll'; import { DRAWER_WIDTH } from 'components/ResponsiveDrawer'; import { useApi } from 'hooks/useApi'; +import AppTabs from './components/AppTabs'; import AppDrawer from './components/drawer/AppDrawer'; import './AppOverrides.scss'; @@ -55,7 +56,9 @@ const AppLayout: FC = ({ isDrawerAvailable={!isMediumScreen && isDrawerAvailable} isDrawerOpen={isDrawerOpen} onDrawerButtonClick={onToggleDrawer} - /> + > + + diff --git a/src/apps/dashboard/AppOverrides.scss b/src/apps/dashboard/AppOverrides.scss index c8597ee57..43b25b149 100644 --- a/src/apps/dashboard/AppOverrides.scss +++ b/src/apps/dashboard/AppOverrides.scss @@ -16,7 +16,15 @@ $mui-bp-xl: 1536px; } // Fix the padding of dashboard pages - .content-primary.content-primary { - padding-top: 3.25rem !important; + .content-primary { + padding-top: 3.25rem; + } + // Tabbed pages + .withTabs .content-primary { + padding-top: 6.5rem; + + @media all and (min-width: $mui-bp-lg) { + padding-top: 3.25rem; + } } } diff --git a/src/apps/dashboard/components/AppTabs.tsx b/src/apps/dashboard/components/AppTabs.tsx new file mode 100644 index 000000000..f56d4df4a --- /dev/null +++ b/src/apps/dashboard/components/AppTabs.tsx @@ -0,0 +1,96 @@ +import { Theme } from '@mui/material/styles'; +import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import debounce from 'lodash-es/debounce'; +import isEqual from 'lodash-es/isEqual'; +import React, { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { Link } from 'react-router-dom'; + +import { EventType } from 'types/eventType'; +import Events, { type Event } from 'utils/events'; + +interface AppTabsParams { + isDrawerOpen: boolean +} + +interface TabDefinition { + href: string + name: string +} + +const handleResize = debounce(() => window.dispatchEvent(new Event('resize')), 100); + +const AppTabs: FC = ({ + isDrawerOpen +}) => { + const documentRef = useRef(document); + const [ activeIndex, setActiveIndex ] = useState(0); + const [ tabs, setTabs ] = useState(); + + const isBigScreen = useMediaQuery((theme: Theme) => theme.breakpoints.up('sm')); + + const onTabsUpdate = useCallback(( + _e: Event, + _newView?: string, + newIndex: number | undefined = 0, + newTabs?: TabDefinition[] + ) => { + setActiveIndex(newIndex); + + if (!isEqual(tabs, newTabs)) { + setTabs(newTabs); + } + }, [ tabs ]); + + useEffect(() => { + const doc = documentRef.current; + + if (doc) Events.on(doc, EventType.SET_TABS, onTabsUpdate); + + return () => { + if (doc) Events.off(doc, EventType.SET_TABS, onTabsUpdate); + }; + }, [ onTabsUpdate ]); + + // HACK: Force resizing to workaround upstream bug with tab resizing + // https://github.com/mui/material-ui/issues/24011 + useEffect(() => { + handleResize(); + }, [ isDrawerOpen ]); + + if (!tabs?.length) return null; + + return ( + + { + tabs.map(({ href, name }, index) => ( + + )) + } + + ); +}; + +export default AppTabs; diff --git a/src/controllers/dashboard/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html index 824b8f828..89dede8e4 100644 --- a/src/controllers/dashboard/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index 0ad65c3d3..66643483b 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -2,7 +2,6 @@ import 'jquery'; import loading from '../../components/loading/loading'; import globalize from '../../scripts/globalize'; import dom from '../../scripts/dom'; -import libraryMenu from '../../scripts/libraryMenu'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; @@ -167,22 +166,6 @@ function setDecodingCodecsVisible(context, value) { } } -function getTabs() { - return [{ - href: '#/dashboard/playback/transcoding', - name: globalize.translate('Transcoding') - }, { - href: '#/dashboard/playback/resume', - name: globalize.translate('ButtonResume') - }, { - href: '#/dashboard/playback/streaming', - name: globalize.translate('TabStreaming') - }, { - href: '#/dashboard/playback/trickplay', - name: globalize.translate('Trickplay') - }]; -} - let systemInfo; function getSystemInfo() { return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then( @@ -292,7 +275,6 @@ $(document).on('pageinit', '#encodingSettingsPage', function () { $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#encodingSettingsPage', function () { loading.show(); - libraryMenu.setTabs('playback', 0, getTabs); const page = this; ApiClient.getNamedConfiguration('encoding').then(function (config) { ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { diff --git a/src/controllers/dashboard/library.html b/src/controllers/dashboard/library.html index d66431273..843854b8b 100644 --- a/src/controllers/dashboard/library.html +++ b/src/controllers/dashboard/library.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 897ad3ecb..8d9dafde0 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -2,7 +2,6 @@ import escapeHtml from 'escape-html'; import 'jquery'; import taskButton from '../../scripts/taskbutton'; import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; import globalize from '../../scripts/globalize'; import dom from '../../scripts/dom'; import imageHelper from '../../utils/image'; @@ -358,22 +357,6 @@ function getVirtualFolderHtml(page, virtualFolder, index) { return html; } -function getTabs() { - return [{ - href: '#/dashboard/libraries', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/dashboard/libraries/display', - name: globalize.translate('Display') - }, { - href: '#/dashboard/libraries/metadata', - name: globalize.translate('Metadata') - }, { - href: '#/dashboard/libraries/nfo', - name: globalize.translate('TabNfoSettings') - }]; -} - window.WizardLibraryPage = { next: function () { Dashboard.navigate('wizardsettings.html'); @@ -383,8 +366,6 @@ pageClassOn('pageshow', 'mediaLibraryPage', function () { reloadLibrary(this); }); pageIdOn('pageshow', 'mediaLibraryPage', function () { - libraryMenu.setTabs('librarysetup', 0, getTabs); - const page = this; taskButton({ mode: 'on', diff --git a/src/controllers/dashboard/librarydisplay.html b/src/controllers/dashboard/librarydisplay.html index 04a855482..d7bcffafd 100644 --- a/src/controllers/dashboard/librarydisplay.html +++ b/src/controllers/dashboard/librarydisplay.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index a38608277..bae62009d 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -1,26 +1,8 @@ -import globalize from '../../scripts/globalize'; import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-button/emby-button'; import Dashboard from '../../utils/dashboard'; -function getTabs() { - return [{ - href: '#/dashboard/libraries', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/dashboard/libraries/display', - name: globalize.translate('Display') - }, { - href: '#/dashboard/libraries/metadata', - name: globalize.translate('Metadata') - }, { - href: '#/dashboard/libraries/nfo', - name: globalize.translate('TabNfoSettings') - }]; -} - export default function(view) { function loadData() { ApiClient.getServerConfiguration().then(function(config) { @@ -57,7 +39,6 @@ export default function(view) { }); view.addEventListener('viewshow', function() { - libraryMenu.setTabs('librarysetup', 1, getTabs); loadData(); ApiClient.getSystemInfo().then(function(info) { if (info.OperatingSystem === 'Windows') { diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index 076700fd6..81b74b218 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -3,7 +3,6 @@ import { ImageResolution } from '@jellyfin/sdk/lib/generated-client/models/image import 'jquery'; import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; @@ -86,26 +85,9 @@ function onSubmit() { return false; } -function getTabs() { - return [{ - href: '#/dashboard/libraries', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/dashboard/libraries/display', - name: globalize.translate('Display') - }, { - href: '#/dashboard/libraries/metadata', - name: globalize.translate('Metadata') - }, { - href: '#/dashboard/libraries/nfo', - name: globalize.translate('TabNfoSettings') - }]; -} - $(document).on('pageinit', '#metadataImagesConfigurationPage', function() { $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#metadataImagesConfigurationPage', function() { - libraryMenu.setTabs('metadata', 2, getTabs); loading.show(); loadPage(this); }); diff --git a/src/controllers/dashboard/metadataimages.html b/src/controllers/dashboard/metadataimages.html index 251f5b823..afa7a4bc9 100644 --- a/src/controllers/dashboard/metadataimages.html +++ b/src/controllers/dashboard/metadataimages.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/metadatanfo.html b/src/controllers/dashboard/metadatanfo.html index 44d1cc06f..362be76fa 100644 --- a/src/controllers/dashboard/metadatanfo.html +++ b/src/controllers/dashboard/metadatanfo.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index 1a68d4d63..9e8211789 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -1,7 +1,6 @@ import escapeHtml from 'escape-html'; import 'jquery'; import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; @@ -44,27 +43,10 @@ function showConfirmMessage() { }); } -function getTabs() { - return [{ - href: '#/dashboard/libraries', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/dashboard/libraries/display', - name: globalize.translate('Display') - }, { - href: '#/dashboard/libraries/metadata', - name: globalize.translate('Metadata') - }, { - href: '#/dashboard/libraries/nfo', - name: globalize.translate('TabNfoSettings') - }]; -} - const metadataKey = 'xbmcmetadata'; $(document).on('pageinit', '#metadataNfoPage', function () { $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#metadataNfoPage', function () { - libraryMenu.setTabs('metadata', 3, getTabs); loading.show(); const page = this; const promise1 = ApiClient.getUsers(); diff --git a/src/controllers/dashboard/playback.html b/src/controllers/dashboard/playback.html index 302d00d37..ff7e2bf50 100644 --- a/src/controllers/dashboard/playback.html +++ b/src/controllers/dashboard/playback.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index f0d35f02e..e9812ab36 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -1,7 +1,5 @@ import 'jquery'; import loading from '../../components/loading/loading'; -import libraryMenu from '../../scripts/libraryMenu'; -import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; function loadPage(page, config) { @@ -29,27 +27,10 @@ function onSubmit() { return false; } -function getTabs() { - return [{ - href: '#/dashboard/playback/transcoding', - name: globalize.translate('Transcoding') - }, { - href: '#/dashboard/playback/resume', - name: globalize.translate('ButtonResume') - }, { - href: '#/dashboard/playback/streaming', - name: globalize.translate('TabStreaming') - }, { - href: '#/dashboard/playback/trickplay', - name: globalize.translate('Trickplay') - }]; -} - $(document).on('pageinit', '#playbackConfigurationPage', function () { $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#playbackConfigurationPage', function () { loading.show(); - libraryMenu.setTabs('playback', 1, getTabs); const page = this; ApiClient.getServerConfiguration().then(function (config) { loadPage(page, config); diff --git a/src/controllers/dashboard/plugins/available/index.html b/src/controllers/dashboard/plugins/available/index.html index 68567db74..dde4d2ceb 100644 --- a/src/controllers/dashboard/plugins/available/index.html +++ b/src/controllers/dashboard/plugins/available/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 8ce093229..aafcea2f7 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -1,7 +1,6 @@ import escapeHTML from 'escape-html'; import loading from '../../../../components/loading/loading'; -import libraryMenu from '../../../../scripts/libraryMenu'; import globalize from '../../../../scripts/globalize'; import '../../../../components/cardbuilder/card.scss'; import '../../../../elements/emby-button/emby-button'; @@ -159,22 +158,8 @@ function getPluginHtml(plugin, options, installedPlugins) { return html; } -function getTabs() { - return [{ - href: '#/dashboard/plugins', - name: globalize.translate('TabMyPlugins') - }, { - href: '#/dashboard/plugins/catalog', - name: globalize.translate('TabCatalog') - }, { - href: '#/dashboard/plugins/repositories', - name: globalize.translate('TabRepositories') - }]; -} - export default function (view) { view.addEventListener('viewshow', function () { - libraryMenu.setTabs('plugins', 1, getTabs); reloadList(this); }); } diff --git a/src/controllers/dashboard/plugins/installed/index.html b/src/controllers/dashboard/plugins/installed/index.html index 4aedb8a9d..a1f74453d 100644 --- a/src/controllers/dashboard/plugins/installed/index.html +++ b/src/controllers/dashboard/plugins/installed/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/plugins/installed/index.js b/src/controllers/dashboard/plugins/installed/index.js index 9600eb0a3..15483d4c7 100644 --- a/src/controllers/dashboard/plugins/installed/index.js +++ b/src/controllers/dashboard/plugins/installed/index.js @@ -1,5 +1,4 @@ import loading from '../../../../components/loading/loading'; -import libraryMenu from '../../../../scripts/libraryMenu'; import dom from '../../../../scripts/dom'; import globalize from '../../../../scripts/globalize'; import '../../../../components/cardbuilder/card.scss'; @@ -219,19 +218,6 @@ function reloadList(page) { }); } -function getTabs() { - return [{ - href: '#/dashboard/plugins', - name: globalize.translate('TabMyPlugins') - }, { - href: '#/dashboard/plugins/catalog', - name: globalize.translate('TabCatalog') - }, { - href: '#/dashboard/plugins/repositories', - name: globalize.translate('TabRepositories') - }]; -} - function onInstalledPluginsClick(e) { if (dom.parentWithClass(e.target, 'noConfigPluginCard')) { showNoConfigurationMessage(); @@ -257,7 +243,6 @@ function onFilterType(page, searchBar) { } pageIdOn('pageshow', 'pluginsPage', function () { - libraryMenu.setTabs('plugins', 0, getTabs); reloadList(this); }); diff --git a/src/controllers/dashboard/plugins/repositories/index.html b/src/controllers/dashboard/plugins/repositories/index.html index 68dab3c07..e793df253 100644 --- a/src/controllers/dashboard/plugins/repositories/index.html +++ b/src/controllers/dashboard/plugins/repositories/index.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/plugins/repositories/index.js b/src/controllers/dashboard/plugins/repositories/index.js index 55ff12a45..39110cd2e 100644 --- a/src/controllers/dashboard/plugins/repositories/index.js +++ b/src/controllers/dashboard/plugins/repositories/index.js @@ -1,5 +1,4 @@ import loading from '../../../../components/loading/loading'; -import libraryMenu from '../../../../scripts/libraryMenu'; import globalize from '../../../../scripts/globalize'; import dialogHelper from '../../../../components/dialogHelper/dialogHelper'; import confirm from '../../../../components/confirm/confirm'; @@ -103,22 +102,8 @@ function getRepositoryElement(repository) { return listItem; } -function getTabs() { - return [{ - href: '#/dashboard/plugins', - name: globalize.translate('TabMyPlugins') - }, { - href: '#/dashboard/plugins/catalog', - name: globalize.translate('TabCatalog') - }, { - href: '#/dashboard/plugins/repositories', - name: globalize.translate('TabRepositories') - }]; -} - export default function(view) { view.addEventListener('viewshow', function () { - libraryMenu.setTabs('plugins', 2, getTabs); reloadList(this); const save = this; diff --git a/src/controllers/dashboard/streaming.html b/src/controllers/dashboard/streaming.html index 1916c264f..4219e63a5 100644 --- a/src/controllers/dashboard/streaming.html +++ b/src/controllers/dashboard/streaming.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index d6ca05874..79fd0d28f 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -1,7 +1,5 @@ import 'jquery'; -import libraryMenu from '../../scripts/libraryMenu'; import loading from '../../components/loading/loading'; -import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; function loadPage(page, config) { @@ -20,27 +18,10 @@ function onSubmit() { return false; } -function getTabs() { - return [{ - href: '#/dashboard/playback/transcoding', - name: globalize.translate('Transcoding') - }, { - href: '#/dashboard/playback/resume', - name: globalize.translate('ButtonResume') - }, { - href: '#/dashboard/playback/streaming', - name: globalize.translate('TabStreaming') - }, { - href: '#/dashboard/playback/trickplay', - name: globalize.translate('Trickplay') - }]; -} - $(document).on('pageinit', '#streamingSettingsPage', function () { $('.streamingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); }).on('pageshow', '#streamingSettingsPage', function () { loading.show(); - libraryMenu.setTabs('playback', 2, getTabs); const page = this; ApiClient.getServerConfiguration().then(function (config) { loadPage(page, config); diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 725289e46..5de1083b6 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -30,6 +30,7 @@ import '../elements/emby-button/paper-icon-button-light'; import 'material-design-icons-iconfont'; import '../styles/scrollstyles.scss'; import '../styles/flexstyles.scss'; +import { EventType } from 'types/eventType'; function renderHeader() { let html = ''; @@ -703,6 +704,8 @@ const skinHeader = document.querySelector('.skinHeader'); let requiresUserRefresh = true; function setTabs (type, selectedIndex, builder) { + Events.trigger(document, EventType.SET_TABS, type ? [ type, selectedIndex, builder()] : []); + import('../components/maintabsmanager').then((mainTabsManager) => { if (type) { mainTabsManager.setTabs(viewManager.currentView(), selectedIndex, builder, function () { diff --git a/src/styles/dashboard.scss b/src/styles/dashboard.scss index 40b4e2f11..dbd6b9bbf 100644 --- a/src/styles/dashboard.scss +++ b/src/styles/dashboard.scss @@ -152,31 +152,10 @@ div[data-role=controlgroup] a.ui-btn-active { } .content-primary { - padding-top: 6em; padding-right: 1em; padding-left: 1em; } -.withTabs .content-primary { - padding-top: 9em !important; -} - -@media all and (min-width: 40em) { - .content-primary { - padding-top: 4.6em; - } - - .withTabs .content-primary { - padding-top: 10em !important; - } -} - -@media all and (min-width: 84em) { - .withTabs .content-primary { - padding-top: 7em !important; - } -} - .content-primary ul:first-child { margin-top: 0; } diff --git a/src/types/eventType.ts b/src/types/eventType.ts index 929893ccb..2568009c7 100644 --- a/src/types/eventType.ts +++ b/src/types/eventType.ts @@ -2,5 +2,6 @@ * Custom event types. */ export enum EventType { + SET_TABS = 'SET_TABS', SHOW_VIDEO_OSD = 'SHOW_VIDEO_OSD' }