Skip to content

Commit 1bbfb34

Browse files
authored
Merge branch 'master' into inline-config-files
2 parents d6cfe08 + a238b5e commit 1bbfb34

14 files changed

Lines changed: 401 additions & 231 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/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)