diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b2c6b246e9..a758d06f42 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -83,6 +83,8 @@ - [Chris-Codes-It](https://github.com/Chris-Codes-It) - [Vedant](https://github.com/viktory36) - [GeorgeH005](https://github.com/GeorgeH005) +- [JPUC1143](https://github.com/Jpuc1143) +- [David Angel](https://github.com/davidangel) ## Emby Contributors diff --git a/src/apps/dashboard/routes/users/parentalcontrol.tsx b/src/apps/dashboard/routes/users/parentalcontrol.tsx index 52a94df6c8..c5ce145fd1 100644 --- a/src/apps/dashboard/routes/users/parentalcontrol.tsx +++ b/src/apps/dashboard/routes/users/parentalcontrol.tsx @@ -6,7 +6,7 @@ import escapeHTML from 'escape-html'; import globalize from '../../../../scripts/globalize'; import LibraryMenu from '../../../../scripts/libraryMenu'; import AccessScheduleList from '../../../../components/dashboard/users/AccessScheduleList'; -import BlockedTagList from '../../../../components/dashboard/users/BlockedTagList'; +import TagList from '../../../../components/dashboard/users/TagList'; import ButtonElement from '../../../../elements/ButtonElement'; import SectionTitleContainer from '../../../../elements/SectionTitleContainer'; import SectionTabs from '../../../../components/dashboard/users/SectionTabs'; @@ -16,6 +16,7 @@ import { getParameterByName } from '../../../../utils/url'; import CheckBoxElement from '../../../../elements/CheckBoxElement'; import SelectElement from '../../../../elements/SelectElement'; import Page from '../../../../components/Page'; +import prompt from '../../../../components/prompt/prompt'; type UnratedItem = { name: string; @@ -28,6 +29,7 @@ const UserParentalControl: FunctionComponent = () => { const [ parentalRatings, setParentalRatings ] = useState([]); const [ unratedItems, setUnratedItems ] = useState([]); const [ accessSchedules, setAccessSchedules ] = useState([]); + const [ allowedTags, setAllowedTags ] = useState([]); const [ blockedTags, setBlockedTags ] = useState([]); const element = useRef(null); @@ -106,6 +108,29 @@ const UserParentalControl: FunctionComponent = () => { blockUnratedItems.dispatchEvent(new CustomEvent('create')); }, []); + const loadAllowedTags = useCallback((tags) => { + const page = element.current; + + if (!page) { + console.error('Unexpected null reference'); + return; + } + + setAllowedTags(tags); + + const allowedTagsElem = page.querySelector('.allowedTags') as HTMLDivElement; + + for (const btnDeleteTag of allowedTagsElem.querySelectorAll('.btnDeleteTag')) { + btnDeleteTag.addEventListener('click', function () { + const tag = btnDeleteTag.getAttribute('data-tag'); + const newTags = tags.filter(function (t: string) { + return t != tag; + }); + loadAllowedTags(newTags); + }); + } + }, []); + const loadBlockedTags = useCallback((tags) => { const page = element.current; @@ -165,6 +190,7 @@ const UserParentalControl: FunctionComponent = () => { LibraryMenu.setTitle(user.Name); loadUnratedItems(user); + loadAllowedTags(user.Policy.AllowedTags); loadBlockedTags(user.Policy.BlockedTags); populateRatings(allParentalRatings); let ratingValue = ''; @@ -188,7 +214,7 @@ const UserParentalControl: FunctionComponent = () => { } renderAccessSchedule(user.Policy.AccessSchedules || []); loading.hide(); - }, [loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]); + }, [loadAllowedTags, loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]); const loadData = useCallback(() => { loading.show(); @@ -212,32 +238,6 @@ const UserParentalControl: FunctionComponent = () => { loadData(); - const onSaveComplete = () => { - loading.hide(); - toast(globalize.translate('SettingsSaved')); - }; - - const saveUser = (user: UserDto) => { - if (!user.Id || !user.Policy) { - throw new Error('Unexpected null user id or policy'); - } - - const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10); - user.Policy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating; - user.Policy.BlockUnratedItems = Array.prototype.filter.call(page.querySelectorAll('.chkUnratedItem'), function (i) { - return i.checked; - }).map(function (i) { - return i.getAttribute('data-itemtype'); - }); - user.Policy.AccessSchedules = getSchedulesFromPage(); - user.Policy.BlockedTags = getBlockedTagsFromPage(); - window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { - onSaveComplete(); - }).catch(err => { - console.error('[userparentalcontrol] failed to update user policy', err); - }); - }; - const showSchedulePopup = (schedule: AccessSchedule, index: number) => { schedule = schedule || {}; import('../../../../components/accessSchedule/accessSchedule').then(({ default: accessschedule }) => { @@ -270,6 +270,27 @@ const UserParentalControl: FunctionComponent = () => { }) as AccessSchedule[]; }; + const getAllowedTagsFromPage = () => { + return Array.prototype.map.call(page.querySelectorAll('.allowedTag'), function (elem) { + return elem.getAttribute('data-tag'); + }) as string[]; + }; + + const showAllowedTagPopup = () => { + prompt({ + label: globalize.translate('LabelTag') + }).then(function (value) { + const tags = getAllowedTagsFromPage(); + + if (tags.indexOf(value) == -1) { + tags.push(value); + loadAllowedTags(tags); + } + }).catch(() => { + // prompt closed + }); + }; + const getBlockedTagsFromPage = () => { return Array.prototype.map.call(page.querySelectorAll('.blockedTag'), function (elem) { return elem.getAttribute('data-tag'); @@ -277,24 +298,27 @@ const UserParentalControl: FunctionComponent = () => { }; const showBlockedTagPopup = () => { - import('../../../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - label: globalize.translate('LabelTag') - }).then(function (value) { - const tags = getBlockedTagsFromPage(); + prompt({ + label: globalize.translate('LabelTag') + }).then(function (value) { + const tags = getBlockedTagsFromPage(); - if (tags.indexOf(value) == -1) { - tags.push(value); - loadBlockedTags(tags); - } - }).catch(() => { - // prompt closed - }); - }).catch(err => { - console.error('[userparentalcontrol] failed to load prompt', err); + if (tags.indexOf(value) == -1) { + tags.push(value); + loadBlockedTags(tags); + } + }).catch(() => { + // prompt closed }); }; + const onSaveComplete = () => { + loading.hide(); + toast(globalize.translate('SettingsSaved')); + }; + + const saveUser = handleSaveUser(page, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete); + const onSubmit = (e: Event) => { loading.show(); const userId = getParameterByName('userId'); @@ -318,12 +342,16 @@ const UserParentalControl: FunctionComponent = () => { }, -1); }); + (page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).addEventListener('click', function () { + showAllowedTagPopup(); + }); + (page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', function () { showBlockedTagPopup(); }); (page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', onSubmit); - }, [loadBlockedTags, loadData, renderAccessSchedule]); + }, [loadAllowedTags, loadBlockedTags, loadData, renderAccessSchedule]); const optionMaxParentalRating = () => { let content = ''; @@ -378,6 +406,27 @@ const UserParentalControl: FunctionComponent = () => {
+
+ +
+ {allowedTags?.map(tag => { + return ; + })} +
+
{ />
{blockedTags.map(tag => { - return ; })}
@@ -435,4 +485,28 @@ const UserParentalControl: FunctionComponent = () => { ); }; +function handleSaveUser(page: HTMLDivElement, getSchedulesFromPage: () => AccessSchedule[], getAllowedTagsFromPage: () => string[], getBlockedTagsFromPage: () => string[], onSaveComplete: () => void) { + return (user: UserDto) => { + if (!user.Id || !user.Policy) { + throw new Error('Unexpected null user id or policy'); + } + + const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10); + user.Policy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating; + user.Policy.BlockUnratedItems = Array.prototype.filter.call(page.querySelectorAll('.chkUnratedItem'), function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-itemtype'); + }); + user.Policy.AccessSchedules = getSchedulesFromPage(); + user.Policy.AllowedTags = getAllowedTagsFromPage(); + user.Policy.BlockedTags = getBlockedTagsFromPage(); + window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + onSaveComplete(); + }).catch(err => { + console.error('[userparentalcontrol] failed to update user policy', err); + }); + }; +} + export default UserParentalControl; diff --git a/src/components/dashboard/users/BlockedTagList.tsx b/src/components/dashboard/users/TagList.tsx similarity index 75% rename from src/components/dashboard/users/BlockedTagList.tsx rename to src/components/dashboard/users/TagList.tsx index 5158a63e97..531ee2f6e6 100644 --- a/src/components/dashboard/users/BlockedTagList.tsx +++ b/src/components/dashboard/users/TagList.tsx @@ -2,10 +2,11 @@ import React, { FunctionComponent } from 'react'; import IconButtonElement from '../../../elements/IconButtonElement'; type IProps = { - tag?: string; + tag?: string, + tagType?: string; }; -const BlockedTagList: FunctionComponent = ({ tag }: IProps) => { +const TagList: FunctionComponent = ({ tag, tagType }: IProps) => { return (
@@ -16,7 +17,7 @@ const BlockedTagList: FunctionComponent = ({ tag }: IProps) => {
= ({ tag }: IProps) => { ); }; -export default BlockedTagList; +export default TagList; diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 01237cb45d..1d48742604 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -528,6 +528,7 @@ "LabelAlbum": "Album", "LabelAlbumArtists": "Album artists", "LabelAlbumGain": "Album Gain", + "LabelAllowContentWithTags": "Allow items with tags", "LabelAllowedRemoteAddresses": "Remote IP address filter", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode", "LabelAllowHWTranscoding": "Allow hardware transcoding",