2022-02-18 14:27:39 +03:00
|
|
|
import { AccessSchedule, UserDto } from '@thornbill/jellyfin-sdk/dist/generated-client';
|
2022-01-05 19:43:40 +03:00
|
|
|
import React, { FunctionComponent, useCallback, useEffect, useState, useRef } from 'react';
|
|
|
|
import globalize from '../../scripts/globalize';
|
|
|
|
import LibraryMenu from '../../scripts/libraryMenu';
|
|
|
|
import { appRouter } from '../appRouter';
|
|
|
|
import AccessScheduleList from '../dashboard/users/AccessScheduleList';
|
|
|
|
import BlockedTagList from '../dashboard/users/BlockedTagList';
|
|
|
|
import ButtonElement from '../dashboard/users/ButtonElement';
|
|
|
|
import CheckBoxListItem from '../dashboard/users/CheckBoxListItem';
|
|
|
|
import SectionTitleButtonElement from '../dashboard/users/SectionTitleButtonElement';
|
|
|
|
import SectionTitleLinkElement from '../dashboard/users/SectionTitleLinkElement';
|
|
|
|
import SelectMaxParentalRating from '../dashboard/users/SelectMaxParentalRating';
|
|
|
|
import SectionTabs from '../dashboard/users/SectionTabs';
|
|
|
|
import loading from '../loading/loading';
|
|
|
|
import toast from '../toast/toast';
|
|
|
|
|
2022-01-22 19:20:09 +03:00
|
|
|
type RatingsArr = {
|
2022-01-05 19:43:40 +03:00
|
|
|
Name: string;
|
2022-01-22 19:20:09 +03:00
|
|
|
Value: number;
|
2022-01-05 19:43:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type ItemsArr = {
|
|
|
|
name: string;
|
|
|
|
value: string;
|
|
|
|
checkedAttribute: string
|
|
|
|
}
|
|
|
|
|
|
|
|
const UserParentalControl: FunctionComponent = () => {
|
|
|
|
const [ userName, setUserName ] = useState('');
|
|
|
|
const [ parentalRatings, setParentalRatings ] = useState([]);
|
|
|
|
const [ unratedItems, setUnratedItems ] = useState([]);
|
|
|
|
const [ accessSchedules, setAccessSchedules ] = useState([]);
|
|
|
|
const [ blockedTags, setBlockedTags ] = useState([]);
|
|
|
|
|
2022-02-15 23:47:59 +03:00
|
|
|
const element = useRef<HTMLDivElement>(null);
|
2022-01-05 19:43:40 +03:00
|
|
|
|
|
|
|
const populateRatings = useCallback((allParentalRatings) => {
|
|
|
|
let rating;
|
2022-01-22 19:20:09 +03:00
|
|
|
const ratings: RatingsArr[] = [];
|
2022-01-05 19:43:40 +03:00
|
|
|
|
|
|
|
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
|
|
|
rating = allParentalRatings[i];
|
|
|
|
|
|
|
|
if (ratings.length) {
|
|
|
|
const lastRating = ratings[ratings.length - 1];
|
|
|
|
|
|
|
|
if (lastRating.Value === rating.Value) {
|
|
|
|
lastRating.Name += '/' + rating.Name;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ratings.push({
|
|
|
|
Name: rating.Name,
|
|
|
|
Value: rating.Value
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setParentalRatings(ratings);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const loadUnratedItems = useCallback((user) => {
|
2022-02-16 00:37:06 +03:00
|
|
|
const page = element.current;
|
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
console.error('Unexpected null reference');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:43:40 +03:00
|
|
|
const items = [{
|
|
|
|
name: globalize.translate('Books'),
|
|
|
|
value: 'Book'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('Channels'),
|
|
|
|
value: 'ChannelContent'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('LiveTV'),
|
|
|
|
value: 'LiveTvChannel'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('Movies'),
|
|
|
|
value: 'Movie'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('Music'),
|
|
|
|
value: 'Music'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('Trailers'),
|
|
|
|
value: 'Trailer'
|
|
|
|
}, {
|
|
|
|
name: globalize.translate('Shows'),
|
|
|
|
value: 'Series'
|
|
|
|
}];
|
|
|
|
|
|
|
|
const itemsArr: ItemsArr[] = [];
|
|
|
|
|
|
|
|
for (const item of items) {
|
|
|
|
const isChecked = user.Policy.BlockUnratedItems.indexOf(item.value) != -1;
|
|
|
|
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
|
|
|
itemsArr.push({
|
|
|
|
value: item.value,
|
|
|
|
name: item.name,
|
|
|
|
checkedAttribute: checkedAttribute
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setUnratedItems(itemsArr);
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
const blockUnratedItems = page.querySelector('.blockUnratedItems') as HTMLDivElement;
|
2022-01-05 19:43:40 +03:00
|
|
|
blockUnratedItems.dispatchEvent(new CustomEvent('create'));
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const loadBlockedTags = useCallback((tags) => {
|
2022-02-16 00:37:06 +03:00
|
|
|
const page = element.current;
|
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
console.error('Unexpected null reference');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:43:40 +03:00
|
|
|
setBlockedTags(tags);
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
const blockedTagsElem = page.querySelector('.blockedTags') as HTMLDivElement;
|
2022-01-05 19:43:40 +03:00
|
|
|
|
|
|
|
for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) {
|
|
|
|
btnDeleteTag.addEventListener('click', function () {
|
|
|
|
const tag = btnDeleteTag.getAttribute('data-tag');
|
2022-02-18 14:27:39 +03:00
|
|
|
const newTags = tags.filter(function (t: string) {
|
2022-01-05 19:43:40 +03:00
|
|
|
return t != tag;
|
|
|
|
});
|
|
|
|
loadBlockedTags(newTags);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const renderAccessSchedule = useCallback((schedules) => {
|
2022-02-16 00:37:06 +03:00
|
|
|
const page = element.current;
|
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
console.error('Unexpected null reference');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:43:40 +03:00
|
|
|
setAccessSchedules(schedules);
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
const accessScheduleList = page.querySelector('.accessScheduleList') as HTMLDivElement;
|
2022-01-05 19:43:40 +03:00
|
|
|
|
|
|
|
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
|
|
|
btnDelete.addEventListener('click', function () {
|
|
|
|
const index = parseInt(btnDelete.getAttribute('data-index'));
|
|
|
|
schedules.splice(index, 1);
|
2022-02-18 14:27:39 +03:00
|
|
|
const newindex = schedules.filter(function (i: number) {
|
2022-01-05 19:43:40 +03:00
|
|
|
return i != index;
|
|
|
|
});
|
|
|
|
renderAccessSchedule(newindex);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
const loadUser = useCallback((user, allParentalRatings) => {
|
2022-02-16 00:37:06 +03:00
|
|
|
const page = element.current;
|
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
console.error('Unexpected null reference');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:43:40 +03:00
|
|
|
setUserName(user.Name);
|
|
|
|
LibraryMenu.setTitle(user.Name);
|
|
|
|
loadUnratedItems(user);
|
|
|
|
|
|
|
|
loadBlockedTags(user.Policy.BlockedTags);
|
|
|
|
populateRatings(allParentalRatings);
|
|
|
|
let ratingValue = '';
|
|
|
|
|
|
|
|
if (user.Policy.MaxParentalRating) {
|
|
|
|
for (let i = 0, length = allParentalRatings.length; i < length; i++) {
|
|
|
|
const rating = allParentalRatings[i];
|
|
|
|
|
|
|
|
if (user.Policy.MaxParentalRating >= rating.Value) {
|
|
|
|
ratingValue = rating.Value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.selectMaxParentalRating') as HTMLInputElement).value = ratingValue;
|
2022-01-05 19:43:40 +03:00
|
|
|
|
|
|
|
if (user.Policy.IsAdministrator) {
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide');
|
2022-01-05 19:43:40 +03:00
|
|
|
} else {
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.remove('hide');
|
2022-01-05 19:43:40 +03:00
|
|
|
}
|
|
|
|
renderAccessSchedule(user.Policy.AccessSchedules || []);
|
|
|
|
loading.hide();
|
|
|
|
}, [loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]);
|
|
|
|
|
|
|
|
const loadData = useCallback(() => {
|
|
|
|
loading.show();
|
|
|
|
const userId = appRouter.param('userId');
|
|
|
|
const promise1 = window.ApiClient.getUser(userId);
|
|
|
|
const promise2 = window.ApiClient.getParentalRatings();
|
|
|
|
Promise.all([promise1, promise2]).then(function (responses) {
|
|
|
|
loadUser(responses[0], responses[1]);
|
|
|
|
});
|
|
|
|
}, [loadUser]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
2022-02-16 00:37:06 +03:00
|
|
|
const page = element.current;
|
|
|
|
|
|
|
|
if (!page) {
|
|
|
|
console.error('Unexpected null reference');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-05 19:43:40 +03:00
|
|
|
loadData();
|
|
|
|
|
|
|
|
const onSaveComplete = () => {
|
|
|
|
loading.hide();
|
|
|
|
toast(globalize.translate('SettingsSaved'));
|
|
|
|
};
|
|
|
|
|
2022-02-18 14:27:39 +03:00
|
|
|
const saveUser = (user: UserDto) => {
|
|
|
|
if (!user.Id) {
|
|
|
|
throw new Error('Unexpected null user.Id');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!user.Policy) {
|
|
|
|
throw new Error('Unexpected null user.Policy');
|
|
|
|
}
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
user.Policy.MaxParentalRating = (page.querySelector('.selectMaxParentalRating') as HTMLInputElement).value || null;
|
2022-02-16 00:37:06 +03:00
|
|
|
user.Policy.BlockUnratedItems = Array.prototype.filter.call(page.querySelectorAll('.chkUnratedItem'), function (i) {
|
2022-01-05 19:43:40 +03:00
|
|
|
return i.checked;
|
|
|
|
}).map(function (i) {
|
2022-01-22 19:20:09 +03:00
|
|
|
return i.getAttribute('data-itemtype');
|
2022-01-05 19:43:40 +03:00
|
|
|
});
|
|
|
|
user.Policy.AccessSchedules = getSchedulesFromPage();
|
|
|
|
user.Policy.BlockedTags = getBlockedTagsFromPage();
|
|
|
|
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
|
|
|
onSaveComplete();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-02-18 14:27:39 +03:00
|
|
|
const showSchedulePopup = (schedule: AccessSchedule, index: number) => {
|
2022-01-05 19:43:40 +03:00
|
|
|
schedule = schedule || {};
|
|
|
|
import('../../components/accessSchedule/accessSchedule').then(({default: accessschedule}) => {
|
|
|
|
accessschedule.show({
|
|
|
|
schedule: schedule
|
|
|
|
}).then(function (updatedSchedule) {
|
|
|
|
const schedules = getSchedulesFromPage();
|
|
|
|
|
|
|
|
if (index == -1) {
|
|
|
|
index = schedules.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
schedules[index] = updatedSchedule;
|
|
|
|
renderAccessSchedule(schedules);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getSchedulesFromPage = () => {
|
2022-02-16 00:37:06 +03:00
|
|
|
return Array.prototype.map.call(page.querySelectorAll('.liSchedule'), function (elem) {
|
2022-01-05 19:43:40 +03:00
|
|
|
return {
|
|
|
|
DayOfWeek: elem.getAttribute('data-day'),
|
|
|
|
StartHour: elem.getAttribute('data-start'),
|
|
|
|
EndHour: elem.getAttribute('data-end')
|
|
|
|
};
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getBlockedTagsFromPage = () => {
|
2022-02-16 00:37:06 +03:00
|
|
|
return Array.prototype.map.call(page.querySelectorAll('.blockedTag'), function (elem) {
|
2022-01-05 19:43:40 +03:00
|
|
|
return elem.getAttribute('data-tag');
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const showBlockedTagPopup = () => {
|
|
|
|
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
|
|
|
prompt({
|
|
|
|
label: globalize.translate('LabelTag')
|
|
|
|
}).then(function (value) {
|
|
|
|
const tags = getBlockedTagsFromPage();
|
|
|
|
|
|
|
|
if (tags.indexOf(value) == -1) {
|
|
|
|
tags.push(value);
|
|
|
|
loadBlockedTags(tags);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-02-18 14:27:39 +03:00
|
|
|
const onSubmit = (e: Event) => {
|
2022-01-05 19:43:40 +03:00
|
|
|
loading.show();
|
|
|
|
const userId = appRouter.param('userId');
|
|
|
|
window.ApiClient.getUser(userId).then(function (result) {
|
|
|
|
saveUser(result);
|
|
|
|
});
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.btnAddSchedule') as HTMLButtonElement).addEventListener('click', function () {
|
2022-01-05 19:43:40 +03:00
|
|
|
showSchedulePopup({}, -1);
|
|
|
|
});
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', function () {
|
2022-01-05 19:43:40 +03:00
|
|
|
showBlockedTagPopup();
|
|
|
|
});
|
|
|
|
|
2022-02-16 22:01:13 +03:00
|
|
|
(page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
2022-01-05 19:43:40 +03:00
|
|
|
}, [loadBlockedTags, loadData, renderAccessSchedule]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div ref={element}>
|
|
|
|
<div className='content-primary'>
|
|
|
|
<div className='verticalSection'>
|
|
|
|
<div className='sectionTitleContainer flex align-items-center'>
|
|
|
|
<h2 className='sectionTitle username'>
|
|
|
|
{userName}
|
|
|
|
</h2>
|
|
|
|
<SectionTitleLinkElement
|
|
|
|
className='raised button-alt headerHelpButton'
|
|
|
|
title='Help'
|
|
|
|
url='https://docs.jellyfin.org/general/server/users/'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<SectionTabs activeTab='userparentalcontrol'/>
|
|
|
|
<form className='userParentalControlForm'>
|
|
|
|
<div className='selectContainer'>
|
|
|
|
<SelectMaxParentalRating
|
|
|
|
className= 'selectMaxParentalRating'
|
|
|
|
label= 'LabelMaxParentalRating'
|
|
|
|
parentalRatings={parentalRatings}
|
|
|
|
/>
|
|
|
|
<div className='fieldDescription'>
|
|
|
|
{globalize.translate('MaxParentalRatingHelp')}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div className='blockUnratedItems'>
|
|
|
|
<h3 className='checkboxListLabel'>
|
|
|
|
{globalize.translate('HeaderBlockItemsWithNoRating')}
|
|
|
|
</h3>
|
|
|
|
<div className='checkboxList paperList' style={{ padding: '.5em 1em' }}>
|
|
|
|
{unratedItems.map(Item => {
|
|
|
|
return <CheckBoxListItem
|
|
|
|
key={Item.value}
|
|
|
|
className='chkUnratedItem'
|
2022-01-22 19:20:09 +03:00
|
|
|
ItemType={Item.value}
|
2022-01-05 19:43:40 +03:00
|
|
|
Name={Item.name}
|
|
|
|
checkedAttribute={Item.checkedAttribute}
|
|
|
|
/>;
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<br />
|
|
|
|
<div className='verticalSection' style={{marginBottom: '2em'}}>
|
|
|
|
<div
|
|
|
|
className='detailSectionHeader sectionTitleContainer'
|
|
|
|
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
|
|
|
>
|
|
|
|
<h2 className='sectionTitle'>
|
|
|
|
{globalize.translate('LabelBlockContentWithTags')}
|
|
|
|
</h2>
|
|
|
|
<SectionTitleButtonElement
|
|
|
|
className='fab btnAddBlockedTag submit'
|
|
|
|
title='Add'
|
|
|
|
icon='add'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className='blockedTags' style={{marginTop: '.5em'}}>
|
|
|
|
{blockedTags.map((tag, index) => {
|
|
|
|
return <BlockedTagList
|
|
|
|
key={index}
|
|
|
|
tag={tag}
|
|
|
|
/>;
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className='accessScheduleSection verticalSection' style={{marginBottom: '2em'}}>
|
|
|
|
<div
|
|
|
|
className='sectionTitleContainer'
|
|
|
|
style={{display: 'flex', alignItems: 'center', paddingBottom: '1em'}}
|
|
|
|
>
|
|
|
|
<h2 className='sectionTitle'>
|
|
|
|
{globalize.translate('HeaderAccessSchedule')}
|
|
|
|
</h2>
|
|
|
|
<SectionTitleButtonElement
|
|
|
|
className='fab btnAddSchedule submit'
|
|
|
|
title='Add'
|
|
|
|
icon='add'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<p>{globalize.translate('HeaderAccessScheduleHelp')}</p>
|
|
|
|
<div className='accessScheduleList paperList'>
|
|
|
|
{accessSchedules.map((accessSchedule, index) => {
|
|
|
|
return <AccessScheduleList
|
|
|
|
key={index}
|
|
|
|
index={index}
|
|
|
|
DayOfWeek={accessSchedule.DayOfWeek}
|
|
|
|
StartHour={accessSchedule.StartHour}
|
|
|
|
EndHour={accessSchedule.EndHour}
|
|
|
|
/>;
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<ButtonElement
|
|
|
|
type='submit'
|
|
|
|
className='raised button-submit block'
|
|
|
|
title='Save'
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default UserParentalControl;
|