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 [ blockedTags, setBlockedTags ] = useState<string[]>([]);
|
||||||
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
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 element = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => {
|
const populateRatings = useCallback((allParentalRatings: ParentalRating[]) => {
|
||||||
|
@ -146,48 +153,6 @@ const UserParentalControl = () => {
|
||||||
blockUnratedItems.dispatchEvent(new CustomEvent('create'));
|
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 loadUser = useCallback((user: UserDto, allParentalRatings: ParentalRating[]) => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
|
|
||||||
|
@ -200,8 +165,8 @@ const UserParentalControl = () => {
|
||||||
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
||||||
loadUnratedItems(user);
|
loadUnratedItems(user);
|
||||||
|
|
||||||
loadAllowedTags(user.Policy?.AllowedTags || []);
|
setAllowedTags(user.Policy?.AllowedTags || []);
|
||||||
loadBlockedTags(user.Policy?.BlockedTags || []);
|
setBlockedTags(user.Policy?.BlockedTags || []);
|
||||||
populateRatings(allParentalRatings);
|
populateRatings(allParentalRatings);
|
||||||
|
|
||||||
let ratingValue = '';
|
let ratingValue = '';
|
||||||
|
@ -222,7 +187,7 @@ const UserParentalControl = () => {
|
||||||
}
|
}
|
||||||
setAccessSchedules(user.Policy?.AccessSchedules || []);
|
setAccessSchedules(user.Policy?.AccessSchedules || []);
|
||||||
loading.hide();
|
loading.hide();
|
||||||
}, [loadAllowedTags, loadBlockedTags, loadUnratedItems, populateRatings]);
|
}, [setAllowedTags, setBlockedTags, loadUnratedItems, populateRatings]);
|
||||||
|
|
||||||
const loadData = useCallback(() => {
|
const loadData = useCallback(() => {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
@ -296,7 +261,7 @@ const UserParentalControl = () => {
|
||||||
|
|
||||||
if (tags.indexOf(value) == -1) {
|
if (tags.indexOf(value) == -1) {
|
||||||
tags.push(value);
|
tags.push(value);
|
||||||
loadAllowedTags(tags);
|
setAllowedTags(tags);
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// prompt closed
|
// prompt closed
|
||||||
|
@ -317,7 +282,7 @@ const UserParentalControl = () => {
|
||||||
|
|
||||||
if (tags.indexOf(value) == -1) {
|
if (tags.indexOf(value) == -1) {
|
||||||
tags.push(value);
|
tags.push(value);
|
||||||
loadBlockedTags(tags);
|
setBlockedTags(tags);
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// prompt closed
|
// prompt closed
|
||||||
|
@ -348,7 +313,11 @@ const UserParentalControl = () => {
|
||||||
return false;
|
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({
|
showSchedulePopup({
|
||||||
Id: 0,
|
Id: 0,
|
||||||
UserId: '',
|
UserId: '',
|
||||||
|
@ -356,37 +325,27 @@ const UserParentalControl = () => {
|
||||||
StartHour: 0,
|
StartHour: 0,
|
||||||
EndHour: 0
|
EndHour: 0
|
||||||
}, -1);
|
}, -1);
|
||||||
});
|
};
|
||||||
|
(page.querySelector('#btnAddSchedule') as HTMLButtonElement).addEventListener('click', accessSchedulesPopupCallback);
|
||||||
|
|
||||||
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).addEventListener('click', function () {
|
if (allowedTagsPopupCallback) {
|
||||||
showAllowedTagPopup();
|
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).removeEventListener('click', allowedTagsPopupCallback);
|
||||||
});
|
|
||||||
|
|
||||||
(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;
|
|
||||||
}
|
}
|
||||||
|
allowedTagsPopupCallback = showAllowedTagPopup;
|
||||||
|
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).addEventListener('click', allowedTagsPopupCallback);
|
||||||
|
|
||||||
const accessScheduleList = page.querySelector('.accessScheduleList') as HTMLDivElement;
|
if (blockedTagsPopupCallback) {
|
||||||
|
(page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).removeEventListener('click', blockedTagsPopupCallback);
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [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 = () => {
|
const optionMaxParentalRating = () => {
|
||||||
let content = '';
|
let content = '';
|
||||||
|
@ -397,6 +356,21 @@ const UserParentalControl = () => {
|
||||||
return content;
|
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 (
|
return (
|
||||||
<Page
|
<Page
|
||||||
id='userParentalControlPage'
|
id='userParentalControlPage'
|
||||||
|
@ -461,6 +435,7 @@ const UserParentalControl = () => {
|
||||||
key={tag}
|
key={tag}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
tagType='allowedTag'
|
tagType='allowedTag'
|
||||||
|
removeTagCallback={removeAllowedTagsCallback}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -485,6 +460,7 @@ const UserParentalControl = () => {
|
||||||
key={tag}
|
key={tag}
|
||||||
tag={tag}
|
tag={tag}
|
||||||
tagType='blockedTag'
|
tagType='blockedTag'
|
||||||
|
removeTagCallback={removeBlockedTagsTagsCallback}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
@ -508,6 +484,7 @@ const UserParentalControl = () => {
|
||||||
DayOfWeek={accessSchedule.DayOfWeek}
|
DayOfWeek={accessSchedule.DayOfWeek}
|
||||||
StartHour={accessSchedule.StartHour}
|
StartHour={accessSchedule.StartHour}
|
||||||
EndHour={accessSchedule.EndHour}
|
EndHour={accessSchedule.EndHour}
|
||||||
|
removeScheduleCallback={removeScheduleCallback}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent, useCallback } from 'react';
|
||||||
import datetime from '../../../scripts/datetime';
|
import datetime from '../../../scripts/datetime';
|
||||||
import globalize from '../../../lib/globalize';
|
import globalize from '../../../lib/globalize';
|
||||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||||
|
@ -8,6 +8,7 @@ type AccessScheduleListProps = {
|
||||||
DayOfWeek?: string;
|
DayOfWeek?: string;
|
||||||
StartHour?: number ;
|
StartHour?: number ;
|
||||||
EndHour?: number;
|
EndHour?: number;
|
||||||
|
removeScheduleCallback?: (index: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDisplayTime(hours = 0) {
|
function getDisplayTime(hours = 0) {
|
||||||
|
@ -21,7 +22,10 @@ function getDisplayTime(hours = 0) {
|
||||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className='liSchedule listItem'
|
className='liSchedule listItem'
|
||||||
|
@ -43,6 +47,7 @@ const AccessScheduleList: FunctionComponent<AccessScheduleListProps> = ({ index,
|
||||||
title='Delete'
|
title='Delete'
|
||||||
icon='delete'
|
icon='delete'
|
||||||
dataIndex={index}
|
dataIndex={index}
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent, useCallback } from 'react';
|
||||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
tag?: string,
|
tag?: string,
|
||||||
tagType?: 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 (
|
return (
|
||||||
<div className='paperList'>
|
<div className='paperList'>
|
||||||
<div className='listItem'>
|
<div className='listItem'>
|
||||||
|
@ -21,6 +25,7 @@ const TagList: FunctionComponent<IProps> = ({ tag, tagType }: IProps) => {
|
||||||
title='Delete'
|
title='Delete'
|
||||||
icon='delete'
|
icon='delete'
|
||||||
dataTag={tag}
|
dataTag={tag}
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,6 +11,7 @@ type IProps = {
|
||||||
dataIndex?: string | number;
|
dataIndex?: string | number;
|
||||||
dataTag?: string | number;
|
dataTag?: string | number;
|
||||||
dataProfileid?: string | number;
|
dataProfileid?: string | number;
|
||||||
|
onClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createIconButtonElement = ({ is, id, className, title, icon, dataIndex, dataTag, dataProfileid }: IProps) => ({
|
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>`
|
</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 (
|
return (
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={createIconButtonElement({
|
dangerouslySetInnerHTML={createIconButtonElement({
|
||||||
|
@ -41,6 +42,7 @@ const IconButtonElement: FunctionComponent<IProps> = ({ is, id, className, title
|
||||||
dataTag: dataTag ? `data-tag="${dataTag}"` : '',
|
dataTag: dataTag ? `data-tag="${dataTag}"` : '',
|
||||||
dataProfileid: dataProfileid ? `data-profileid="${dataProfileid}"` : ''
|
dataProfileid: dataProfileid ? `data-profileid="${dataProfileid}"` : ''
|
||||||
})}
|
})}
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue