Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion packages/app/resources/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
35 changes: 34 additions & 1 deletion packages/app/src/context/SettingsContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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;
};

Expand Down Expand Up @@ -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);
Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/context/defaultSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
35 changes: 35 additions & 0 deletions packages/app/src/context/settingsSync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/utils/kidsMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 13 additions & 2 deletions packages/app/src/utils/kidsMode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/views/Details/AlbumScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -112,7 +113,7 @@ const AlbumScreen = ({
})}
</div>

{item.Overview && (
{item.Overview && !hidesMediaDescription(item, settings) && (
<div className={css.albumOverview}>
<p className={css.overview}>{item.Overview}</p>
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/views/Details/ArtistScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {getImageUrl} from '../../utils/helpers';
import {isMdblistEnabled} from '../../services/mdblistApi';
import {DETAIL_ICON_PATHS} from './detailIcons';
import {SpottableDiv, HorizontalContainer} from './detailsSpottables';
import {hidesMediaDescription} from './detailsMedia';

import css from './Details.module.less';

Expand Down Expand Up @@ -39,7 +40,7 @@ const ArtistScreen = ({
<div className={css.personInfo}>
<h1 className={css.title}>{item.Name}</h1>
<RatingsRow item={item} serverUrl={serverUrl} pluginEnabled={isMdblistEnabled(settings)} />
{item.Overview && <p className={css.overview}>{item.Overview}</p>}
{item.Overview && !hidesMediaDescription(item, settings) && <p className={css.overview}>{item.Overview}</p>}
<HorizontalContainer className={css.actionButtons} spotlightId="details-action-buttons">
{artistAlbums.length > 0 && (
<SpottableDiv className={css.btnWrapper} onClick={onPlay} onFocus={onFocusRow} spotlightId="details-primary-btn">
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/views/Details/AudioTrackScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {isMdblistEnabled} from '../../services/mdblistApi';
import {DETAIL_ICON_PATHS} from './detailIcons';
import {SpottableDiv, HorizontalContainer} from './detailsSpottables';
import {PosterBadges} from './DetailBadges';
import {hidesMediaDescription} from './detailsMedia';

import css from './Details.module.less';

Expand All @@ -28,7 +29,7 @@ const AudioTrackScreen = ({item, serverUrl, settings, showsSection, posterUrl, y
</div>
<RatingsRow item={item} serverUrl={serverUrl} pluginEnabled={isMdblistEnabled(settings)} />
</div>
{item.Overview && <p className={css.overview}>{item.Overview}</p>}
{item.Overview && !hidesMediaDescription(item, settings) && <p className={css.overview}>{item.Overview}</p>}
</div>
{showsSection('poster') && (
<div className={css.posterSection}>
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/views/Details/Details.js
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,7 @@ const Details = ({itemId: itemIdProp, initialItem, onPlay, onSelectItem, onSelec
<PlaylistScreen
item={item}
serverUrl={effectiveServerUrl}
settings={settings}
showsSection={showsSection}
posterUrl={posterUrl}
genres={genres}
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/views/Details/PersonScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import usePersonSeerrCredits from '../../hooks/usePersonSeerrCredits';
import {getImageUrl} from '../../utils/helpers';
import {isMdblistEnabled} from '../../services/mdblistApi';
import {KEYS} from '../../utils/keys';
import {hidesMediaDescription} from './detailsMedia';

import css from './Details.module.less';

Expand Down Expand Up @@ -110,7 +111,7 @@ const PersonScreen = ({item, serverUrl, settings, showsSection, filmography, per
{birthPlace && <span className={css.infoItem}>{birthPlace}</span>}
</div>
<RatingsRow item={item} serverUrl={serverUrl} pluginEnabled={isMdblistEnabled(settings)} />
{showsSection('biography') && item.Overview && (
{showsSection('biography') && !hidesMediaDescription(item?.Type ? item : {...item, Type: 'Person'}, settings) && item.Overview && (
<SpottableDiv
className={css.overviewToggle}
onKeyDown={handleOverviewKeyDown}
Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/views/Details/PlaylistScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import {
SORT_DIR_ASC,
PLAYED_ALL
} from '../../utils/playlistSortFilter';
import {hidesMediaDescription} from './detailsMedia';

import css from './Details.module.less';

const PlaylistScreen = ({
item,
serverUrl,
settings,
showsSection,
posterUrl,
genres,
Expand Down Expand Up @@ -159,7 +161,7 @@ const PlaylistScreen = ({
{genres.length > 0 && (
<span className={css.seasonDetailCount}>{genres.join(', ')}</span>
)}
{item.Overview && <p className={css.overview}>{item.Overview}</p>}
{item.Overview && !hidesMediaDescription(item, settings) && <p className={css.overview}>{item.Overview}</p>}
</div>
</div>

Expand Down Expand Up @@ -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,
Expand Down
63 changes: 57 additions & 6 deletions packages/app/src/views/Details/detailsMedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading