mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Fix tags and schedule edit for parental control
This commit is contained in:
parent
568b5f607f
commit
956f9bf7e4
4 changed files with 69 additions and 80 deletions
|
@ -70,6 +70,13 @@ const UserParentalControl = () => {
|
|||
const [ blockedTags, setBlockedTags ] = useState<string[]>([]);
|
||||
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
||||
|
||||
// The following are meant to be reset on each render.
|
||||
// These are to prevent multiple callbacks to be added to a single element in one render as useEffect may be executed multiple times in each render.
|
||||
let allowedTagsPopupCallback: (() => void) | null = null;
|
||||
let blockedTagsPopupCallback: (() => void) | null = null;
|
||||
let accessSchedulesPopupCallback: (() => void) | null = null;
|
||||
let formSubmissionCallback: ((e: Event) => void) | null = null;
|
||||
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => {
|
||||
|
@ -146,48 +153,6 @@ const UserParentalControl = () => {
|
|||
blockUnratedItems.dispatchEvent(new CustomEvent('create'));
|
||||
}, []);
|
||||
|
||||
const loadAllowedTags = useCallback((tags: string[]) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('[userparentalcontrol] Unexpected null page 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(t => t !== tag);
|
||||
loadAllowedTags(newTags);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadBlockedTags = useCallback((tags: string[]) => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('[userparentalcontrol] Unexpected null page reference');
|
||||
return;
|
||||
}
|
||||
|
||||
setBlockedTags(tags);
|
||||
|
||||
const blockedTagsElem = page.querySelector('.blockedTags') as HTMLDivElement;
|
||||
|
||||
for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) {
|
||||
btnDeleteTag.addEventListener('click', function () {
|
||||
const tag = btnDeleteTag.getAttribute('data-tag');
|
||||
const newTags = tags.filter(t => t !== tag);
|
||||
loadBlockedTags(newTags);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadUser = useCallback((user: UserDto, allParentalRatings: ParentalRating[]) => {
|
||||
const page = element.current;
|
||||
|
||||
|
@ -200,8 +165,8 @@ const UserParentalControl = () => {
|
|||
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
||||
loadUnratedItems(user);
|
||||
|
||||
loadAllowedTags(user.Policy?.AllowedTags || []);
|
||||
loadBlockedTags(user.Policy?.BlockedTags || []);
|
||||
setAllowedTags(user.Policy?.AllowedTags || []);
|
||||
setBlockedTags(user.Policy?.BlockedTags || []);
|
||||
populateRatings(allParentalRatings);
|
||||
|
||||
let ratingValue = '';
|
||||
|
@ -222,7 +187,7 @@ const UserParentalControl = () => {
|
|||
}
|
||||
setAccessSchedules(user.Policy?.AccessSchedules || []);
|
||||
loading.hide();
|
||||
}, [loadAllowedTags, loadBlockedTags, loadUnratedItems, populateRatings]);
|
||||
}, [setAllowedTags, setBlockedTags, loadUnratedItems, populateRatings]);
|
||||
|
||||
const loadData = useCallback(() => {
|
||||
if (!userId) {
|
||||
|
@ -296,7 +261,7 @@ const UserParentalControl = () => {
|
|||
|
||||
if (tags.indexOf(value) == -1) {
|
||||
tags.push(value);
|
||||
loadAllowedTags(tags);
|
||||
setAllowedTags(tags);
|
||||
}
|
||||
}).catch(() => {
|
||||
// prompt closed
|
||||
|
@ -317,7 +282,7 @@ const UserParentalControl = () => {
|
|||
|
||||
if (tags.indexOf(value) == -1) {
|
||||
tags.push(value);
|
||||
loadBlockedTags(tags);
|
||||
setBlockedTags(tags);
|
||||
}
|
||||
}).catch(() => {
|
||||
// prompt closed
|
||||
|
@ -348,7 +313,11 @@ const UserParentalControl = () => {
|
|||
return false;
|
||||
};
|
||||
|
||||
(page.querySelector('#btnAddSchedule') as HTMLButtonElement).addEventListener('click', function () {
|
||||
// FIXME: The following is still hacky and should migrate to pure react implementation for callbacks in the future
|
||||
if (accessSchedulesPopupCallback) {
|
||||
(page.querySelector('#btnAddSchedule') as HTMLButtonElement).removeEventListener('click', accessSchedulesPopupCallback);
|
||||
}
|
||||
accessSchedulesPopupCallback = function () {
|
||||
showSchedulePopup({
|
||||
Id: 0,
|
||||
UserId: '',
|
||||
|
@ -356,37 +325,27 @@ const UserParentalControl = () => {
|
|||
StartHour: 0,
|
||||
EndHour: 0
|
||||
}, -1);
|
||||
});
|
||||
};
|
||||
(page.querySelector('#btnAddSchedule') as HTMLButtonElement).addEventListener('click', accessSchedulesPopupCallback);
|
||||
|
||||
(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);
|
||||
}, [loadAllowedTags, loadBlockedTags, loadData, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
const page = element.current;
|
||||
|
||||
if (!page) {
|
||||
console.error('[userparentalcontrol] Unexpected null page reference');
|
||||
return;
|
||||
if (allowedTagsPopupCallback) {
|
||||
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).removeEventListener('click', allowedTagsPopupCallback);
|
||||
}
|
||||
allowedTagsPopupCallback = showAllowedTagPopup;
|
||||
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).addEventListener('click', allowedTagsPopupCallback);
|
||||
|
||||
const accessScheduleList = page.querySelector('.accessScheduleList') as HTMLDivElement;
|
||||
|
||||
for (const btnDelete of accessScheduleList.querySelectorAll('.btnDelete')) {
|
||||
btnDelete.addEventListener('click', function () {
|
||||
const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10);
|
||||
const newindex = accessSchedules.filter((_e, i) => i != index);
|
||||
setAccessSchedules(newindex);
|
||||
});
|
||||
if (blockedTagsPopupCallback) {
|
||||
(page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).removeEventListener('click', blockedTagsPopupCallback);
|
||||
}
|
||||
}, [accessSchedules]);
|
||||
blockedTagsPopupCallback = showBlockedTagPopup;
|
||||
(page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', blockedTagsPopupCallback);
|
||||
|
||||
if (formSubmissionCallback) {
|
||||
(page.querySelector('.userParentalControlForm') as HTMLFormElement).removeEventListener('submit', formSubmissionCallback);
|
||||
}
|
||||
formSubmissionCallback = onSubmit;
|
||||
(page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', formSubmissionCallback);
|
||||
}, [setAllowedTags, setBlockedTags, loadData, userId]);
|
||||
|
||||
const optionMaxParentalRating = () => {
|
||||
let content = '';
|
||||
|
@ -397,6 +356,21 @@ const UserParentalControl = () => {
|
|||
return content;
|
||||
};
|
||||
|
||||
const removeAllowedTagsCallback = useCallback((tag: string) => {
|
||||
const newTags = allowedTags.filter(t => t !== tag);
|
||||
setAllowedTags(newTags);
|
||||
}, [allowedTags, setAllowedTags]);
|
||||
|
||||
const removeBlockedTagsTagsCallback = useCallback((tag: string) => {
|
||||
const newTags = blockedTags.filter(t => t !== tag);
|
||||
setBlockedTags(newTags);
|
||||
}, [blockedTags, setBlockedTags]);
|
||||
|
||||
const removeScheduleCallback = useCallback((index: number) => {
|
||||
const newSchedules = accessSchedules.filter((_e, i) => i != index);
|
||||
setAccessSchedules(newSchedules);
|
||||
}, [accessSchedules, setAccessSchedules]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
id='userParentalControlPage'
|
||||
|
@ -461,6 +435,7 @@ const UserParentalControl = () => {
|
|||
key={tag}
|
||||
tag={tag}
|
||||
tagType='allowedTag'
|
||||
removeTagCallback={removeAllowedTagsCallback}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
|
@ -485,6 +460,7 @@ const UserParentalControl = () => {
|
|||
key={tag}
|
||||
tag={tag}
|
||||
tagType='blockedTag'
|
||||
removeTagCallback={removeBlockedTagsTagsCallback}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
|
@ -508,6 +484,7 @@ const UserParentalControl = () => {
|
|||
DayOfWeek={accessSchedule.DayOfWeek}
|
||||
StartHour={accessSchedule.StartHour}
|
||||
EndHour={accessSchedule.EndHour}
|
||||
removeScheduleCallback={removeScheduleCallback}
|
||||
/>;
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import React, { FunctionComponent, useCallback } from 'react';
|
||||
import datetime from '../../../scripts/datetime';
|
||||
import globalize from '../../../lib/globalize';
|
||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||
|
@ -8,6 +8,7 @@ type AccessScheduleListProps = {
|
|||
DayOfWeek?: string;
|
||||
StartHour?: number ;
|
||||
EndHour?: number;
|
||||
removeScheduleCallback?: (index: number) => void;
|
||||
};
|
||||
|
||||
function getDisplayTime(hours = 0) {
|
||||
|
@ -21,7 +22,10 @@ function getDisplayTime(hours = 0) {
|
|||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
||||
}
|
||||
|
||||
const AccessScheduleList: FunctionComponent<AccessScheduleListProps> = ({ index, DayOfWeek, StartHour, EndHour }: AccessScheduleListProps) => {
|
||||
const AccessScheduleList: FunctionComponent<AccessScheduleListProps> = ({ index, DayOfWeek, StartHour, EndHour, removeScheduleCallback }: AccessScheduleListProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
index !== undefined && removeScheduleCallback !== undefined && removeScheduleCallback(index);
|
||||
}, [index, removeScheduleCallback]);
|
||||
return (
|
||||
<div
|
||||
className='liSchedule listItem'
|
||||
|
@ -43,6 +47,7 @@ const AccessScheduleList: FunctionComponent<AccessScheduleListProps> = ({ index,
|
|||
title='Delete'
|
||||
icon='delete'
|
||||
dataIndex={index}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import React, { FunctionComponent } from 'react';
|
||||
import React, { FunctionComponent, useCallback } from 'react';
|
||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||
|
||||
type IProps = {
|
||||
tag?: string,
|
||||
tagType?: string;
|
||||
removeTagCallback?: (tag: string) => void;
|
||||
};
|
||||
|
||||
const TagList: FunctionComponent<IProps> = ({ tag, tagType }: IProps) => {
|
||||
const TagList: FunctionComponent<IProps> = ({ tag, tagType, removeTagCallback }: IProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
tag !== undefined && removeTagCallback !== undefined && removeTagCallback(tag);
|
||||
}, [tag, removeTagCallback]);
|
||||
return (
|
||||
<div className='paperList'>
|
||||
<div className='listItem'>
|
||||
|
@ -21,6 +25,7 @@ const TagList: FunctionComponent<IProps> = ({ tag, tagType }: IProps) => {
|
|||
title='Delete'
|
||||
icon='delete'
|
||||
dataTag={tag}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,7 @@ type IProps = {
|
|||
dataIndex?: string | number;
|
||||
dataTag?: string | number;
|
||||
dataProfileid?: string | number;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
const createIconButtonElement = ({ is, id, className, title, icon, dataIndex, dataTag, dataProfileid }: IProps) => ({
|
||||
|
@ -28,7 +29,7 @@ const createIconButtonElement = ({ is, id, className, title, icon, dataIndex, da
|
|||
</button>`
|
||||
});
|
||||
|
||||
const IconButtonElement: FunctionComponent<IProps> = ({ is, id, className, title, icon, dataIndex, dataTag, dataProfileid }: IProps) => {
|
||||
const IconButtonElement: FunctionComponent<IProps> = ({ is, id, className, title, icon, dataIndex, dataTag, dataProfileid, onClick }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
dangerouslySetInnerHTML={createIconButtonElement({
|
||||
|
@ -41,6 +42,7 @@ const IconButtonElement: FunctionComponent<IProps> = ({ is, id, className, title
|
|||
dataTag: dataTag ? `data-tag="${dataTag}"` : '',
|
||||
dataProfileid: dataProfileid ? `data-profileid="${dataProfileid}"` : ''
|
||||
})}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue