Skip to content

Commit d817bbb

Browse files
authored
Merge branch 'master' into use-rest-parameters
2 parents 1d6dbca + 37aa7b8 commit d817bbb

24 files changed

Lines changed: 493 additions & 267 deletions

File tree

src/apps/dashboard/controllers/library.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ function getVirtualFolderHtml(page, virtualFolder, index) {
345345
html += '</div>';
346346
} else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) {
347347
html += "<div class='cardText cardText-secondary' dir='ltr' style='text-align:left;'>";
348-
html += virtualFolder.Locations[0];
348+
html += escapeHtml(virtualFolder.Locations[0]);
349349
html += '</div>';
350350
} else {
351351
html += "<div class='cardText cardText-secondary'>";

src/apps/dashboard/routes/users/parentalcontrol.tsx

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type UnratedNamedItem = NamedItem & {
3030

3131
function handleSaveUser(
3232
page: HTMLDivElement,
33+
parentalRatingsRef: React.MutableRefObject<ParentalRating[]>,
3334
getSchedulesFromPage: () => AccessSchedule[],
3435
getAllowedTagsFromPage: () => string[],
3536
getBlockedTagsFromPage: () => string[],
@@ -42,8 +43,12 @@ function handleSaveUser(
4243
throw new Error('Unexpected null user id or policy');
4344
}
4445

45-
const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10);
46-
userPolicy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating;
46+
const parentalRatingIndex = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10);
47+
const parentalRating = parentalRatingsRef.current[parentalRatingIndex] as ParentalRating;
48+
const score = parentalRating?.RatingScore?.score;
49+
const subScore = parentalRating?.RatingScore?.subScore;
50+
userPolicy.MaxParentalRating = Number.isNaN(score) ? null : score;
51+
userPolicy.MaxParentalSubRating = Number.isNaN(subScore) ? null : subScore;
4752
userPolicy.BlockUnratedItems = Array.prototype.filter
4853
.call(page.querySelectorAll('.chkUnratedItem'), i => i.checked)
4954
.map(i => i.getAttribute('data-itemtype'));
@@ -72,31 +77,7 @@ const UserParentalControl = () => {
7277
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
7378

7479
const element = useRef<HTMLDivElement>(null);
75-
76-
const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => {
77-
let rating;
78-
const ratings: ParentalRating[] = [];
79-
80-
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
81-
rating = allParentalRatings[i];
82-
83-
if (ratings.length) {
84-
const lastRating = ratings[ratings.length - 1];
85-
86-
if (lastRating.Value === rating.Value) {
87-
lastRating.Name += '/' + rating.Name;
88-
continue;
89-
}
90-
}
91-
92-
ratings.push({
93-
Name: rating.Name,
94-
Value: rating.Value
95-
});
96-
}
97-
98-
setParentalRatings(ratings);
99-
}, []);
80+
const parentalRatingsRef = useRef<ParentalRating[]>([]);
10081

10182
const loadUnratedItems = useCallback((user: UserDto) => {
10283
const page = element.current;
@@ -161,16 +142,52 @@ const UserParentalControl = () => {
161142

162143
setAllowedTags(user.Policy?.AllowedTags || []);
163144
setBlockedTags(user.Policy?.BlockedTags || []);
164-
populateRatings(allParentalRatings);
165145

166-
let ratingValue = '';
167-
allParentalRatings.forEach(rating => {
168-
if (rating.Value != null && user.Policy?.MaxParentalRating != null && user.Policy.MaxParentalRating >= rating.Value) {
169-
ratingValue = `${rating.Value}`;
146+
// Build the grouped ratings array
147+
const ratings: ParentalRating[] = [];
148+
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
149+
const rating = allParentalRatings[i];
150+
151+
if (ratings.length) {
152+
const lastRating = ratings[ratings.length - 1];
153+
154+
if (lastRating.RatingScore?.score === rating.RatingScore?.score && lastRating.RatingScore?.subScore == rating.RatingScore?.subScore) {
155+
lastRating.Name += '/' + rating.Name;
156+
continue;
157+
}
170158
}
171-
});
172159

173-
setMaxParentalRating(ratingValue);
160+
ratings.push(rating);
161+
}
162+
setParentalRatings(ratings);
163+
parentalRatingsRef.current = ratings;
164+
165+
// Find matching rating - first try exact match with score and subscore
166+
let ratingIndex = '';
167+
const userMaxRating = user.Policy?.MaxParentalRating;
168+
const userMaxSubRating = user.Policy?.MaxParentalSubRating;
169+
170+
if (userMaxRating != null) {
171+
// First try to find exact match with both score and subscore
172+
ratings.forEach((rating, index) => {
173+
if (rating.RatingScore?.score === userMaxRating
174+
&& rating.RatingScore?.subScore === userMaxSubRating) {
175+
ratingIndex = `${index}`;
176+
}
177+
});
178+
179+
// If no exact match found, fallback to score-only match
180+
if (!ratingIndex) {
181+
ratings.forEach((rating, index) => {
182+
if (rating.RatingScore?.score != null
183+
&& rating.RatingScore.score <= userMaxRating) {
184+
ratingIndex = `${index}`;
185+
}
186+
});
187+
}
188+
}
189+
190+
setMaxParentalRating(ratingIndex);
174191

175192
if (user.Policy?.IsAdministrator) {
176193
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide');
@@ -179,7 +196,7 @@ const UserParentalControl = () => {
179196
}
180197
setAccessSchedules(user.Policy?.AccessSchedules || []);
181198
loading.hide();
182-
}, [libraryMenu, setAllowedTags, setBlockedTags, loadUnratedItems, populateRatings]);
199+
}, [libraryMenu, setAllowedTags, setBlockedTags, loadUnratedItems]);
183200

184201
const loadData = useCallback(() => {
185202
if (!userId) {
@@ -286,7 +303,7 @@ const UserParentalControl = () => {
286303
toast(globalize.translate('SettingsSaved'));
287304
};
288305

289-
const saveUser = handleSaveUser(page, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete);
306+
const saveUser = handleSaveUser(page, parentalRatingsRef, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete);
290307

291308
const onSubmit = (e: Event) => {
292309
if (!userId) {
@@ -342,11 +359,11 @@ const UserParentalControl = () => {
342359
const optionMaxParentalRating = () => {
343360
let content = '';
344361
content += '<option value=\'\'></option>';
345-
for (const rating of parentalRatings) {
346-
if (rating.Value != null) {
347-
content += `<option value='${rating.Value}'>${escapeHTML(rating.Name)}</option>`;
362+
parentalRatings.forEach((rating, index) => {
363+
if (rating.RatingScore != null) {
364+
content += `<option value='${index}'>${escapeHTML(rating.Name)}</option>`;
348365
}
349-
}
366+
});
350367
return content;
351368
};
352369

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Options specifying if the player's native subtitle (cue) element should be used, a custom element (div), or allow
3+
* Jellyfin to choose automatically based on known browser support. Some browsers do not properly apply CSS styling to
4+
* the native subtitle element.
5+
*/
6+
export const SubtitleStylingOption = {
7+
Auto: 'Auto',
8+
Custom: 'Custom',
9+
Native: 'Native'
10+
} as const;
11+
12+
// eslint-disable-next-line @typescript-eslint/no-redeclare
13+
export type SubtitleStylingOption = typeof SubtitleStylingOption[keyof typeof SubtitleStylingOption];
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { SubtitleStylingOption } from 'apps/stable/features/playback/constants/subtitleStylingOption';
2+
import browser from 'scripts/browser';
3+
import type { UserSettings } from 'scripts/settings/userSettings';
4+
5+
// TODO: This type override should be removed when userSettings are properly typed
6+
interface SubtitleAppearanceSettings {
7+
subtitleStyling: SubtitleStylingOption
8+
}
9+
10+
export function useCustomSubtitles(userSettings: UserSettings) {
11+
const subtitleAppearance = userSettings.getSubtitleAppearanceSettings() as SubtitleAppearanceSettings;
12+
switch (subtitleAppearance.subtitleStyling) {
13+
case SubtitleStylingOption.Native:
14+
return false;
15+
case SubtitleStylingOption.Custom:
16+
return true;
17+
default:
18+
// after a system update, ps4 isn't showing anything when creating a track element dynamically
19+
// going to have to do it ourselves
20+
if (browser.ps4) {
21+
return true;
22+
}
23+
24+
// Tizen 5 doesn't support displaying secondary subtitles
25+
if ((browser.tizenVersion && browser.tizenVersion >= 5) || browser.web0s) {
26+
return true;
27+
}
28+
29+
if (browser.edge) {
30+
return true;
31+
}
32+
33+
// font-size styling does not seem to work natively in firefox. Switching to custom subtitles element for firefox.
34+
if (browser.firefox) {
35+
return true;
36+
}
37+
38+
// iOS/macOS global caption settings are causing huge font-size and margins
39+
if (browser.safari) return true;
40+
41+
return false;
42+
}
43+
}

src/apps/wizard/controllers/library.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ function getVirtualFolderHtml(page, virtualFolder, index) {
354354
html += '</div>';
355355
} else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) {
356356
html += "<div class='cardText cardText-secondary' dir='ltr' style='text-align:left;'>";
357-
html += virtualFolder.Locations[0];
357+
html += escapeHtml(virtualFolder.Locations[0]);
358358
html += '</div>';
359359
} else {
360360
html += "<div class='cardText cardText-secondary'>";

src/components/metadataEditor/metadataEditor.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -589,17 +589,17 @@ function setFieldVisibilities(context, item) {
589589
hideElement('#fld3dFormat', context);
590590
}
591591

592-
if (item.Type === 'Audio') {
592+
if (item.Type === BaseItemKind.Audio || item.Type === BaseItemKind.MusicAlbum || item.Type === BaseItemKind.MusicVideo) {
593+
showElement('#fldArtist', context);
593594
showElement('#fldAlbumArtist', context);
594595
} else {
596+
hideElement('#fldArtist', context);
595597
hideElement('#fldAlbumArtist', context);
596598
}
597599

598-
if (item.Type === 'Audio' || item.Type === 'MusicVideo') {
599-
showElement('#fldArtist', context);
600+
if (item.Type === BaseItemKind.Audio || item.Type === BaseItemKind.MusicVideo) {
600601
showElement('#fldAlbum', context);
601602
} else {
602-
hideElement('#fldArtist', context);
603603
hideElement('#fldAlbum', context);
604604
}
605605

src/controllers/favorites.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
2+
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
3+
14
import cardBuilder from 'components/cardbuilder/cardBuilder';
25
import focusManager from 'components/focusManager';
36
import layoutManager from 'components/layoutManager';
@@ -6,7 +9,6 @@ import dom from 'utils/dom';
69
import globalize from 'lib/globalize';
710
import { ServerConnections } from 'lib/jellyfin-apiclient';
811
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
9-
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
1012

1113
import 'elements/emby-itemscontainer/emby-itemscontainer';
1214
import 'elements/emby-scroller/emby-scroller';
@@ -34,6 +36,15 @@ function getSections() {
3436
overlayPlayButton: true,
3537
overlayText: false,
3638
centerText: true
39+
}, {
40+
name: 'HeaderSeasons',
41+
types: BaseItemKind.Season,
42+
shape: getPortraitShape(enableScrollX()),
43+
showTitle: true,
44+
showParentTitle: true,
45+
overlayPlayButton: true,
46+
overlayText: false,
47+
centerText: true
3748
}, {
3849
name: 'Episodes',
3950
types: 'Episode',

src/elements/emby-scroller/Scroller.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,6 @@ const Scroller: FC<PropsWithChildren<ScrollerProps>> = ({
178178
allowNativeScroll: !enableScrollButtons,
179179
forceHideScrollbars: enableScrollButtons,
180180
// In edge, with the native scroll, the content jumps around when hovering over the buttons
181-
// @ts-expect-error browser doesn't explicitly declare browser.edge, so fails type checking
182181
requireAnimation: enableScrollButtons && browser.edge
183182
};
184183

0 commit comments

Comments
 (0)