From 952a83d282d402467679cb6db055161e44290bf1 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 23 Sep 2025 14:24:19 +0200 Subject: [PATCH 1/4] Set MaxParentalRating and MaxParentalSubRating when setting parental controls --- src/apps/dashboard/routes/users/parentalcontrol.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index c171719b0..ad46f1a11 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -1,4 +1,4 @@ -import type { AccessSchedule, ParentalRating, UserDto } from '@jellyfin/sdk/lib/generated-client'; +import type { AccessSchedule, ParentalRating, ParentalRatingScore, UserDto } from '@jellyfin/sdk/lib/generated-client'; import { UnratedItem } from '@jellyfin/sdk/lib/generated-client/models/unrated-item'; import { DynamicDayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/dynamic-day-of-week'; import escapeHTML from 'escape-html'; @@ -42,8 +42,11 @@ function handleSaveUser( throw new Error('Unexpected null user id or policy'); } - const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10); - userPolicy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating; + const parentalRating = page.querySelector('#selectMaxParentalRating') as ParentalRatingScore; + const score = parentalRating.score; + const subScore = parentalRating.subScore; + userPolicy.MaxParentalRating = Number.isNaN(score) ? null : score; + userPolicy.MaxParentalSubRating = Number.isNaN(subScore) ? null : subScore; userPolicy.BlockUnratedItems = Array.prototype.filter .call(page.querySelectorAll('.chkUnratedItem'), i => i.checked) .map(i => i.getAttribute('data-itemtype')); @@ -343,8 +346,8 @@ const UserParentalControl = () => { let content = ''; content += ''; for (const rating of parentalRatings) { - if (rating.Value != null) { - content += ``; + if (rating.RatingScore != null) { + content += ``; } } return content; From 2e4dde35f4a2169c686fec0ab708acb89cb9625d Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 23 Sep 2025 14:30:07 +0200 Subject: [PATCH 2/4] Fix grouping --- src/apps/dashboard/routes/users/parentalcontrol.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index ad46f1a11..3a889251b 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -86,7 +86,7 @@ const UserParentalControl = () => { if (ratings.length) { const lastRating = ratings[ratings.length - 1]; - if (lastRating.Value === rating.Value) { + if (lastRating.RatingScore?.score === rating.RatingScore?.score && lastRating.RatingScore?.subScore == rating.RatingScore?.subScore) { lastRating.Name += '/' + rating.Name; continue; } From fb58a759ac368cd21ae375eef13c027464ddb775 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 23 Sep 2025 15:10:05 +0200 Subject: [PATCH 3/4] Fix grouping again --- src/apps/dashboard/routes/users/parentalcontrol.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index 3a889251b..1707ed8d7 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -92,10 +92,7 @@ const UserParentalControl = () => { } } - ratings.push({ - Name: rating.Name, - Value: rating.Value - }); + ratings.push(rating); } setParentalRatings(ratings); From 3a95e751d00582c8c7344ab29031c0f29543e072 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Wed, 24 Sep 2025 01:17:58 +0200 Subject: [PATCH 4/4] Fixup --- .../routes/users/parentalcontrol.tsx | 93 +++++++++++-------- 1 file changed, 55 insertions(+), 38 deletions(-) diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index 1707ed8d7..df930ae17 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -1,4 +1,4 @@ -import type { AccessSchedule, ParentalRating, ParentalRatingScore, UserDto } from '@jellyfin/sdk/lib/generated-client'; +import type { AccessSchedule, ParentalRating, UserDto } from '@jellyfin/sdk/lib/generated-client'; import { UnratedItem } from '@jellyfin/sdk/lib/generated-client/models/unrated-item'; import { DynamicDayOfWeek } from '@jellyfin/sdk/lib/generated-client/models/dynamic-day-of-week'; import escapeHTML from 'escape-html'; @@ -30,6 +30,7 @@ type UnratedNamedItem = NamedItem & { function handleSaveUser( page: HTMLDivElement, + parentalRatingsRef: React.MutableRefObject, getSchedulesFromPage: () => AccessSchedule[], getAllowedTagsFromPage: () => string[], getBlockedTagsFromPage: () => string[], @@ -42,9 +43,10 @@ function handleSaveUser( throw new Error('Unexpected null user id or policy'); } - const parentalRating = page.querySelector('#selectMaxParentalRating') as ParentalRatingScore; - const score = parentalRating.score; - const subScore = parentalRating.subScore; + const parentalRatingIndex = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10); + const parentalRating = parentalRatingsRef.current[parentalRatingIndex] as ParentalRating; + const score = parentalRating?.RatingScore?.score; + const subScore = parentalRating?.RatingScore?.subScore; userPolicy.MaxParentalRating = Number.isNaN(score) ? null : score; userPolicy.MaxParentalSubRating = Number.isNaN(subScore) ? null : subScore; userPolicy.BlockUnratedItems = Array.prototype.filter @@ -75,28 +77,7 @@ const UserParentalControl = () => { const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []); const element = useRef(null); - - const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => { - let rating; - const ratings: ParentalRating[] = []; - - for (let i = 0, length = allParentalRatings.length; i < length; i++) { - rating = allParentalRatings[i]; - - if (ratings.length) { - const lastRating = ratings[ratings.length - 1]; - - if (lastRating.RatingScore?.score === rating.RatingScore?.score && lastRating.RatingScore?.subScore == rating.RatingScore?.subScore) { - lastRating.Name += '/' + rating.Name; - continue; - } - } - - ratings.push(rating); - } - - setParentalRatings(ratings); - }, []); + const parentalRatingsRef = useRef([]); const loadUnratedItems = useCallback((user: UserDto) => { const page = element.current; @@ -161,16 +142,52 @@ const UserParentalControl = () => { setAllowedTags(user.Policy?.AllowedTags || []); setBlockedTags(user.Policy?.BlockedTags || []); - populateRatings(allParentalRatings); - let ratingValue = ''; - allParentalRatings.forEach(rating => { - if (rating.Value != null && user.Policy?.MaxParentalRating != null && user.Policy.MaxParentalRating >= rating.Value) { - ratingValue = `${rating.Value}`; + // Build the grouped ratings array + const ratings: ParentalRating[] = []; + for (let i = 0, length = allParentalRatings.length; i < length; i++) { + const rating = allParentalRatings[i]; + + if (ratings.length) { + const lastRating = ratings[ratings.length - 1]; + + if (lastRating.RatingScore?.score === rating.RatingScore?.score && lastRating.RatingScore?.subScore == rating.RatingScore?.subScore) { + lastRating.Name += '/' + rating.Name; + continue; + } } - }); - setMaxParentalRating(ratingValue); + ratings.push(rating); + } + setParentalRatings(ratings); + parentalRatingsRef.current = ratings; + + // Find matching rating - first try exact match with score and subscore + let ratingIndex = ''; + const userMaxRating = user.Policy?.MaxParentalRating; + const userMaxSubRating = user.Policy?.MaxParentalSubRating; + + if (userMaxRating != null) { + // First try to find exact match with both score and subscore + ratings.forEach((rating, index) => { + if (rating.RatingScore?.score === userMaxRating + && rating.RatingScore?.subScore === userMaxSubRating) { + ratingIndex = `${index}`; + } + }); + + // If no exact match found, fallback to score-only match + if (!ratingIndex) { + ratings.forEach((rating, index) => { + if (rating.RatingScore?.score != null + && rating.RatingScore.score <= userMaxRating) { + ratingIndex = `${index}`; + } + }); + } + } + + setMaxParentalRating(ratingIndex); if (user.Policy?.IsAdministrator) { (page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide'); @@ -179,7 +196,7 @@ const UserParentalControl = () => { } setAccessSchedules(user.Policy?.AccessSchedules || []); loading.hide(); - }, [libraryMenu, setAllowedTags, setBlockedTags, loadUnratedItems, populateRatings]); + }, [libraryMenu, setAllowedTags, setBlockedTags, loadUnratedItems]); const loadData = useCallback(() => { if (!userId) { @@ -286,7 +303,7 @@ const UserParentalControl = () => { toast(globalize.translate('SettingsSaved')); }; - const saveUser = handleSaveUser(page, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete); + const saveUser = handleSaveUser(page, parentalRatingsRef, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete); const onSubmit = (e: Event) => { if (!userId) { @@ -342,11 +359,11 @@ const UserParentalControl = () => { const optionMaxParentalRating = () => { let content = ''; content += ''; - for (const rating of parentalRatings) { + parentalRatings.forEach((rating, index) => { if (rating.RatingScore != null) { - content += ``; + content += ``; } - } + }); return content; };