diff --git a/packages/app/resources/strings.json b/packages/app/resources/strings.json index dd0d71445..0acdac021 100644 --- a/packages/app/resources/strings.json +++ b/packages/app/resources/strings.json @@ -2417,5 +2417,20 @@ "{value} (converted from {source})": "{value} (converted from {source})", "{value} (original {original})": "{value} (original {original})", "◀ ▶ Navigate": "◀ ▶ Navigate", - "◀ ▶ to re-order · DEL to remove": "◀ ▶ to re-order · DEL to remove" + "◀ ▶ to re-order · DEL to remove": "◀ ▶ to re-order · DEL to remove", + "Toggle Media Description Visibility": "Toggle Media Description Visibility", + "Hide media descriptions from various detail pages.": "Hide media descriptions from various detail pages.", + "Local Content": "Local Content", + "Hide descriptions on movie detail pages.": "Hide descriptions on movie detail pages.", + "Hide descriptions on TV show detail pages.": "Hide descriptions on TV show detail pages.", + "Hide descriptions on season detail pages.": "Hide descriptions on season detail pages.", + "Hide descriptions on episode detail pages.": "Hide descriptions on episode detail pages.", + "Hide descriptions on box set and collection detail pages.": "Hide descriptions on box set and collection detail pages.", + "Hide descriptions on book and audiobook detail pages.": "Hide descriptions on book and audiobook detail pages.", + "Hide descriptions on person and actor detail pages.": "Hide descriptions on person and actor detail pages.", + "Hide descriptions on album and artist detail pages.": "Hide descriptions on album and artist detail pages.", + "Seerr Content": "Seerr Content", + "Hide descriptions on Seerr movie discovery pages.": "Hide descriptions on Seerr movie discovery pages.", + "Hide descriptions on Seerr TV show discovery pages.": "Hide descriptions on Seerr TV show discovery pages.", + "Hide descriptions on Seerr collection discovery pages.": "Hide descriptions on Seerr collection discovery pages." } diff --git a/packages/app/src/context/SettingsContext.js b/packages/app/src/context/SettingsContext.js index 5381d85bf..6c2c267e0 100644 --- a/packages/app/src/context/SettingsContext.js +++ b/packages/app/src/context/SettingsContext.js @@ -287,7 +287,11 @@ export const SYNCABLE_KEYS = [ 'mediaBarOverlayColor', 'mediaBarOverlayOpacity', 'homeRows', 'homeRowsStyle', 'modernCardsOnMyMediaRow', 'detailScreenStyle', 'detailExpandedTabs', 'fullScreenRows', 'homeRowsPosterSize', 'useSeriesThumbnails', 'episodePreviewEnabled', 'previewAudioEnabled', 'previewVolume', - 'hideDetailsMediaDescription', 'detailUseSeriesThumbnails', 'hideHomeMediaDescription', + 'hideDetailsMediaDescription', + 'hideDetailsMediaDescriptionMovie', 'hideDetailsMediaDescriptionShow', 'hideDetailsMediaDescriptionSeason', 'hideDetailsMediaDescriptionEpisode', + 'hideDetailsMediaDescriptionBoxSet', 'hideDetailsMediaDescriptionBook', 'hideDetailsMediaDescriptionPerson', 'hideDetailsMediaDescriptionMusic', + 'hideDetailsMediaDescriptionSeerrMovie', 'hideDetailsMediaDescriptionSeerrShow', 'hideDetailsMediaDescriptionSeerrCollection', + 'detailUseSeriesThumbnails', 'hideHomeMediaDescription', 'personalRatingStyle', 'recentlyReleasedSeriesType', 'mergeRecentRowsByType', 'playlistsGroupByType', 'groupItemsIntoCollections', 'useDetailedSubHeadings', 'showMediaDetailsOnLibraryPage', 'hideBackdropsInLibraries', 'libraryStyle', 'musicLibraryStyle', 'syncplayEnabled', 'syncplayAutoOpen', @@ -354,6 +358,19 @@ export const profileToLocal = (serverProfile) => { if (serverProfile.tmdbApiKey !== undefined && serverProfile.tmdbApiKey !== null) { local.tmdbApiKey = serverProfile.tmdbApiKey; } + if (typeof serverProfile.hideDetailsMediaDescription === 'boolean') { + const legacyValue = serverProfile.hideDetailsMediaDescription; + if (local.hideDetailsMediaDescriptionMovie === undefined) { + local.hideDetailsMediaDescriptionMovie = legacyValue; + } + if (local.hideDetailsMediaDescriptionEpisode === undefined) { + local.hideDetailsMediaDescriptionEpisode = legacyValue; + } + if (local.hideDetailsMediaDescriptionSeerrMovie === undefined) { + local.hideDetailsMediaDescriptionSeerrMovie = legacyValue; + } + delete local.hideDetailsMediaDescription; + } return local; }; @@ -786,6 +803,22 @@ export function SettingsProvider({children}) { delete stored.liveTvDirect; migrated = true; } + if ('hideDetailsMediaDescription' in stored) { + if (typeof stored.hideDetailsMediaDescription === 'boolean') { + const legacyValue = stored.hideDetailsMediaDescription; + if (stored.hideDetailsMediaDescriptionMovie === undefined) { + stored.hideDetailsMediaDescriptionMovie = legacyValue; + } + if (stored.hideDetailsMediaDescriptionEpisode === undefined) { + stored.hideDetailsMediaDescriptionEpisode = legacyValue; + } + if (stored.hideDetailsMediaDescriptionSeerrMovie === undefined) { + stored.hideDetailsMediaDescriptionSeerrMovie = legacyValue; + } + } + delete stored.hideDetailsMediaDescription; + migrated = true; + } const merged = {...defaultSettings, ...stored}; setSettings(merged); if (migrated) saveToStorage('settings', merged); diff --git a/packages/app/src/context/defaultSettings.js b/packages/app/src/context/defaultSettings.js index f63162fd9..9be80ace7 100644 --- a/packages/app/src/context/defaultSettings.js +++ b/packages/app/src/context/defaultSettings.js @@ -258,6 +258,17 @@ export const defaultSettings = { personalRatingStyle: 'thumbs', detailExpandedTabs: true, hideDetailsMediaDescription: false, + hideDetailsMediaDescriptionMovie: false, + hideDetailsMediaDescriptionShow: false, + hideDetailsMediaDescriptionSeason: false, + hideDetailsMediaDescriptionEpisode: false, + hideDetailsMediaDescriptionBoxSet: false, + hideDetailsMediaDescriptionBook: false, + hideDetailsMediaDescriptionPerson: false, + hideDetailsMediaDescriptionMusic: false, + hideDetailsMediaDescriptionSeerrMovie: false, + hideDetailsMediaDescriptionSeerrShow: false, + hideDetailsMediaDescriptionSeerrCollection: false, detailUseSeriesThumbnails: false, hideHomeMediaDescription: false, homeRowOverlay: true, diff --git a/packages/app/src/context/settingsSync.test.js b/packages/app/src/context/settingsSync.test.js index 647cf1fb6..561486c66 100644 --- a/packages/app/src/context/settingsSync.test.js +++ b/packages/app/src/context/settingsSync.test.js @@ -568,6 +568,41 @@ describe('the per row image types', () => { expect(resolveFromEnvelope({}, {homeRowImageTypes: 'genres:thumb'}).homeRowImageTypes).toEqual({genres: 'thumb'}); expect(resolveFromEnvelope({}, {})).not.toHaveProperty('homeRowImageTypes'); }); + + test('migrates legacy hideDetailsMediaDescription to granular movie, episode, and seerr movie settings', () => { + const localTrue = profileToLocal({hideDetailsMediaDescription: true}); + expect(localTrue.hideDetailsMediaDescriptionMovie).toBe(true); + expect(localTrue.hideDetailsMediaDescriptionEpisode).toBe(true); + expect(localTrue.hideDetailsMediaDescriptionSeerrMovie).toBe(true); + expect(localTrue.hideDetailsMediaDescription).toBeUndefined(); + + const localFalse = profileToLocal({hideDetailsMediaDescription: false}); + expect(localFalse.hideDetailsMediaDescriptionMovie).toBe(false); + expect(localFalse.hideDetailsMediaDescriptionEpisode).toBe(false); + expect(localFalse.hideDetailsMediaDescriptionSeerrMovie).toBe(false); + expect(localFalse.hideDetailsMediaDescription).toBeUndefined(); + + const partial = profileToLocal({ + hideDetailsMediaDescription: true, + hideDetailsMediaDescriptionMovie: false + }); + expect(partial.hideDetailsMediaDescriptionMovie).toBe(false); + expect(partial.hideDetailsMediaDescriptionEpisode).toBe(true); + expect(partial.hideDetailsMediaDescriptionSeerrMovie).toBe(true); + expect(partial.hideDetailsMediaDescription).toBeUndefined(); + }); + + test('preserves explicit false legacy hideDetailsMediaDescription across profile layers', () => { + const envelope = { + tv: {hideDetailsMediaDescription: false}, + global: {hideDetailsMediaDescription: true} + }; + const resolved = resolveFromEnvelope(envelope, null, 'tv'); + expect(resolved.hideDetailsMediaDescriptionMovie).toBe(false); + expect(resolved.hideDetailsMediaDescriptionEpisode).toBe(false); + expect(resolved.hideDetailsMediaDescriptionSeerrMovie).toBe(false); + expect(resolved.hideDetailsMediaDescription).toBeUndefined(); + }); }); describe('the profile push queue', () => { diff --git a/packages/app/src/utils/kidsMode.js b/packages/app/src/utils/kidsMode.js index 37572a944..f7ba80aee 100644 --- a/packages/app/src/utils/kidsMode.js +++ b/packages/app/src/utils/kidsMode.js @@ -24,6 +24,17 @@ const KIDS_MODE_DETAILS = { detailShowTechnicalDetails: false, // Named for hiding, so the mode turns it on to leave the description out. hideDetailsMediaDescription: true, + hideDetailsMediaDescriptionMovie: true, + hideDetailsMediaDescriptionShow: true, + hideDetailsMediaDescriptionSeason: true, + hideDetailsMediaDescriptionEpisode: true, + hideDetailsMediaDescriptionBoxSet: true, + hideDetailsMediaDescriptionBook: true, + hideDetailsMediaDescriptionPerson: true, + hideDetailsMediaDescriptionMusic: true, + hideDetailsMediaDescriptionSeerrMovie: true, + hideDetailsMediaDescriptionSeerrShow: true, + hideDetailsMediaDescriptionSeerrCollection: true, detailUseSeriesThumbnails: false, // The online source is an outside catalogue no parental rating reaches, so recommendations stay // inside the server's own library. diff --git a/packages/app/src/utils/kidsMode.test.js b/packages/app/src/utils/kidsMode.test.js index f21c65ae5..ce229dfc0 100644 --- a/packages/app/src/utils/kidsMode.test.js +++ b/packages/app/src/utils/kidsMode.test.js @@ -211,8 +211,19 @@ describe('the details screen', () => { // The preference is named for hiding, so leaving the description out means forcing it on. test('hiding the description means forcing its flag on, not off', () => { - expect(kidsModeSettings({...stored, kidsModeEnabled: true}).hideDetailsMediaDescription) - .toBe(true); + const effective = kidsModeSettings({...stored, kidsModeEnabled: true}); + expect(effective.hideDetailsMediaDescription).toBe(true); + expect(effective.hideDetailsMediaDescriptionMovie).toBe(true); + expect(effective.hideDetailsMediaDescriptionShow).toBe(true); + expect(effective.hideDetailsMediaDescriptionSeason).toBe(true); + expect(effective.hideDetailsMediaDescriptionEpisode).toBe(true); + expect(effective.hideDetailsMediaDescriptionBoxSet).toBe(true); + expect(effective.hideDetailsMediaDescriptionBook).toBe(true); + expect(effective.hideDetailsMediaDescriptionPerson).toBe(true); + expect(effective.hideDetailsMediaDescriptionMusic).toBe(true); + expect(effective.hideDetailsMediaDescriptionSeerrMovie).toBe(true); + expect(effective.hideDetailsMediaDescriptionSeerrShow).toBe(true); + expect(effective.hideDetailsMediaDescriptionSeerrCollection).toBe(true); }); // Read never written, so the stored style still syncs as the user picked it. diff --git a/packages/app/src/views/Details/AlbumScreen.js b/packages/app/src/views/Details/AlbumScreen.js index 7bec9ea55..1f3e27246 100644 --- a/packages/app/src/views/Details/AlbumScreen.js +++ b/packages/app/src/views/Details/AlbumScreen.js @@ -8,6 +8,7 @@ import {DETAIL_ICON_PATHS} from './detailIcons'; import {SpottableDiv, HorizontalContainer} from './detailsSpottables'; import {handleSeasonButtonKeyDown} from './detailsFocus'; import {PosterBadges, WatchedCheckIcon} from './DetailBadges'; +import {hidesMediaDescription} from './detailsMedia'; import css from './Details.module.less'; @@ -112,7 +113,7 @@ const AlbumScreen = ({ })} - {item.Overview && ( + {item.Overview && !hidesMediaDescription(item, settings) && (
{item.Overview}
{item.Overview}
} + {item.Overview && !hidesMediaDescription(item, settings) &&{item.Overview}
}{item.Overview}
} + {item.Overview && !hidesMediaDescription(item, settings) &&{item.Overview}
} {showsSection('poster') && ({item.Overview}
} + {item.Overview && !hidesMediaDescription(item, settings) &&{item.Overview}
} @@ -272,6 +274,7 @@ PlaylistScreen.propTypes = { playlistMenu: PropTypes.object, posterUrl: PropTypes.string, serverUrl: PropTypes.string, + settings: PropTypes.object, showsSection: PropTypes.func.isRequired, spotlightBackRef: PropTypes.oneOfType([ PropTypes.func, diff --git a/packages/app/src/views/Details/detailsMedia.js b/packages/app/src/views/Details/detailsMedia.js index fa843506f..c9dad3823 100644 --- a/packages/app/src/views/Details/detailsMedia.js +++ b/packages/app/src/views/Details/detailsMedia.js @@ -18,12 +18,63 @@ export const COLLECTION_ITEM_TYPES = ['Movie', 'Series', 'Season', 'Episode', 'V export const IDENTIFIABLE_TYPES = ['Movie', 'Series', 'Season', 'Episode', 'BoxSet', 'Person', 'MusicAlbum', 'MusicArtist', 'Book', 'Trailer', 'MusicVideo']; -// Whether the summary is being kept back for someone avoiding spoilers. Only a -// film or an episode gives the plot away, so a series or a season keeps its -// description either way. -export const hidesMediaDescription = (item, settings) => - settings?.hideDetailsMediaDescription === true && - (item?.Type === 'Movie' || item?.Type === 'Episode'); +// Whether the summary is being kept back for someone avoiding spoilers or hiding descriptions. +export const hidesMediaDescription = (item, settings) => { + if (!item || !item.Type) return false; + if (settings?.kidsModeEnabled === true) return true; + + const isSeerr = item._seerrMediaId != null || + item._seerrImdbId != null || + item.isSeerr === true || + (typeof item.Id === 'string' && item.Id.startsWith('seerr-')); + const type = item.Type; + + if (isSeerr) { + switch (type) { + case 'Movie': + return Boolean(settings?.hideDetailsMediaDescriptionSeerrMovie || settings?.hideDetailsMediaDescription); + case 'Series': + case 'Show': + case 'Tv': + return Boolean(settings?.hideDetailsMediaDescriptionSeerrShow); + case 'BoxSet': + case 'Collection': + return Boolean(settings?.hideDetailsMediaDescriptionSeerrCollection); + case 'Person': + return Boolean(settings?.hideDetailsMediaDescriptionPerson); + default: + return false; + } + } + + switch (type) { + case 'Movie': + return Boolean(settings?.hideDetailsMediaDescriptionMovie || settings?.hideDetailsMediaDescription); + case 'Series': + case 'Show': + case 'Tv': + return Boolean(settings?.hideDetailsMediaDescriptionShow); + case 'Season': + return Boolean(settings?.hideDetailsMediaDescriptionSeason); + case 'Episode': + return Boolean(settings?.hideDetailsMediaDescriptionEpisode || settings?.hideDetailsMediaDescription); + case 'BoxSet': + case 'Collection': + return Boolean(settings?.hideDetailsMediaDescriptionBoxSet); + case 'Book': + case 'AudioBook': + return Boolean(settings?.hideDetailsMediaDescriptionBook); + case 'Person': + return Boolean(settings?.hideDetailsMediaDescriptionPerson); + case 'MusicAlbum': + case 'MusicArtist': + case 'Audio': + case 'Playlist': + return Boolean(settings?.hideDetailsMediaDescriptionMusic); + default: + return false; + } +}; // The parent series artwork, for standing in where an episode still or a chapter // frame would give something away. Null when the series offers no artwork, which diff --git a/packages/app/src/views/Details/detailsMedia.test.js b/packages/app/src/views/Details/detailsMedia.test.js index b7f9148dd..68a16eda4 100644 --- a/packages/app/src/views/Details/detailsMedia.test.js +++ b/packages/app/src/views/Details/detailsMedia.test.js @@ -38,6 +38,56 @@ describe('hidesMediaDescription', () => { expect(hidesMediaDescription({Type: 'Movie'}, off)).toBe(false); expect(hidesMediaDescription({Type: 'Episode'}, off)).toBe(false); }); + + test('local content items are hidden when their specific setting is on', () => { + expect(hidesMediaDescription({Type: 'Movie'}, {hideDetailsMediaDescriptionMovie: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Series'}, {hideDetailsMediaDescriptionShow: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Show'}, {hideDetailsMediaDescriptionShow: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Tv'}, {hideDetailsMediaDescriptionShow: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Season'}, {hideDetailsMediaDescriptionSeason: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Episode'}, {hideDetailsMediaDescriptionEpisode: true})).toBe(true); + expect(hidesMediaDescription({Type: 'BoxSet'}, {hideDetailsMediaDescriptionBoxSet: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Collection'}, {hideDetailsMediaDescriptionBoxSet: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Book'}, {hideDetailsMediaDescriptionBook: true})).toBe(true); + expect(hidesMediaDescription({Type: 'AudioBook'}, {hideDetailsMediaDescriptionBook: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Person'}, {hideDetailsMediaDescriptionPerson: true})).toBe(true); + expect(hidesMediaDescription({Type: 'MusicAlbum'}, {hideDetailsMediaDescriptionMusic: true})).toBe(true); + expect(hidesMediaDescription({Type: 'MusicArtist'}, {hideDetailsMediaDescriptionMusic: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Audio'}, {hideDetailsMediaDescriptionMusic: true})).toBe(true); + expect(hidesMediaDescription({Type: 'Playlist'}, {hideDetailsMediaDescriptionMusic: true})).toBe(true); + }); + + test('seerr content items respect seerr specific settings', () => { + const seerrMovie = {Type: 'Movie', _seerrMediaId: 100}; + const seerrShow = {Type: 'Series', _seerrMediaId: 200}; + const seerrTv = {Type: 'Tv', isSeerr: true}; + const seerrCollection = {Type: 'BoxSet', Id: 'seerr-collection-300'}; + const seerrPerson = {Type: 'Person', _seerrMediaId: 400}; + + expect(hidesMediaDescription(seerrMovie, {hideDetailsMediaDescriptionSeerrMovie: true})).toBe(true); + expect(hidesMediaDescription(seerrMovie, {hideDetailsMediaDescription: true})).toBe(true); + expect(hidesMediaDescription(seerrMovie, {hideDetailsMediaDescriptionMovie: true})).toBe(false); + + expect(hidesMediaDescription(seerrShow, {hideDetailsMediaDescriptionSeerrShow: true})).toBe(true); + expect(hidesMediaDescription(seerrTv, {hideDetailsMediaDescriptionSeerrShow: true})).toBe(true); + expect(hidesMediaDescription(seerrShow, {hideDetailsMediaDescriptionShow: true})).toBe(false); + + expect(hidesMediaDescription(seerrCollection, {hideDetailsMediaDescriptionSeerrCollection: true})).toBe(true); + expect(hidesMediaDescription(seerrCollection, {hideDetailsMediaDescriptionBoxSet: true})).toBe(false); + + expect(hidesMediaDescription(seerrPerson, {hideDetailsMediaDescriptionPerson: true})).toBe(true); + }); + + test('kids mode forces all media descriptions to be hidden', () => { + const kidsSettings = {kidsModeEnabled: true}; + expect(hidesMediaDescription({Type: 'Movie'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'Series'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'Season'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'Episode'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'BoxSet'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'Person'}, kidsSettings)).toBe(true); + expect(hidesMediaDescription({Type: 'Movie', _seerrMediaId: 1}, kidsSettings)).toBe(true); + }); }); describe('splitCastAndCrew', () => { diff --git a/packages/app/src/views/Details/nouveau/NouveauDetailContent.js b/packages/app/src/views/Details/nouveau/NouveauDetailContent.js index e0c225aa4..d13c56d46 100644 --- a/packages/app/src/views/Details/nouveau/NouveauDetailContent.js +++ b/packages/app/src/views/Details/nouveau/NouveauDetailContent.js @@ -4,7 +4,7 @@ import Spotlight from '@enact/spotlight'; import {getImageUrl} from '../../../utils/helpers'; import {rem, rootScale} from '../../../utils/rootScale'; -import {castPhotoUrl, seriesThumbUrl} from '../detailsMedia'; +import {castPhotoUrl, hidesMediaDescription, seriesThumbUrl} from '../detailsMedia'; import {RowContainer, SpottableDiv} from '../detailsSpottables'; import {spotlightItemImageUrl} from '../spotlight/spotlightImages'; import {nouveauCardImageUrl} from './nouveauCardImage'; @@ -362,11 +362,11 @@ const NouveauDetailContent = (props) => { }, [sections, sortedPlaylist.length, discovery.related.length, discovery.recommendations.length]); const chain = useMemo(() => buildNouveauChain({ - hasOverview: Boolean(item.Overview) && (!isPerson || showsSection('biography')), + hasOverview: Boolean(item.Overview) && !hidesMediaDescription(item, settings) && (!isPerson || showsSection('biography')), hasSeasonSelector: isSeries && seasonNumbers.length > 1, rails: railNodes, footerRows: sections.includes(SECTION_DETAILS) ? 1 : 0 - }), [item.Overview, isPerson, showsSection, isSeries, seasonNumbers.length, railNodes, sections]); + }), [item, settings, isPerson, showsSection, isSeries, seasonNumbers.length, railNodes, sections]); chainRef.current = chain; diff --git a/packages/app/src/views/Person/Person.js b/packages/app/src/views/Person/Person.js index b71b41528..07133987e 100644 --- a/packages/app/src/views/Person/Person.js +++ b/packages/app/src/views/Person/Person.js @@ -11,6 +11,8 @@ import {useUserDataList} from '../../hooks/useUserDataSync'; import {getImageUrl} from '../../utils/helpers'; import {personDateLines, splitFilmography, withPersonRoles} from '../../utils/personCredits'; import {sectionVisibility} from '../../utils/detailSectionLayout'; +import {kidsModeSettings} from '../../utils/kidsMode'; +import {hidesMediaDescription} from '../Details/detailsMedia'; import css from './Person.module.less'; @@ -20,7 +22,8 @@ import css from './Person.module.less'; // overview and tab bar all come from the shared PersonDetailShell. const Person = ({personId, onSelectItem, onSelectSeerrItem, onSelectSeerrPerson}) => { const {api, serverUrl} = useAuth(); - const {settings} = useSettings(); + const {settings: storedSettings} = useSettings(); + const settings = useMemo(() => kidsModeSettings(storedSettings), [storedSettings]); // The detail sections setting reaches this page too, since it stands in for the Details // screen on a person under every style but Nouveau. const showsSection = useMemo(() => sectionVisibility(settings.hiddenDetailSectionsTv), [settings.hiddenDetailSectionsTv]); @@ -161,6 +164,9 @@ const Person = ({personId, onSelectItem, onSelectSeerrItem, onSelectSeerrPerson} actions.push({key: 'seerr', label: $L('Seerr'), icon: null, onClick: handleOpenSeerr}); } + const personItem = person.Type ? person : {...person, Type: 'Person'}; + const hideOverview = !showsSection('biography') || hidesMediaDescription(personItem, settings); + return (