mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge branch 'master' into fix_long_getItems_request_URL
This commit is contained in:
commit
6d6d03a9c6
220 changed files with 21471 additions and 16761 deletions
|
@ -3,13 +3,14 @@ import React from 'react';
|
|||
|
||||
import { HistoryRouter } from './components/HistoryRouter';
|
||||
import { ApiProvider } from './hooks/useApi';
|
||||
import AppRoutes from './routes/index';
|
||||
import { AppRoutes, ExperimentalAppRoutes } from './routes';
|
||||
|
||||
const App = ({ history }: { history: History }) => {
|
||||
const layoutMode = localStorage.getItem('layout');
|
||||
return (
|
||||
<ApiProvider>
|
||||
<HistoryRouter history={history}>
|
||||
<AppRoutes />
|
||||
{layoutMode === 'experimental' ? <ExperimentalAppRoutes /> : <AppRoutes /> }
|
||||
</HistoryRouter>
|
||||
</ApiProvider>
|
||||
);
|
||||
|
|
|
@ -37,7 +37,7 @@ import template from './accessSchedule.template.html';
|
|||
context.querySelector('#selectEnd').innerHTML = html;
|
||||
}
|
||||
|
||||
function loadSchedule(context, {DayOfWeek, StartHour, EndHour}) {
|
||||
function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) {
|
||||
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday';
|
||||
context.querySelector('#selectStart').value = StartHour || 0;
|
||||
context.querySelector('#selectEnd').value = EndHour || 0;
|
||||
|
|
|
@ -309,8 +309,8 @@ function askForExit() {
|
|||
exitPromise = actionsheet.show({
|
||||
title: globalize.translate('MessageConfirmAppExit'),
|
||||
items: [
|
||||
{id: 'yes', name: globalize.translate('Yes')},
|
||||
{id: 'no', name: globalize.translate('No')}
|
||||
{ id: 'yes', name: globalize.translate('Yes') },
|
||||
{ id: 'no', name: globalize.translate('No') }
|
||||
]
|
||||
}).then(function (value) {
|
||||
if (value === 'yes') {
|
||||
|
@ -366,20 +366,20 @@ export const appHost = {
|
|||
};
|
||||
},
|
||||
deviceName: function () {
|
||||
return window.NativeShell?.AppHost?.deviceName
|
||||
? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||
return window.NativeShell?.AppHost?.deviceName ?
|
||||
window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||
},
|
||||
deviceId: function () {
|
||||
return window.NativeShell?.AppHost?.deviceId
|
||||
? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||
return window.NativeShell?.AppHost?.deviceId ?
|
||||
window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||
},
|
||||
appName: function () {
|
||||
return window.NativeShell?.AppHost?.appName
|
||||
? window.NativeShell.AppHost.appName() : appName;
|
||||
return window.NativeShell?.AppHost?.appName ?
|
||||
window.NativeShell.AppHost.appName() : appName;
|
||||
},
|
||||
appVersion: function () {
|
||||
return window.NativeShell?.AppHost?.appVersion
|
||||
? window.NativeShell.AppHost.appVersion() : Package.version;
|
||||
return window.NativeShell?.AppHost?.appVersion ?
|
||||
window.NativeShell.AppHost.appVersion() : Package.version;
|
||||
},
|
||||
getPushTokenInfo: function () {
|
||||
return {};
|
||||
|
|
|
@ -896,13 +896,13 @@ import { appRouter } from '../appRouter';
|
|||
}
|
||||
|
||||
if (options.showYear || options.showSeriesYear) {
|
||||
const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, {useGrouping: false});
|
||||
const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, { useGrouping: false });
|
||||
if (item.Type === 'Series') {
|
||||
if (item.Status === 'Continuing') {
|
||||
lines.push(globalize.translate('SeriesYearToPresent', productionYear || ''));
|
||||
} else {
|
||||
if (item.EndDate && item.ProductionYear) {
|
||||
const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), {useGrouping: false});
|
||||
const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false });
|
||||
lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
|
||||
} else {
|
||||
lines.push(productionYear || '');
|
||||
|
|
|
@ -28,7 +28,7 @@ import ServerConnections from '../ServerConnections';
|
|||
}
|
||||
|
||||
const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
|
||||
const videoStream = mediaStreams.filter(({Type}) => {
|
||||
const videoStream = mediaStreams.filter(({ Type }) => {
|
||||
return Type === 'Video';
|
||||
})[0] || {};
|
||||
|
||||
|
@ -68,7 +68,7 @@ import ServerConnections from '../ServerConnections';
|
|||
return html;
|
||||
}
|
||||
|
||||
function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) {
|
||||
function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) {
|
||||
if (ImageTag) {
|
||||
return apiClient.getScaledImageUrl(Id, {
|
||||
|
||||
|
@ -82,7 +82,7 @@ import ServerConnections from '../ServerConnections';
|
|||
return null;
|
||||
}
|
||||
|
||||
function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) {
|
||||
function buildChapterCard(item, apiClient, chapter, index, { width, coverImage }, className, shape) {
|
||||
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
|
||||
|
||||
let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
|
||||
|
|
|
@ -22,7 +22,7 @@ const Filter: FC<FilterProps> = ({
|
|||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const showFilterMenu = useCallback(() => {
|
||||
import('../filtermenu/filtermenu').then(({default: FilterMenu}) => {
|
||||
import('../filtermenu/filtermenu').then(({ default: FilterMenu }) => {
|
||||
const filterMenu = new FilterMenu();
|
||||
filterMenu.show({
|
||||
settings: viewQuerySettings,
|
||||
|
|
|
@ -6,7 +6,7 @@ const NewCollection: FC = () => {
|
|||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const showCollectionEditor = useCallback(() => {
|
||||
import('../collectionEditor/collectionEditor').then(({default: CollectionEditor}) => {
|
||||
import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => {
|
||||
const serverId = window.ApiClient.serverId();
|
||||
const collectionEditor = new CollectionEditor();
|
||||
collectionEditor.show({
|
||||
|
|
|
@ -16,7 +16,7 @@ const SelectView: FC<SelectViewProps> = ({
|
|||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const showViewSettingsMenu = useCallback(() => {
|
||||
import('../viewSettings/viewSettings').then(({default: ViewSettings}) => {
|
||||
import('../viewSettings/viewSettings').then(({ default: ViewSettings }) => {
|
||||
const viewsettings = new ViewSettings();
|
||||
viewsettings.show({
|
||||
settings: viewQuerySettings,
|
||||
|
|
|
@ -19,7 +19,7 @@ const Sort: FC<SortProps> = ({
|
|||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const showSortMenu = useCallback(() => {
|
||||
import('../sortmenu/sortmenu').then(({default: SortMenu}) => {
|
||||
import('../sortmenu/sortmenu').then(({ default: SortMenu }) => {
|
||||
const sortMenu = new SortMenu();
|
||||
sortMenu.show({
|
||||
settings: viewQuerySettings,
|
||||
|
|
|
@ -14,7 +14,7 @@ type IProps = {
|
|||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const AccessContainer: FunctionComponent<IProps> = ({containerClassName, headerTitle, checkBoxClassName, checkBoxTitle, listContainerClassName, accessClassName, listTitle, description, children }: IProps) => {
|
||||
const AccessContainer: FunctionComponent<IProps> = ({ containerClassName, headerTitle, checkBoxClassName, checkBoxTitle, listContainerClassName, accessClassName, listTitle, description, children }: IProps) => {
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<h2>{globalize.translate(headerTitle)}</h2>
|
||||
|
|
|
@ -22,7 +22,7 @@ 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 }: AccessScheduleListProps) => {
|
||||
return (
|
||||
<div
|
||||
className='liSchedule listItem'
|
||||
|
|
|
@ -5,7 +5,7 @@ type IProps = {
|
|||
tag?: string;
|
||||
}
|
||||
|
||||
const BlockedTagList: FunctionComponent<IProps> = ({tag}: IProps) => {
|
||||
const BlockedTagList: FunctionComponent<IProps> = ({ tag }: IProps) => {
|
||||
return (
|
||||
<div className='paperList'>
|
||||
<div className='listItem'>
|
||||
|
|
|
@ -36,7 +36,7 @@ const createLinkElement = (activeTab: string) => ({
|
|||
</a>`
|
||||
});
|
||||
|
||||
const SectionTabs: FunctionComponent<IProps> = ({activeTab}: IProps) => {
|
||||
const SectionTabs: FunctionComponent<IProps> = ({ activeTab }: IProps) => {
|
||||
return (
|
||||
<div
|
||||
data-role='controlgroup'
|
||||
|
|
|
@ -74,7 +74,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
|||
</div>
|
||||
<div className='cardFooter visualCardBox-cardFooter'>
|
||||
<div
|
||||
style={{textAlign: 'right', float: 'right', paddingTop: '5px'}}
|
||||
style={{ textAlign: 'right', float: 'right', paddingTop: '5px' }}
|
||||
>
|
||||
<IconButtonElement
|
||||
is='paper-icon-button-light'
|
||||
|
|
|
@ -14,7 +14,7 @@ type IProps = {
|
|||
userId: string;
|
||||
}
|
||||
|
||||
const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
|
||||
const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
const loadUser = useCallback(() => {
|
||||
|
@ -76,7 +76,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
|
|||
|
||||
chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false;
|
||||
|
||||
import('../../autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -214,7 +214,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
|
|||
<div ref={element}>
|
||||
<form
|
||||
className='updatePasswordForm passwordSection hide'
|
||||
style={{margin: '0 auto 2em'}}
|
||||
style={{ margin: '0 auto 2em' }}
|
||||
>
|
||||
<div className='detailSection'>
|
||||
<div id='fldCurrentPassword' className='inputContainer hide'>
|
||||
|
@ -260,7 +260,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
|
|||
<br />
|
||||
<form
|
||||
className='localAccessForm localAccessSection'
|
||||
style={{margin: '0 auto'}}
|
||||
style={{ margin: '0 auto' }}
|
||||
>
|
||||
<div className='detailSection'>
|
||||
<div className='detailSectionHeader'>
|
||||
|
|
|
@ -250,7 +250,6 @@ import '../../styles/scrollstyles.scss';
|
|||
}
|
||||
|
||||
function isOpened(dlg) {
|
||||
//return dlg.opened;
|
||||
return !dlg.classList.contains('hide');
|
||||
}
|
||||
|
||||
|
|
|
@ -297,10 +297,8 @@ class FilterMenu {
|
|||
}
|
||||
|
||||
if (submitted) {
|
||||
//if (!options.onChange) {
|
||||
saveValues(dlg, options.settings, options.settingsKey, options.setfilters);
|
||||
return resolve();
|
||||
//}
|
||||
}
|
||||
return resolve();
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ import ServerConnections from '../ServerConnections';
|
|||
import template from './tvguide.template.html';
|
||||
|
||||
function showViewSettings(instance) {
|
||||
import('./guide-settings').then(({default: guideSettingsDialog}) => {
|
||||
import('./guide-settings').then(({ default: guideSettingsDialog }) => {
|
||||
guideSettingsDialog.show(instance.categoryOptions).then(function () {
|
||||
instance.refresh();
|
||||
});
|
||||
|
|
|
@ -26,8 +26,8 @@ import Events from '../utils/events.ts';
|
|||
function canPlayNativeHls() {
|
||||
const media = document.createElement('video');
|
||||
|
||||
return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
|
||||
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, ''));
|
||||
return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '')
|
||||
|| media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, ''));
|
||||
}
|
||||
|
||||
export function enableHlsJsPlayer(runTimeTicks, mediaType) {
|
||||
|
@ -51,18 +51,10 @@ import Events from '../utils/events.ts';
|
|||
return true;
|
||||
}
|
||||
|
||||
if (browser.edge && mediaType === 'Video') {
|
||||
//return true;
|
||||
}
|
||||
|
||||
// simple playback should use the native support
|
||||
if (runTimeTicks) {
|
||||
//if (!browser.edge) {
|
||||
return false;
|
||||
//}
|
||||
}
|
||||
|
||||
//return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -201,8 +193,8 @@ import Events from '../utils/events.ts';
|
|||
.catch((e) => {
|
||||
const errorName = (e.name || '').toLowerCase();
|
||||
// safari uses aborterror
|
||||
if (errorName === 'notallowederror' ||
|
||||
errorName === 'aborterror') {
|
||||
if (errorName === 'notallowederror'
|
||||
|| errorName === 'aborterror') {
|
||||
// swallow this error because the user can still click the play button on the video element
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ import template from './imageeditor.template.html';
|
|||
const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10);
|
||||
const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10);
|
||||
|
||||
import('../actionSheet/actionSheet').then(({default: actionSheet}) => {
|
||||
import('../actionSheet/actionSheet').then(({ default: actionSheet }) => {
|
||||
const commands = [];
|
||||
|
||||
commands.push({
|
||||
|
@ -353,7 +353,7 @@ import template from './imageeditor.template.html';
|
|||
addListeners(context, 'btnOpenUploadMenu', 'click', function () {
|
||||
const imageType = this.getAttribute('data-imagetype');
|
||||
|
||||
import('../imageUploader/imageUploader').then(({default: imageUploader}) => {
|
||||
import('../imageUploader/imageUploader').then(({ default: imageUploader }) => {
|
||||
imageUploader.show({
|
||||
|
||||
theme: options.theme,
|
||||
|
|
|
@ -326,7 +326,7 @@ import toast from './toast/toast';
|
|||
// eslint-disable-next-line sonarjs/max-switch-cases
|
||||
switch (id) {
|
||||
case 'addtocollection':
|
||||
import('./collectionEditor/collectionEditor').then(({default: CollectionEditor}) => {
|
||||
import('./collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => {
|
||||
const collectionEditor = new CollectionEditor();
|
||||
collectionEditor.show({
|
||||
items: [itemId],
|
||||
|
@ -335,7 +335,7 @@ import toast from './toast/toast';
|
|||
});
|
||||
break;
|
||||
case 'addtoplaylist':
|
||||
import('./playlisteditor/playlisteditor').then(({default: playlistEditor}) => {
|
||||
import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => {
|
||||
new playlistEditor({
|
||||
items: [itemId],
|
||||
serverId: serverId
|
||||
|
@ -408,7 +408,7 @@ import toast from './toast/toast';
|
|||
break;
|
||||
}
|
||||
case 'editsubtitles':
|
||||
import('./subtitleeditor/subtitleeditor').then(({default: subtitleEditor}) => {
|
||||
import('./subtitleeditor/subtitleeditor').then(({ default: subtitleEditor }) => {
|
||||
subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
|
||||
});
|
||||
break;
|
||||
|
@ -464,7 +464,7 @@ import toast from './toast/toast';
|
|||
playbackManager.clearQueue();
|
||||
break;
|
||||
case 'record':
|
||||
import('./recordingcreator/recordingcreator').then(({default: recordingCreator}) => {
|
||||
import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => {
|
||||
recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
|
||||
});
|
||||
break;
|
||||
|
@ -535,7 +535,7 @@ import toast from './toast/toast';
|
|||
}
|
||||
|
||||
function deleteTimer(apiClient, item, resolve, command) {
|
||||
import('./recordingcreator/recordinghelper').then(({default: recordingHelper}) => {
|
||||
import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => {
|
||||
const timerId = item.TimerId || item.Id;
|
||||
recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () {
|
||||
getResolveFunction(resolve, command, true)();
|
||||
|
@ -544,7 +544,7 @@ import toast from './toast/toast';
|
|||
}
|
||||
|
||||
function deleteSeriesTimer(apiClient, item, resolve, command) {
|
||||
import('./recordingcreator/recordinghelper').then(({default: recordingHelper}) => {
|
||||
import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => {
|
||||
recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () {
|
||||
getResolveFunction(resolve, command, true)();
|
||||
});
|
||||
|
@ -585,15 +585,15 @@ import toast from './toast/toast';
|
|||
const serverId = apiClient.serverInfo().Id;
|
||||
|
||||
if (item.Type === 'Timer') {
|
||||
import('./recordingcreator/recordingeditor').then(({default: recordingEditor}) => {
|
||||
import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => {
|
||||
recordingEditor.show(item.Id, serverId).then(resolve, reject);
|
||||
});
|
||||
} else if (item.Type === 'SeriesTimer') {
|
||||
import('./recordingcreator/seriesrecordingeditor').then(({default: recordingEditor}) => {
|
||||
import('./recordingcreator/seriesrecordingeditor').then(({ default: recordingEditor }) => {
|
||||
recordingEditor.show(item.Id, serverId).then(resolve, reject);
|
||||
});
|
||||
} else {
|
||||
import('./metadataEditor/metadataEditor').then(({default: metadataEditor}) => {
|
||||
import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => {
|
||||
metadataEditor.show(item.Id, serverId).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
@ -614,7 +614,7 @@ import toast from './toast/toast';
|
|||
}
|
||||
|
||||
function refresh(apiClient, item) {
|
||||
import('./refreshdialog/refreshdialog').then(({default: refreshDialog}) => {
|
||||
import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => {
|
||||
new refreshDialog({
|
||||
itemIds: [item.Id],
|
||||
serverId: apiClient.serverInfo().Id,
|
||||
|
|
|
@ -167,7 +167,7 @@ import datetime from '../../scripts/datetime';
|
|||
lines.push(escapeHtml(identifyResult.Name));
|
||||
|
||||
if (identifyResult.ProductionYear) {
|
||||
lines.push(datetime.toLocaleString(identifyResult.ProductionYear, {useGrouping: false}));
|
||||
lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false }));
|
||||
}
|
||||
|
||||
let resultHtml = lines.join('<br/>');
|
||||
|
|
|
@ -316,7 +316,7 @@ import template from './libraryoptionseditor.template.html';
|
|||
}
|
||||
|
||||
function showImageOptionsForType(type) {
|
||||
import('../imageOptionsEditor/imageOptionsEditor').then(({default: ImageOptionsEditor}) => {
|
||||
import('../imageOptionsEditor/imageOptionsEditor').then(({ default: ImageOptionsEditor }) => {
|
||||
let typeOptions = getTypeOptions(currentLibraryOptions, type);
|
||||
if (!typeOptions) {
|
||||
typeOptions = {
|
||||
|
|
|
@ -65,7 +65,7 @@ import '../elements/emby-button/emby-button';
|
|||
}
|
||||
};
|
||||
|
||||
import('../scripts/touchHelper').then(({default: TouchHelper}) => {
|
||||
import('../scripts/touchHelper').then(({ default: TouchHelper }) => {
|
||||
const touchHelper = new TouchHelper(view.parentNode.parentNode);
|
||||
|
||||
Events.on(touchHelper, 'swipeleft', onSwipeLeft);
|
||||
|
|
|
@ -25,7 +25,7 @@ import toast from '../toast/toast';
|
|||
import alert from '../alert';
|
||||
import template from './mediaLibraryCreator.template.html';
|
||||
|
||||
function onAddLibrary() {
|
||||
function onAddLibrary(e) {
|
||||
if (isCreating) {
|
||||
return false;
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ import template from './mediaLibraryCreator.template.html';
|
|||
isCreating = false;
|
||||
loading.hide();
|
||||
});
|
||||
return false;
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function getCollectionTypeOptionsHtml(collectionTypeOptions) {
|
||||
|
@ -96,14 +96,14 @@ import template from './mediaLibraryCreator.template.html';
|
|||
$('.collectionTypeFieldDescription', dlg).html(folderOption?.message || '');
|
||||
});
|
||||
page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick);
|
||||
page.querySelector('.btnSubmit').addEventListener('click', onAddLibrary);
|
||||
page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary);
|
||||
page.querySelector('.folderList').addEventListener('click', onRemoveClick);
|
||||
}
|
||||
|
||||
function onAddButtonClick() {
|
||||
const page = dom.parentWithClass(this, 'dlg-librarycreator');
|
||||
|
||||
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
enableNetworkSharePath: true,
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||
</div>
|
||||
|
||||
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
||||
<div class="dialogContentInner dialog-content-centered">
|
||||
|
||||
<div id="fldCollectionType" class="selectContainer">
|
||||
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
|
||||
<div class="collectionTypeFieldDescription fieldDescription">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtValue" required="required" label="${LabelDisplayName}" />
|
||||
</div>
|
||||
|
||||
<div class="folders">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<h1 style="margin: .5em 0;">${Folders}</h1>
|
||||
<button is="emby-button" type="button" class="fab btnAddFolder submit" title="${Add}">
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="paperList folderList hide" style="margin-bottom:2em;"></div>
|
||||
</div>
|
||||
|
||||
<div class="libraryOptions"></div>
|
||||
<form class="addLibraryForm" style="max-width:100%;">
|
||||
<div class="formDialogHeader">
|
||||
<button type="button" is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${ButtonBack}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>
|
||||
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="formDialogFooter">
|
||||
<button is="emby-button" type="button" class="raised btnSubmit button-submit block formDialogFooterItem">
|
||||
<span>${ButtonOk}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="formDialogContent scrollY" style="padding-top:2em;">
|
||||
<div class="dialogContentInner dialog-content-centered">
|
||||
|
||||
<div id="fldCollectionType" class="selectContainer">
|
||||
<select is="emby-select" id="selectCollectionType" data-mini="true" required="required" label="${LabelContentType}"></select>
|
||||
<div class="collectionTypeFieldDescription fieldDescription">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="text" id="txtValue" required="required" label="${LabelDisplayName}" />
|
||||
</div>
|
||||
|
||||
<div class="folders">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<h1 style="margin: .5em 0;">${Folders}</h1>
|
||||
<button is="emby-button" type="button" class="fab btnAddFolder submit" title="${Add}">
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="paperList folderList hide" style="margin-bottom:2em;"></div>
|
||||
</div>
|
||||
|
||||
<div class="libraryOptions"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="formDialogFooter">
|
||||
<button is="emby-button" type="submit" class="raised btnSubmit button-submit block formDialogFooterItem">
|
||||
<span>${ButtonOk}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -164,7 +164,7 @@ import template from './mediaLibraryEditor.template.html';
|
|||
}
|
||||
|
||||
function showDirectoryBrowser(context, originalPath, networkPath) {
|
||||
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
enableNetworkSharePath: true,
|
||||
|
|
|
@ -177,13 +177,13 @@ import * as userSettings from '../../scripts/settings/userSettings';
|
|||
|
||||
if (options.year !== false && item.ProductionYear && item.Type === 'Series') {
|
||||
if (item.Status === 'Continuing') {
|
||||
miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, {useGrouping: false})));
|
||||
miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, { useGrouping: false })));
|
||||
} else if (item.ProductionYear) {
|
||||
text = datetime.toLocaleString(item.ProductionYear, {useGrouping: false});
|
||||
text = datetime.toLocaleString(item.ProductionYear, { useGrouping: false });
|
||||
|
||||
if (item.EndDate) {
|
||||
try {
|
||||
const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), {useGrouping: false});
|
||||
const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false });
|
||||
|
||||
if (endYear !== item.ProductionYear) {
|
||||
text += `-${endYear}`;
|
||||
|
@ -253,7 +253,7 @@ import * as userSettings from '../../scripts/settings/userSettings';
|
|||
miscInfo.push(item.ProductionYear);
|
||||
} else if (item.PremiereDate) {
|
||||
try {
|
||||
text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false});
|
||||
text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false });
|
||||
miscInfo.push(text);
|
||||
} catch (e) {
|
||||
console.error('error parsing date:', item.PremiereDate);
|
||||
|
|
|
@ -211,7 +211,7 @@ import template from './metadataEditor.template.html';
|
|||
}
|
||||
|
||||
function addElementToList(source, sortCallback) {
|
||||
import('../prompt/prompt').then(({default: prompt}) => {
|
||||
import('../prompt/prompt').then(({ default: prompt }) => {
|
||||
prompt({
|
||||
label: 'Value:'
|
||||
}).then(function (text) {
|
||||
|
@ -229,7 +229,7 @@ import template from './metadataEditor.template.html';
|
|||
}
|
||||
|
||||
function editPerson(context, person, index) {
|
||||
import('./personEditor').then(({default: personEditor}) => {
|
||||
import('./personEditor').then(({ default: personEditor }) => {
|
||||
personEditor.show(person).then(function (updatedPerson) {
|
||||
const isNew = index === -1;
|
||||
|
||||
|
@ -253,7 +253,7 @@ import template from './metadataEditor.template.html';
|
|||
}
|
||||
|
||||
function showMoreMenu(context, button, user) {
|
||||
import('../itemContextMenu').then(({default: itemContextMenu}) => {
|
||||
import('../itemContextMenu').then(({ default: itemContextMenu }) => {
|
||||
const item = currentItem;
|
||||
|
||||
itemContextMenu.show({
|
||||
|
@ -588,12 +588,12 @@ import template from './metadataEditor.template.html';
|
|||
hideElement('#collapsibleSpecialEpisodeInfo', context);
|
||||
}
|
||||
|
||||
if (item.Type === 'Person' ||
|
||||
item.Type === 'Genre' ||
|
||||
item.Type === 'Studio' ||
|
||||
item.Type === 'MusicGenre' ||
|
||||
item.Type === 'TvChannel' ||
|
||||
item.Type === 'Book') {
|
||||
if (item.Type === 'Person'
|
||||
|| item.Type === 'Genre'
|
||||
|| item.Type === 'Studio'
|
||||
|| item.Type === 'MusicGenre'
|
||||
|| item.Type === 'TvChannel'
|
||||
|| item.Type === 'Book') {
|
||||
hideElement('#peopleCollapsible', context);
|
||||
} else {
|
||||
showElement('#peopleCollapsible', context);
|
||||
|
|
|
@ -205,13 +205,6 @@ import datetime from '../../scripts/datetime';
|
|||
|
||||
if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) {
|
||||
// Disabled because there is no callback for this item
|
||||
/*
|
||||
menuItems.push({
|
||||
name: globalize.translate('Download'),
|
||||
id: 'download',
|
||||
icon: 'file_download'
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
|
@ -267,7 +260,7 @@ import datetime from '../../scripts/datetime';
|
|||
}
|
||||
break;
|
||||
case 'addtocollection':
|
||||
import('../collectionEditor/collectionEditor').then(({default: CollectionEditor}) => {
|
||||
import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => {
|
||||
const collectionEditor = new CollectionEditor();
|
||||
collectionEditor.show({
|
||||
items: items,
|
||||
|
@ -308,7 +301,7 @@ import datetime from '../../scripts/datetime';
|
|||
dispatchNeedsRefresh();
|
||||
break;
|
||||
case 'refresh':
|
||||
import('../refreshdialog/refreshdialog').then(({default: refreshDialog}) => {
|
||||
import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => {
|
||||
new refreshDialog({
|
||||
itemIds: items,
|
||||
serverId: serverId
|
||||
|
|
|
@ -69,7 +69,7 @@ import shell from '../../scripts/shell';
|
|||
const list = [];
|
||||
|
||||
imageSizes.forEach((size) => {
|
||||
const url = getImageUrl(item, {height: size});
|
||||
const url = getImageUrl(item, { height: size });
|
||||
if (url !== null) {
|
||||
list.push(url);
|
||||
}
|
||||
|
|
|
@ -1039,7 +1039,6 @@ class PlaybackManager {
|
|||
}
|
||||
}
|
||||
|
||||
//var mediaType = item.MediaType;
|
||||
return getPlayer(item, getDefaultPlayOptions()) != null;
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ import Events from '../../utils/events.ts';
|
|||
import layoutManager from '../layoutManager';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import playMethodHelper from '../playback/playmethodhelper';
|
||||
import SyncPlay from '../../plugins/syncPlay/core';
|
||||
import { pluginManager } from '../pluginManager';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
import './playerstats.scss';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
|
||||
|
@ -325,6 +326,12 @@ import ServerConnections from '../ServerConnections';
|
|||
}
|
||||
|
||||
function getSyncPlayStats() {
|
||||
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
|
||||
|
||||
if (!SyncPlay?.Manager.isSyncPlayEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const syncStats = [];
|
||||
const stats = SyncPlay.Manager.getStats();
|
||||
|
||||
|
@ -422,10 +429,10 @@ import ServerConnections from '../ServerConnections';
|
|||
name: globalize.translate('LabelOriginalMediaInfo')
|
||||
});
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId);
|
||||
if (SyncPlay.Manager.isSyncPlayEnabled() && apiClient.isMinServerVersion('10.6.0')) {
|
||||
const syncPlayStats = getSyncPlayStats();
|
||||
if (syncPlayStats.length > 0) {
|
||||
categories.push({
|
||||
stats: getSyncPlayStats(),
|
||||
stats: syncPlayStats,
|
||||
name: globalize.translate('LabelSyncPlayInfo')
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import dialogHelper from '../dialogHelper/dialogHelper';
|
|||
import loading from '../loading/loading';
|
||||
import layoutManager from '../layoutManager';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import SyncPlay from '../../plugins/syncPlay/core';
|
||||
import { pluginManager } from '../pluginManager';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import { appRouter } from '../appRouter';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import { PluginType } from '../../types/plugin.ts';
|
||||
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-input/emby-input';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
|
@ -117,10 +119,12 @@ import ServerConnections from '../ServerConnections';
|
|||
};
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
|
||||
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
|
||||
let html = '';
|
||||
|
||||
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay.Manager.isSyncPlayEnabled()) {
|
||||
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
|
||||
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
|
||||
}
|
||||
|
||||
|
|
|
@ -119,9 +119,14 @@ class PluginManager {
|
|||
}
|
||||
|
||||
ofType(type) {
|
||||
return this.pluginsList.filter((o) => {
|
||||
return o.type === type;
|
||||
});
|
||||
return this.pluginsList.filter(plugin => plugin.type === type);
|
||||
}
|
||||
|
||||
firstOfType(type) {
|
||||
// Get all plugins of the specified type
|
||||
return this.ofType(type)
|
||||
// Return the plugin with the "highest" (lowest numeric value) priority
|
||||
.sort((p1, p2) => (p1.priority || 0) - (p2.priority || 0))[0];
|
||||
}
|
||||
|
||||
#mapRoute(plugin, route) {
|
||||
|
|
|
@ -141,7 +141,7 @@ function onManageRecordingClick() {
|
|||
}
|
||||
|
||||
const self = this;
|
||||
import('./recordingeditor').then(({default: recordingEditor}) => {
|
||||
import('./recordingeditor').then(({ default: recordingEditor }) => {
|
||||
recordingEditor.show(self.TimerId, options.serverId, {
|
||||
enableCancel: false
|
||||
}).then(function () {
|
||||
|
@ -159,7 +159,7 @@ function onManageSeriesRecordingClick() {
|
|||
|
||||
const self = this;
|
||||
|
||||
import('./seriesrecordingeditor').then(({default: seriesRecordingEditor}) => {
|
||||
import('./seriesrecordingeditor').then(({ default: seriesRecordingEditor }) => {
|
||||
seriesRecordingEditor.show(self.SeriesTimerId, options.serverId, {
|
||||
|
||||
enableCancel: false
|
||||
|
|
|
@ -389,13 +389,13 @@ import layoutManager from './layoutManager';
|
|||
|
||||
if (xScroller !== yScroller) {
|
||||
if (xScroller) {
|
||||
scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior});
|
||||
scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior });
|
||||
}
|
||||
if (yScroller) {
|
||||
scrollToHelper(yScroller, {top: scrollY, behavior: scrollBehavior});
|
||||
scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior });
|
||||
}
|
||||
} else if (xScroller) {
|
||||
scrollToHelper(xScroller, {left: scrollX, top: scrollY, behavior: scrollBehavior});
|
||||
scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -597,7 +597,7 @@ import layoutManager from './layoutManager';
|
|||
setTimeout(function() {
|
||||
scrollToElement(e.target, useSmoothScroll());
|
||||
}, 0);
|
||||
}, {capture: true});
|
||||
}, { capture: true });
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
|
|
|
@ -85,8 +85,8 @@ const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {
|
|||
dangerouslySetInnerHTML={createInputElement()}
|
||||
/>
|
||||
</div>
|
||||
{layoutManager.tv && !browser.tv &&
|
||||
<AlphaPicker onAlphaPicked={onAlphaPicked} />
|
||||
{layoutManager.tv && !browser.tv
|
||||
&& <AlphaPicker onAlphaPicked={onAlphaPicked} />
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -71,7 +71,7 @@ import toast from './toast/toast';
|
|||
}
|
||||
|
||||
function showProgramDialog(item) {
|
||||
import('./recordingcreator/recordingcreator').then(({default:recordingCreator}) => {
|
||||
import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => {
|
||||
recordingCreator.show(item.Id, item.ServerId);
|
||||
});
|
||||
}
|
||||
|
@ -272,7 +272,7 @@ import toast from './toast/toast';
|
|||
}
|
||||
|
||||
function addToPlaylist(item) {
|
||||
import('./playlisteditor/playlisteditor').then(({default: playlistEditor}) => {
|
||||
import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => {
|
||||
new playlistEditor().show({
|
||||
items: [item.Id],
|
||||
serverId: item.ServerId
|
||||
|
@ -297,16 +297,16 @@ import toast from './toast/toast';
|
|||
|
||||
if (item.Type === 'Timer') {
|
||||
if (item.ProgramId) {
|
||||
import('./recordingcreator/recordingcreator').then(({default: recordingCreator}) => {
|
||||
import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => {
|
||||
recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject);
|
||||
});
|
||||
} else {
|
||||
import('./recordingcreator/recordingeditor').then(({default: recordingEditor}) => {
|
||||
import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => {
|
||||
recordingEditor.show(item.Id, currentServerId).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
import('./metadataEditor/metadataEditor').then(({default: metadataEditor}) => {
|
||||
import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => {
|
||||
metadataEditor.show(item.Id, currentServerId).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ function showDownloadOptions(button, context, subtitleId) {
|
|||
}
|
||||
|
||||
function centerFocus(elem, horiz, on) {
|
||||
import('../../scripts/scrollHelper').then(({default: scrollHelper}) => {
|
||||
import('../../scripts/scrollHelper').then(({ default: scrollHelper }) => {
|
||||
const fn = on ? 'on' : 'off';
|
||||
scrollHelper.centerFocus[fn](elem, horiz);
|
||||
});
|
||||
|
@ -353,7 +353,7 @@ function onOpenUploadMenu(e) {
|
|||
const selectLanguage = dialog.querySelector('#selectLanguage');
|
||||
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
|
||||
|
||||
import('../subtitleuploader/subtitleuploader').then(({default: subtitleUploader}) => {
|
||||
import('../subtitleuploader/subtitleuploader').then(({ default: subtitleUploader }) => {
|
||||
subtitleUploader.show({
|
||||
languages: {
|
||||
list: selectLanguage.innerHTML,
|
||||
|
|
|
@ -94,9 +94,9 @@ function init(instance) {
|
|||
|
||||
subtitleSyncSlider.getBubbleHtml = function (value) {
|
||||
const newOffset = getOffsetFromPercentage(value);
|
||||
return '<h1 class="sliderBubbleText">' +
|
||||
(newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' +
|
||||
'</h1>';
|
||||
return '<h1 class="sliderBubbleText">'
|
||||
+ (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's'
|
||||
+ '</h1>';
|
||||
};
|
||||
|
||||
subtitleSyncCloseButton.addEventListener('click', function () {
|
||||
|
|
114
src/components/tabbedview/tabbedview.js
Normal file
114
src/components/tabbedview/tabbedview.js
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { clearBackdrop } from '../backdrop/backdrop';
|
||||
import * as mainTabsManager from '../maintabsmanager';
|
||||
import layoutManager from '../layoutManager';
|
||||
import '../../elements/emby-tabs/emby-tabs';
|
||||
import LibraryMenu from '../../scripts/libraryMenu';
|
||||
|
||||
function onViewDestroy() {
|
||||
const tabControllers = this.tabControllers;
|
||||
|
||||
if (tabControllers) {
|
||||
tabControllers.forEach(function (t) {
|
||||
if (t.destroy) {
|
||||
t.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
this.tabControllers = null;
|
||||
}
|
||||
|
||||
this.view = null;
|
||||
this.params = null;
|
||||
this.currentTabController = null;
|
||||
this.initialTabIndex = null;
|
||||
}
|
||||
|
||||
class TabbedView {
|
||||
constructor(view, params) {
|
||||
this.tabControllers = [];
|
||||
this.view = view;
|
||||
this.params = params;
|
||||
|
||||
const self = this;
|
||||
|
||||
let currentTabIndex = parseInt(params.tab || this.getDefaultTabIndex(params.parentId), 10);
|
||||
this.initialTabIndex = currentTabIndex;
|
||||
|
||||
function validateTabLoad(index) {
|
||||
return self.validateTabLoad ? self.validateTabLoad(index) : Promise.resolve();
|
||||
}
|
||||
|
||||
function loadTab(index, previousIndex) {
|
||||
validateTabLoad(index).then(function () {
|
||||
self.getTabController(index).then(function (controller) {
|
||||
const refresh = !controller.refreshed;
|
||||
|
||||
controller.onResume({
|
||||
autoFocus: previousIndex == null && layoutManager.tv,
|
||||
refresh: refresh
|
||||
});
|
||||
|
||||
controller.refreshed = true;
|
||||
|
||||
currentTabIndex = index;
|
||||
self.currentTabController = controller;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getTabContainers() {
|
||||
return view.querySelectorAll('.tabContent');
|
||||
}
|
||||
|
||||
function onTabChange(e) {
|
||||
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
|
||||
const previousIndex = e.detail.previousIndex;
|
||||
|
||||
const previousTabController = previousIndex == null ? null : self.tabControllers[previousIndex];
|
||||
if (previousTabController && previousTabController.onPause) {
|
||||
previousTabController.onPause();
|
||||
}
|
||||
|
||||
loadTab(newIndex, previousIndex);
|
||||
}
|
||||
|
||||
view.addEventListener('viewbeforehide', this.onPause.bind(this));
|
||||
|
||||
view.addEventListener('viewbeforeshow', function () {
|
||||
mainTabsManager.setTabs(view, currentTabIndex, self.getTabs, getTabContainers, null, onTabChange, false);
|
||||
});
|
||||
|
||||
view.addEventListener('viewshow', function (e) {
|
||||
self.onResume(e.detail);
|
||||
});
|
||||
|
||||
view.addEventListener('viewdestroy', onViewDestroy.bind(this));
|
||||
}
|
||||
|
||||
onResume() {
|
||||
this.setTitle();
|
||||
clearBackdrop();
|
||||
|
||||
const currentTabController = this.currentTabController;
|
||||
|
||||
if (!currentTabController) {
|
||||
mainTabsManager.selectedTabIndex(this.initialTabIndex);
|
||||
} else if (currentTabController && currentTabController.onResume) {
|
||||
currentTabController.onResume({});
|
||||
}
|
||||
}
|
||||
|
||||
onPause() {
|
||||
const currentTabController = this.currentTabController;
|
||||
|
||||
if (currentTabController && currentTabController.onPause) {
|
||||
currentTabController.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
setTitle() {
|
||||
LibraryMenu.setTitle('');
|
||||
}
|
||||
}
|
||||
|
||||
export default TabbedView;
|
|
@ -48,7 +48,7 @@ function refreshTunerDevices(page, providerInfo, devices) {
|
|||
function onSelectPathClick(e) {
|
||||
const page = $(e.target).parents('.xmltvForm')[0];
|
||||
|
||||
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
|
|
|
@ -97,7 +97,7 @@ function dispatchViewEvent(view, eventInfo, eventName, isCancellable) {
|
|||
return eventResult;
|
||||
}
|
||||
|
||||
function getViewEventDetail(view, {state, url, options = {}}, isRestored) {
|
||||
function getViewEventDetail(view, { state, url, options = {} }, isRestored) {
|
||||
const index = url.indexOf('?');
|
||||
// eslint-disable-next-line compat/compat
|
||||
const searchParams = new URLSearchParams(url.substring(index + 1));
|
||||
|
|
|
@ -52,7 +52,7 @@ import { pageIdOn } from '../../utils/dashboard';
|
|||
}
|
||||
|
||||
function showNewKeyPrompt(page) {
|
||||
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
||||
import('../../components/prompt/prompt').then(({ default: prompt }) => {
|
||||
prompt({
|
||||
title: globalize.translate('HeaderNewApiKey'),
|
||||
label: globalize.translate('LabelAppName'),
|
||||
|
|
|
@ -65,7 +65,7 @@ import confirm from '../../components/confirm/confirm';
|
|||
}
|
||||
|
||||
function showSendMessageForm(btn, session) {
|
||||
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
||||
import('../../components/prompt/prompt').then(({ default: prompt }) => {
|
||||
prompt({
|
||||
title: globalize.translate('HeaderSendMessage'),
|
||||
label: globalize.translate('LabelMessageText'),
|
||||
|
@ -82,7 +82,7 @@ import confirm from '../../components/confirm/confirm';
|
|||
}
|
||||
|
||||
function showOptionsMenu(btn, session) {
|
||||
import('../../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
const menuItems = [];
|
||||
|
||||
if (session.ServerId && session.DeviceId !== ServerConnections.deviceId()) {
|
||||
|
|
|
@ -68,7 +68,7 @@ import confirm from '../../../components/confirm/confirm';
|
|||
});
|
||||
}
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: btn,
|
||||
|
|
|
@ -111,7 +111,7 @@
|
|||
<span>${EnableIntelLowPowerHevcHwEncoder}</span>
|
||||
</label>
|
||||
<div class="fieldDescription">
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://01.org/linuxgraphics/downloads/firmware" target="_blank">${IntelLowPowerEncHelp}</a>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="button-link" href="https://jellyfin.org/docs/general/administration/hardware-acceleration/intel#configure-and-verify-lp-mode-on-linux" target="_blank">${IntelLowPowerEncHelp}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -253,6 +253,13 @@
|
|||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${EnableFallbackFontHelp}</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input is="emby-checkbox" type="checkbox" id="chkEnableAudioVbr" />
|
||||
<span>${LabelEnableAudioVbr}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LabelEnableAudioVbrHelp}</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtDownMixAudioBoost" pattern="[0-9]*" required="required" min=".5" max="3" step=".1" label="${LabelDownMixAudioScale}" />
|
||||
<div class="fieldDescription">${LabelDownMixAudioScaleHelp}</div>
|
||||
|
|
|
@ -22,6 +22,7 @@ import alert from '../../components/alert';
|
|||
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
|
||||
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
|
||||
$('#selectThreadCount', page).val(config.EncodingThreadCount);
|
||||
page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr;
|
||||
$('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost);
|
||||
$('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None');
|
||||
page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || '';
|
||||
|
@ -78,6 +79,7 @@ import alert from '../../components/alert';
|
|||
const onDecoderConfirmed = function () {
|
||||
loading.show();
|
||||
ApiClient.getNamedConfiguration('encoding').then(function (config) {
|
||||
config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked;
|
||||
config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val();
|
||||
config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None';
|
||||
config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value;
|
||||
|
@ -237,7 +239,7 @@ import alert from '../../components/alert';
|
|||
setDecodingCodecsVisible(page, this.value);
|
||||
});
|
||||
$('#btnSelectEncoderPath', page).on('click.selectDirectory', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
|
@ -252,7 +254,7 @@ import alert from '../../components/alert';
|
|||
});
|
||||
});
|
||||
$('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
|
@ -269,7 +271,7 @@ import alert from '../../components/alert';
|
|||
});
|
||||
});
|
||||
$('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeDirectories: true,
|
||||
|
|
|
@ -80,6 +80,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verticalSection">
|
||||
<h2>${HeaderPerformance}</h2>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtParallelImageEncodingLimit" label="${LabelParallelImageEncodingLimit}" type="number" pattern="[0-9]*" min="0" step="1" />
|
||||
<div class="fieldDescription">${LabelParallelImageEncodingLimitHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
|
|
|
@ -21,6 +21,7 @@ import alert from '../../components/alert';
|
|||
$('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) {
|
||||
return '<option value="' + language.Value + '">' + language.Name + '</option>';
|
||||
})).val(config.UICulture);
|
||||
page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || '';
|
||||
|
||||
loading.hide();
|
||||
}
|
||||
|
@ -36,6 +37,7 @@ import alert from '../../components/alert';
|
|||
config.MetadataPath = $('#txtMetadataPath', form).val();
|
||||
config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val();
|
||||
config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked;
|
||||
config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10);
|
||||
|
||||
ApiClient.updateServerConfiguration(config).then(function() {
|
||||
ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) {
|
||||
|
@ -58,7 +60,7 @@ import alert from '../../components/alert';
|
|||
const brandingConfigKey = 'branding';
|
||||
export default function (view) {
|
||||
$('#btnSelectCachePath', view).on('click.selectDirectory', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
|
@ -75,7 +77,7 @@ import alert from '../../components/alert';
|
|||
});
|
||||
});
|
||||
$('#btnSelectMetadataPath', view).on('click.selectDirectory', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
path: $('#txtMetadataPath', view).val(),
|
||||
|
|
|
@ -15,7 +15,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
|||
/* eslint-disable indent */
|
||||
|
||||
function addVirtualFolder(page) {
|
||||
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({default: medialibrarycreator}) => {
|
||||
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => {
|
||||
new medialibrarycreator({
|
||||
collectionTypeOptions: getCollectionTypeOptions().filter(function (f) {
|
||||
return !f.hidden;
|
||||
|
@ -30,7 +30,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
|||
}
|
||||
|
||||
function editVirtualFolder(page, virtualFolder) {
|
||||
import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({default: medialibraryeditor}) => {
|
||||
import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => {
|
||||
new medialibraryeditor({
|
||||
refresh: shouldRefreshLibraryAfterChanges(page),
|
||||
library: virtualFolder
|
||||
|
@ -64,7 +64,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
|||
}
|
||||
|
||||
function refreshVirtualFolder(page, virtualFolder) {
|
||||
import('../../components/refreshdialog/refreshdialog').then(({default: refreshDialog}) => {
|
||||
import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => {
|
||||
new refreshDialog({
|
||||
itemIds: [virtualFolder.ItemId],
|
||||
serverId: ApiClient.serverId(),
|
||||
|
@ -74,7 +74,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
|||
}
|
||||
|
||||
function renameVirtualFolder(page, virtualFolder) {
|
||||
import('../../components/prompt/prompt').then(({default: prompt}) => {
|
||||
import('../../components/prompt/prompt').then(({ default: prompt }) => {
|
||||
prompt({
|
||||
label: globalize.translate('LabelNewName'),
|
||||
description: globalize.translate('MessageRenameMediaFolder'),
|
||||
|
|
|
@ -181,7 +181,7 @@ import alert from '../../components/alert';
|
|||
}
|
||||
});
|
||||
view.querySelector('#btnSelectCertPath').addEventListener('click', function () {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
|
|
198
src/controllers/dashboard/users/useredit.html
Normal file
198
src/controllers/dashboard/users/useredit.html
Normal file
|
@ -0,0 +1,198 @@
|
|||
<div id="editUserPage" data-role="page" class="page type-interior">
|
||||
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer flex align-items-center">
|
||||
<h2 class="sectionTitle username"></h2>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/">${Help}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" id="userProfileNavigation" data-mini="true">
|
||||
<a href="#" is="emby-linkbutton" data-role="button" class="ui-btn-active">${Profile}</a>
|
||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
|
||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
|
||||
<a href="#" is="emby-linkbutton" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
||||
</div>
|
||||
<p class="lnkEditUserPreferencesContainer">
|
||||
<a class="lnkEditUserPreferences button-link" href="#" is="emby-linkbutton">${ButtonEditOtherUserPreferences}</a>
|
||||
</p>
|
||||
<form class="editUserProfileForm">
|
||||
|
||||
<div class="disabledUserBanner" style="display: none;">
|
||||
<div class="btn btnDarkAccent btnStatic">
|
||||
<div>
|
||||
${HeaderThisUserIsCurrentlyDisabled}
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
${MessageReenableUser}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="fldUserName" class="inputContainer">
|
||||
<input is="emby-input" id="txtUserName" required type="text" label="${LabelName}" />
|
||||
</div>
|
||||
|
||||
<div class="selectContainer fldSelectLoginProvider hide">
|
||||
<select class="selectLoginProvider" is="emby-select" label="${LabelAuthProvider}"></select>
|
||||
<div class="fieldDescription">${AuthProviderHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer fldSelectPasswordResetProvider hide">
|
||||
<select class="selectPasswordResetProvider" is="emby-select" label="${LabelPasswordResetProvider}"></select>
|
||||
<div class="fieldDescription">${PasswordResetProviderHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldRemoteAccess hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkRemoteAccess" />
|
||||
<span>${AllowRemoteAccess}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${AllowRemoteAccessHelp}</div>
|
||||
</div>
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkIsAdmin" />
|
||||
<span>${OptionAllowUserToManageServer}</span>
|
||||
</label>
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableCollectionManagement" />
|
||||
<span>${AllowCollectionManagement}</span>
|
||||
</label>
|
||||
<div id="featureAccessFields" class="verticalSection">
|
||||
<h2 class="paperListLabel">${HeaderFeatureAccess}</h2>
|
||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableLiveTvAccess" />
|
||||
<span>${OptionAllowBrowsingLiveTv}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkManageLiveTv" />
|
||||
<span>${OptionAllowManageLiveTv}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<h2 class="paperListLabel">${HeaderPlayback}</h2>
|
||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableMediaPlayback" />
|
||||
<span>${OptionAllowMediaPlayback}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAudioPlaybackTranscoding" />
|
||||
<span>${OptionAllowAudioPlaybackTranscoding}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackTranscoding" />
|
||||
<span>${OptionAllowVideoPlaybackTranscoding}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableVideoPlaybackRemuxing" />
|
||||
<span>${OptionAllowVideoPlaybackRemuxing}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkForceRemoteSourceTranscoding" />
|
||||
<span>${OptionForceRemoteSourceTranscoding}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="fieldDescription">${OptionAllowMediaPlaybackTranscodingHelp}</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="verticalSection">
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtRemoteClientBitrateLimit" inputmode="decimal" pattern="[0-9]*(\.[0-9]+)?" min="0" step=".25" label="${LabelRemoteClientBitrateLimit}" />
|
||||
<div class="fieldDescription">${LabelRemoteClientBitrateLimitHelp}</div>
|
||||
<div class="fieldDescription">${LabelUserRemoteClientBitrateLimitHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<div class="selectContainer fldSelectSyncPlayAccess">
|
||||
<select class="selectSyncPlayAccess" is="emby-select" id="selectSyncPlayAccess" label="${LabelSyncPlayAccess}">
|
||||
<option value="CreateAndJoinGroups">${LabelSyncPlayAccessCreateAndJoinGroups}</option>
|
||||
<option value="JoinGroups">${LabelSyncPlayAccessJoinGroups}</option>
|
||||
<option value="None">${LabelSyncPlayAccessNone}</option>
|
||||
</select>
|
||||
<div class="fieldDescription">${SyncPlayAccessHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<h2 class="checkboxListLabel" style="margin-bottom:1em;">${HeaderAllowMediaDeletionFrom}</h2>
|
||||
<div class="checkboxList paperList checkboxList-paperList">
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableDeleteAllFolders" />
|
||||
<span>${AllLibraries}</span>
|
||||
</label>
|
||||
<div class="deleteAccess">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="verticalSection">
|
||||
<h2 class="checkboxListLabel">${HeaderRemoteControl}</h2>
|
||||
<div class="checkboxList paperList" style="padding:.5em 1em;">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableRemoteControlOtherUsers" />
|
||||
<span>${OptionAllowRemoteControlOthers}</span>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkRemoteControlSharedDevices" />
|
||||
<span>${OptionAllowRemoteSharedDevices}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="fieldDescription">${OptionAllowRemoteSharedDevicesHelp}</div>
|
||||
</div>
|
||||
<h2 class="checkboxListLabel">${Other}</h2>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableDownloading" />
|
||||
<span>${OptionAllowContentDownload}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${OptionAllowContentDownloadHelp}</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsEnabled">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkDisabled" />
|
||||
<span>${OptionDisableUser}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${OptionDisableUserHelp}</div>
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription" id="fldIsHidden">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkIsHidden" />
|
||||
<span>${OptionHideUser}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${OptionHideUserFromLoginHelp}</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class=verticalSection>
|
||||
<div class="inputContainer" id="fldLoginAttemptsBeforeLockout">
|
||||
<input is="emby-input" type="number" id="txtLoginAttemptsBeforeLockout" min="-1" step="1" label="${LabelUserLoginAttemptsBeforeLockout}"/>
|
||||
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockout}</div>
|
||||
<div class="fieldDescription">${OptionLoginAttemptsBeforeLockoutHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class=verticalSection>
|
||||
<div class="inputContainer" id="fldMaxActiveSessions">
|
||||
<input is="emby-input" type="number" id="txtMaxActiveSessions" min="0" step="1" label="${LabelUserMaxActiveSessions}"/>
|
||||
<div class="fieldDescription">${OptionMaxActiveSessions}</div>
|
||||
<div class="fieldDescription">${OptionMaxActiveSessionsHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
|
||||
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
|
||||
<span>${ButtonCancel}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
196
src/controllers/dashboard/users/useredit.js
Normal file
196
src/controllers/dashboard/users/useredit.js
Normal file
|
@ -0,0 +1,196 @@
|
|||
import 'jquery';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import libraryMenu from '../../../scripts/libraryMenu';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import { getParameterByName } from '../../../utils/url.ts';
|
||||
|
||||
function loadDeleteFolders(page, user, mediaFolders) {
|
||||
ApiClient.getJSON(ApiClient.getUrl('Channels', {
|
||||
SupportsMediaDeletion: true
|
||||
})).then(function (channelsResult) {
|
||||
let isChecked;
|
||||
let checkedAttribute;
|
||||
let html = '';
|
||||
|
||||
for (const folder of mediaFolders) {
|
||||
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
for (const folder of channelsResult.Items) {
|
||||
isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1;
|
||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
$('.deleteAccess', page).html(html).trigger('create');
|
||||
$('#chkEnableDeleteAllFolders', page).prop('checked', user.Policy.EnableContentDeletion);
|
||||
});
|
||||
}
|
||||
|
||||
function loadAuthProviders(page, user, providers) {
|
||||
if (providers.length > 1) {
|
||||
page.querySelector('.fldSelectLoginProvider').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.fldSelectLoginProvider').classList.add('hide');
|
||||
}
|
||||
|
||||
const currentProviderId = user.Policy.AuthenticationProviderId;
|
||||
page.querySelector('.selectLoginProvider').innerHTML = providers.map(function (provider) {
|
||||
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
|
||||
return '<option value="' + provider.Id + '"' + selected + '>' + provider.Name + '</option>';
|
||||
});
|
||||
}
|
||||
|
||||
function loadPasswordResetProviders(page, user, providers) {
|
||||
if (providers.length > 1) {
|
||||
page.querySelector('.fldSelectPasswordResetProvider').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.fldSelectPasswordResetProvider').classList.add('hide');
|
||||
}
|
||||
|
||||
const currentProviderId = user.Policy.PasswordResetProviderId;
|
||||
page.querySelector('.selectPasswordResetProvider').innerHTML = providers.map(function (provider) {
|
||||
const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : '';
|
||||
return '<option value="' + provider.Id + '"' + selected + '>' + provider.Name + '</option>';
|
||||
});
|
||||
}
|
||||
|
||||
function loadUser(page, user) {
|
||||
ApiClient.getJSON(ApiClient.getUrl('Auth/Providers')).then(function (providers) {
|
||||
loadAuthProviders(page, user, providers);
|
||||
});
|
||||
ApiClient.getJSON(ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) {
|
||||
loadPasswordResetProviders(page, user, providers);
|
||||
});
|
||||
ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', {
|
||||
IsHidden: false
|
||||
})).then(function (folders) {
|
||||
loadDeleteFolders(page, user, folders.Items);
|
||||
});
|
||||
|
||||
if (user.Policy.IsDisabled) {
|
||||
$('.disabledUserBanner', page).show();
|
||||
} else {
|
||||
$('.disabledUserBanner', page).hide();
|
||||
}
|
||||
|
||||
$('#txtUserName', page).prop('disabled', '').removeAttr('disabled');
|
||||
$('#fldConnectInfo', page).show();
|
||||
$('.lnkEditUserPreferences', page).attr('href', 'mypreferencesmenu.html?userId=' + user.Id);
|
||||
libraryMenu.setTitle(user.Name);
|
||||
page.querySelector('.username').innerHTML = user.Name;
|
||||
$('#txtUserName', page).val(user.Name);
|
||||
$('#chkIsAdmin', page).prop('checked', user.Policy.IsAdministrator);
|
||||
$('#chkDisabled', page).prop('checked', user.Policy.IsDisabled);
|
||||
$('#chkIsHidden', page).prop('checked', user.Policy.IsHidden);
|
||||
$('#chkEnableCollectionManagement', page).prop('checked', user.Policy.chkEnableCollectionManagement);
|
||||
$('#chkRemoteControlSharedDevices', page).prop('checked', user.Policy.EnableSharedDeviceControl);
|
||||
$('#chkEnableRemoteControlOtherUsers', page).prop('checked', user.Policy.EnableRemoteControlOfOtherUsers);
|
||||
$('#chkEnableDownloading', page).prop('checked', user.Policy.EnableContentDownloading);
|
||||
$('#chkManageLiveTv', page).prop('checked', user.Policy.EnableLiveTvManagement);
|
||||
$('#chkEnableLiveTvAccess', page).prop('checked', user.Policy.EnableLiveTvAccess);
|
||||
$('#chkEnableMediaPlayback', page).prop('checked', user.Policy.EnableMediaPlayback);
|
||||
$('#chkEnableAudioPlaybackTranscoding', page).prop('checked', user.Policy.EnableAudioPlaybackTranscoding);
|
||||
$('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding);
|
||||
$('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing);
|
||||
$('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding);
|
||||
$('#chkRemoteAccess', page).prop('checked', user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess);
|
||||
$('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || '');
|
||||
$('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0');
|
||||
$('#txtMaxActiveSessions', page).val(user.Policy.MaxActiveSessions || '0');
|
||||
if (ApiClient.isMinServerVersion('10.6.0')) {
|
||||
$('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess);
|
||||
}
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function onSaveComplete() {
|
||||
Dashboard.navigate('userprofiles.html');
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
}
|
||||
|
||||
function saveUser(user, page) {
|
||||
user.Name = $('#txtUserName', page).val();
|
||||
user.Policy.IsAdministrator = $('#chkIsAdmin', page).is(':checked');
|
||||
user.Policy.IsHidden = $('#chkIsHidden', page).is(':checked');
|
||||
user.Policy.IsDisabled = $('#chkDisabled', page).is(':checked');
|
||||
user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).is(':checked');
|
||||
user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).is(':checked');
|
||||
user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).is(':checked');
|
||||
user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).is(':checked');
|
||||
user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).is(':checked');
|
||||
user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).is(':checked');
|
||||
user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).is(':checked');
|
||||
user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).is(':checked');
|
||||
user.Policy.EnableCollectionManagement = $('#chkEnableCollectionManagement', page).is(':checked');
|
||||
user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked');
|
||||
user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked');
|
||||
user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked');
|
||||
user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0'), 10);
|
||||
user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0', 10);
|
||||
user.Policy.MaxActiveSessions = parseInt($('#txtMaxActiveSessions', page).val() || '0', 10);
|
||||
user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value;
|
||||
user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value;
|
||||
user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).is(':checked');
|
||||
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : $('.chkFolder', page).get().filter(function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
if (ApiClient.isMinServerVersion('10.6.0')) {
|
||||
user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value;
|
||||
}
|
||||
ApiClient.updateUser(user).then(function () {
|
||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
onSaveComplete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
const page = $(this).parents('.page')[0];
|
||||
loading.show();
|
||||
getUser().then(function (result) {
|
||||
saveUser(result, page);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function getUser() {
|
||||
const userId = getParameterByName('userId');
|
||||
return ApiClient.getUser(userId);
|
||||
}
|
||||
|
||||
function loadData(page) {
|
||||
loading.show();
|
||||
getUser().then(function (user) {
|
||||
loadUser(page, user);
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on('pageinit', '#editUserPage', function () {
|
||||
$('.editUserProfileForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
const page = this;
|
||||
$('#chkEnableDeleteAllFolders', this).on('change', function () {
|
||||
if (this.checked) {
|
||||
$('.deleteAccess', page).hide();
|
||||
} else {
|
||||
$('.deleteAccess', page).show();
|
||||
}
|
||||
});
|
||||
ApiClient.getServerConfiguration().then(function (config) {
|
||||
if (config.EnableRemoteAccess) {
|
||||
page.querySelector('.fldRemoteAccess').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.fldRemoteAccess').classList.add('hide');
|
||||
}
|
||||
});
|
||||
}).on('pagebeforeshow', '#editUserPage', function () {
|
||||
loadData(this);
|
||||
});
|
||||
|
68
src/controllers/dashboard/users/userlibraryaccess.html
Normal file
68
src/controllers/dashboard/users/userlibraryaccess.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
<div id="userLibraryAccessPage" data-role="page" class="page type-interior">
|
||||
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer flex align-items-center">
|
||||
<h2 class="sectionTitle username"></h2>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/">${Help}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${Profile}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);" class="ui-btn-active">${TabAccess}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
||||
</div>
|
||||
<form class="userLibraryAccessForm">
|
||||
|
||||
<div class="folderAccessContainer">
|
||||
<h2>${HeaderLibraryAccess}</h2>
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllFolders" />
|
||||
<span>${OptionEnableAccessToAllLibraries}</span>
|
||||
</label>
|
||||
<div class="folderAccessListContainer">
|
||||
<div class="folderAccess">
|
||||
</div>
|
||||
<div class="fieldDescription">${LibraryAccessHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="channelAccessContainer" style="display:none;">
|
||||
<h2>${HeaderChannelAccess}</h2>
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllChannels" />
|
||||
<span>${OptionEnableAccessToAllChannels}</span>
|
||||
</label>
|
||||
<div class="channelAccessListContainer">
|
||||
<div class="channelAccess">
|
||||
</div>
|
||||
<div class="fieldDescription">${ChannelAccessHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="deviceAccessContainer hide">
|
||||
<h2>${HeaderDeviceAccess}</h2>
|
||||
<label class="checkboxContainer">
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllDevices" />
|
||||
<span>${OptionEnableAccessFromAllDevices}</span>
|
||||
</label>
|
||||
<div class="deviceAccessListContainer">
|
||||
<div class="deviceAccess">
|
||||
</div>
|
||||
<div class="fieldDescription">${DeviceAccessHelp}</div>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
184
src/controllers/dashboard/users/userlibraryaccess.js
Normal file
184
src/controllers/dashboard/users/userlibraryaccess.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
import 'jquery';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import libraryMenu from '../../../scripts/libraryMenu';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import { getParameterByName } from '../../../utils/url.ts';
|
||||
|
||||
function triggerChange(select) {
|
||||
const evt = document.createEvent('HTMLEvents');
|
||||
evt.initEvent('change', false, true);
|
||||
select.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
function loadMediaFolders(page, user, mediaFolders) {
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderLibraries') + '</h3>';
|
||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
||||
|
||||
for (let i = 0, length = mediaFolders.length; i < length; i++) {
|
||||
const folder = mediaFolders[i];
|
||||
const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1;
|
||||
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
page.querySelector('.folderAccess').innerHTML = html;
|
||||
const chkEnableAllFolders = page.querySelector('#chkEnableAllFolders');
|
||||
chkEnableAllFolders.checked = user.Policy.EnableAllFolders;
|
||||
triggerChange(chkEnableAllFolders);
|
||||
}
|
||||
|
||||
function loadChannels(page, user, channels) {
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('Channels') + '</h3>';
|
||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
||||
|
||||
for (let i = 0, length = channels.length; i < length; i++) {
|
||||
const folder = channels[i];
|
||||
const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1;
|
||||
const checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkChannel" data-id="' + folder.Id + '" ' + checkedAttribute + '><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$('.channelAccess', page).show().html(html);
|
||||
|
||||
if (channels.length) {
|
||||
$('.channelAccessContainer', page).show();
|
||||
} else {
|
||||
$('.channelAccessContainer', page).hide();
|
||||
}
|
||||
|
||||
const chkEnableAllChannels = page.querySelector('#chkEnableAllChannels');
|
||||
chkEnableAllChannels.checked = user.Policy.EnableAllChannels;
|
||||
triggerChange(chkEnableAllChannels);
|
||||
}
|
||||
|
||||
function loadDevices(page, user, devices) {
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderDevices') + '</h3>';
|
||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
||||
|
||||
for (let i = 0, length = devices.length; i < length; i++) {
|
||||
const device = devices[i];
|
||||
const checkedAttribute = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1 ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkDevice" data-id="' + device.Id + '" ' + checkedAttribute + '><span>' + device.Name + ' - ' + device.AppName + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$('.deviceAccess', page).show().html(html);
|
||||
const chkEnableAllDevices = page.querySelector('#chkEnableAllDevices');
|
||||
chkEnableAllDevices.checked = user.Policy.EnableAllDevices;
|
||||
triggerChange(chkEnableAllDevices);
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
page.querySelector('.deviceAccessContainer').classList.add('hide');
|
||||
} else {
|
||||
page.querySelector('.deviceAccessContainer').classList.remove('hide');
|
||||
}
|
||||
}
|
||||
|
||||
function loadUser(page, user, loggedInUser, mediaFolders, channels, devices) {
|
||||
page.querySelector('.username').innerHTML = user.Name;
|
||||
libraryMenu.setTitle(user.Name);
|
||||
loadChannels(page, user, channels);
|
||||
loadMediaFolders(page, user, mediaFolders);
|
||||
loadDevices(page, user, devices);
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function onSaveComplete() {
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
}
|
||||
|
||||
function saveUser(user, page) {
|
||||
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked');
|
||||
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : $('.chkFolder', page).get().filter(function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked');
|
||||
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : $('.chkChannel', page).get().filter(function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).is(':checked');
|
||||
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $('.chkDevice', page).get().filter(function (c) {
|
||||
return c.checked;
|
||||
}).map(function (c) {
|
||||
return c.getAttribute('data-id');
|
||||
});
|
||||
user.Policy.BlockedChannels = null;
|
||||
user.Policy.BlockedMediaFolders = null;
|
||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
onSaveComplete();
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
const page = $(this).parents('.page');
|
||||
loading.show();
|
||||
const userId = getParameterByName('userId');
|
||||
ApiClient.getUser(userId).then(function (result) {
|
||||
saveUser(result, page);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
$(document).on('pageinit', '#userLibraryAccessPage', function () {
|
||||
const page = this;
|
||||
$('#chkEnableAllDevices', page).on('change', function () {
|
||||
if (this.checked) {
|
||||
$('.deviceAccessListContainer', page).hide();
|
||||
} else {
|
||||
$('.deviceAccessListContainer', page).show();
|
||||
}
|
||||
});
|
||||
$('#chkEnableAllChannels', page).on('change', function () {
|
||||
if (this.checked) {
|
||||
$('.channelAccessListContainer', page).hide();
|
||||
} else {
|
||||
$('.channelAccessListContainer', page).show();
|
||||
}
|
||||
});
|
||||
page.querySelector('#chkEnableAllFolders').addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
page.querySelector('.folderAccessListContainer').classList.add('hide');
|
||||
} else {
|
||||
page.querySelector('.folderAccessListContainer').classList.remove('hide');
|
||||
}
|
||||
});
|
||||
$('.userLibraryAccessForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
}).on('pageshow', '#userLibraryAccessPage', function () {
|
||||
const page = this;
|
||||
loading.show();
|
||||
let promise1;
|
||||
const userId = getParameterByName('userId');
|
||||
|
||||
if (userId) {
|
||||
promise1 = ApiClient.getUser(userId);
|
||||
} else {
|
||||
const deferred = $.Deferred();
|
||||
deferred.resolveWith(null, [{
|
||||
Configuration: {}
|
||||
}]);
|
||||
promise1 = deferred.promise();
|
||||
}
|
||||
|
||||
const promise2 = Dashboard.getCurrentUser();
|
||||
const promise4 = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', {
|
||||
IsHidden: false
|
||||
}));
|
||||
const promise5 = ApiClient.getJSON(ApiClient.getUrl('Channels'));
|
||||
const promise6 = ApiClient.getJSON(ApiClient.getUrl('Devices'));
|
||||
Promise.all([promise1, promise2, promise4, promise5, promise6]).then(function (responses) {
|
||||
loadUser(page, responses[0], responses[1], responses[2].Items, responses[3].Items, responses[4].Items);
|
||||
});
|
||||
});
|
||||
|
62
src/controllers/dashboard/users/usernew.html
Normal file
62
src/controllers/dashboard/users/usernew.html
Normal file
|
@ -0,0 +1,62 @@
|
|||
<div id="newUserPage" data-role="page" class="page type-interior">
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
<form class="newUserProfileForm">
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer flex align-items-center">
|
||||
<h2 class="sectionTitle">${ButtonAddUser}</h2>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/">${Help}</a>
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtUsername" required type="text" label="${LabelName}" />
|
||||
</div>
|
||||
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" id="txtPassword" type="password" label="${LabelPassword}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="folderAccessContainer verticalSection">
|
||||
<h2 class="sectionTitle">${HeaderLibraryAccess}</h2>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllFolders" />
|
||||
<span>${OptionEnableAccessToAllLibraries}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LibraryAccessHelp}</div>
|
||||
</div>
|
||||
<div class="folderAccessListContainer">
|
||||
<div class="folderAccess">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="channelAccessContainer verticalSection verticalSection-extrabottompadding" style="display:none;">
|
||||
<h2 class="sectionTitle">${HeaderChannelAccess}</h2>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkEnableAllChannels" />
|
||||
<span>${OptionEnableAccessToAllChannels}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${ChannelAccessHelp}</div>
|
||||
</div>
|
||||
<div class="channelAccessListContainer">
|
||||
<div class="channelAccess">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
|
||||
<button is="emby-button" type="button" class="raised button-cancel block btnCancel" onclick="history.back();">
|
||||
<span>${ButtonCancel}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
128
src/controllers/dashboard/users/usernew.js
Normal file
128
src/controllers/dashboard/users/usernew.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
import 'jquery';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import '../../../elements/emby-checkbox/emby-checkbox';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import toast from '../../../components/toast/toast';
|
||||
|
||||
function loadMediaFolders(page, mediaFolders) {
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderLibraries') + '</h3>';
|
||||
html += '<div class="checkboxList paperList" style="padding:.5em 1em;">';
|
||||
|
||||
for (let i = 0; i < mediaFolders.length; i++) {
|
||||
const folder = mediaFolders[i];
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkFolder" data-id="' + folder.Id + '"/><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$('.folderAccess', page).html(html).trigger('create');
|
||||
$('#chkEnableAllFolders', page).prop('checked', false);
|
||||
}
|
||||
|
||||
function loadChannels(page, channels) {
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('Channels') + '</h3>';
|
||||
html += '<div class="checkboxList paperList" style="padding:.5em 1em;">';
|
||||
|
||||
for (let i = 0; i < channels.length; i++) {
|
||||
const folder = channels[i];
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkChannel" data-id="' + folder.Id + '"/><span>' + folder.Name + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$('.channelAccess', page).show().html(html).trigger('create');
|
||||
|
||||
if (channels.length) {
|
||||
$('.channelAccessContainer', page).show();
|
||||
} else {
|
||||
$('.channelAccessContainer', page).hide();
|
||||
}
|
||||
|
||||
$('#chkEnableAllChannels', page).prop('checked', false);
|
||||
}
|
||||
|
||||
function loadUser(page) {
|
||||
$('#txtUsername', page).val('');
|
||||
$('#txtPassword', page).val('');
|
||||
loading.show();
|
||||
const promiseFolders = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', {
|
||||
IsHidden: false
|
||||
}));
|
||||
const promiseChannels = ApiClient.getJSON(ApiClient.getUrl('Channels'));
|
||||
Promise.all([promiseFolders, promiseChannels]).then(function (responses) {
|
||||
loadMediaFolders(page, responses[0].Items);
|
||||
loadChannels(page, responses[1].Items);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function saveUser(page) {
|
||||
const _user = {
|
||||
Name: $('#txtUsername', page).val(),
|
||||
Password: $('#txtPassword', page).val()
|
||||
};
|
||||
ApiClient.createUser(_user).then(function (user) {
|
||||
user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked');
|
||||
user.Policy.EnabledFolders = [];
|
||||
|
||||
if (!user.Policy.EnableAllFolders) {
|
||||
user.Policy.EnabledFolders = $('.chkFolder', page).get().filter(function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-id');
|
||||
});
|
||||
}
|
||||
|
||||
user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked');
|
||||
user.Policy.EnabledChannels = [];
|
||||
|
||||
if (!user.Policy.EnableAllChannels) {
|
||||
user.Policy.EnabledChannels = $('.chkChannel', page).get().filter(function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-id');
|
||||
});
|
||||
}
|
||||
|
||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
Dashboard.navigate('useredit.html?userId=' + user.Id);
|
||||
});
|
||||
}, function () {
|
||||
toast(globalize.translate('ErrorDefault'));
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
const page = $(this).parents('.page')[0];
|
||||
loading.show();
|
||||
saveUser(page);
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadData(page) {
|
||||
loadUser(page);
|
||||
}
|
||||
|
||||
$(document).on('pageinit', '#newUserPage', function () {
|
||||
const page = this;
|
||||
$('#chkEnableAllChannels', page).on('change', function () {
|
||||
if (this.checked) {
|
||||
$('.channelAccessListContainer', page).hide();
|
||||
} else {
|
||||
$('.channelAccessListContainer', page).show();
|
||||
}
|
||||
});
|
||||
$('#chkEnableAllFolders', page).on('change', function () {
|
||||
if (this.checked) {
|
||||
$('.folderAccessListContainer', page).hide();
|
||||
} else {
|
||||
$('.folderAccessListContainer', page).show();
|
||||
}
|
||||
});
|
||||
$('.newUserProfileForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
}).on('pageshow', '#newUserPage', function () {
|
||||
loadData(this);
|
||||
});
|
||||
|
60
src/controllers/dashboard/users/userparentalcontrol.html
Normal file
60
src/controllers/dashboard/users/userparentalcontrol.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<div id="userParentalControlPage" data-role="page" class="page type-interior">
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer flex align-items-center">
|
||||
<h2 class="sectionTitle username"></h2>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/">${Help}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${Profile}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);" class="ui-btn-active">${TabParentalControl}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);">${HeaderPassword}</a>
|
||||
</div>
|
||||
|
||||
<form class="userParentalControlForm">
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectMaxParentalRating" label="${LabelMaxParentalRating}"></select>
|
||||
<div class="fieldDescription">${MaxParentalRatingHelp}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="blockUnratedItems"></div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="verticalSection" style="margin-bottom:2em;">
|
||||
<div class="detailSectionHeader sectionTitleContainer">
|
||||
<h2 class="sectionTitle">${LabelBlockContentWithTags}</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddBlockedTag submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="blockedTags" style="margin-top:.5em;"></div>
|
||||
</div>
|
||||
|
||||
<div class="accessScheduleSection verticalSection" style="margin-bottom:2em;">
|
||||
<div class="sectionTitleContainer">
|
||||
<h2 class="sectionTitle">${HeaderAccessSchedule}</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddSchedule submit" style="margin-left:1em;" title="${Add}">
|
||||
<span class="material-icons add"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p>${HeaderAccessScheduleHelp}</p>
|
||||
<div class="accessScheduleList paperList"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
278
src/controllers/dashboard/users/userparentalcontrol.js
Normal file
278
src/controllers/dashboard/users/userparentalcontrol.js
Normal file
|
@ -0,0 +1,278 @@
|
|||
import 'jquery';
|
||||
import datetime from '../../../scripts/datetime';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import libraryMenu from '../../../scripts/libraryMenu';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import '../../../components/listview/listview.scss';
|
||||
import '../../../elements/emby-button/paper-icon-button-light';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import { getParameterByName } from '../../../utils/url.ts';
|
||||
|
||||
function populateRatings(allParentalRatings, page) {
|
||||
let html = '';
|
||||
html += "<option value=''></option>";
|
||||
let rating;
|
||||
const ratings = [];
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
for (let i = 0, length = ratings.length; i < length; i++) {
|
||||
rating = ratings[i];
|
||||
html += "<option value='" + rating.Value + "'>" + rating.Name + '</option>';
|
||||
}
|
||||
|
||||
$('#selectMaxParentalRating', page).html(html);
|
||||
}
|
||||
|
||||
function loadUnratedItems(page, user) {
|
||||
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'
|
||||
}];
|
||||
let html = '';
|
||||
html += '<h3 class="checkboxListLabel">' + globalize.translate('HeaderBlockItemsWithNoRating') + '</h3>';
|
||||
html += '<div class="checkboxList paperList checkboxList-paperList">';
|
||||
|
||||
for (let i = 0, length = items.length; i < length; i++) {
|
||||
const item = items[i];
|
||||
const checkedAttribute = user.Policy.BlockUnratedItems.indexOf(item.value) != -1 ? ' checked="checked"' : '';
|
||||
html += '<label><input type="checkbox" is="emby-checkbox" class="chkUnratedItem" data-itemtype="' + item.value + '" type="checkbox"' + checkedAttribute + '><span>' + item.name + '</span></label>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
$('.blockUnratedItems', page).html(html).trigger('create');
|
||||
}
|
||||
|
||||
function loadUser(page, user, allParentalRatings) {
|
||||
page.querySelector('.username').innerHTML = user.Name;
|
||||
libraryMenu.setTitle(user.Name);
|
||||
loadUnratedItems(page, user);
|
||||
loadBlockedTags(page, user.Policy.BlockedTags);
|
||||
populateRatings(allParentalRatings, page);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$('#selectMaxParentalRating', page).val(ratingValue);
|
||||
|
||||
if (user.Policy.IsAdministrator) {
|
||||
$('.accessScheduleSection', page).hide();
|
||||
} else {
|
||||
$('.accessScheduleSection', page).show();
|
||||
}
|
||||
|
||||
renderAccessSchedule(page, user.Policy.AccessSchedules || []);
|
||||
loading.hide();
|
||||
}
|
||||
|
||||
function loadBlockedTags(page, tags) {
|
||||
let html = tags.map(function (h) {
|
||||
let li = '<div class="listItem">';
|
||||
li += '<div class="listItemBody">';
|
||||
li += '<h3 class="listItemBodyText">';
|
||||
li += h;
|
||||
li += '</h3>';
|
||||
li += '</div>';
|
||||
li += '<button type="button" is="paper-icon-button-light" class="blockedTag btnDeleteTag listItemButton" data-tag="' + h + '"><span class="material-icons delete"></span></button>';
|
||||
li += '</div>';
|
||||
return li;
|
||||
}).join('');
|
||||
|
||||
if (html) {
|
||||
html = '<div class="paperList">' + html + '</div>';
|
||||
}
|
||||
|
||||
const blockedTags = page.querySelector('.blockedTags');
|
||||
blockedTags.innerHTML = html;
|
||||
|
||||
for (const btnDeleteTag of blockedTags.querySelectorAll('.btnDeleteTag')) {
|
||||
btnDeleteTag.addEventListener('click', function () {
|
||||
const tag = this.getAttribute('data-tag');
|
||||
const newTags = tags.filter(function (t) {
|
||||
return t != tag;
|
||||
});
|
||||
loadBlockedTags(page, newTags);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAccessSchedule(page, schedules, index) {
|
||||
schedules.splice(index, 1);
|
||||
renderAccessSchedule(page, schedules);
|
||||
}
|
||||
|
||||
function renderAccessSchedule(page, schedules) {
|
||||
let html = '';
|
||||
let index = 0;
|
||||
html += schedules.map(function (a) {
|
||||
let itemHtml = '';
|
||||
itemHtml += '<div class="liSchedule listItem" data-day="' + a.DayOfWeek + '" data-start="' + a.StartHour + '" data-end="' + a.EndHour + '">';
|
||||
itemHtml += '<div class="listItemBody two-line">';
|
||||
itemHtml += '<h3 class="listItemBodyText">';
|
||||
itemHtml += globalize.translate('Option' + a.DayOfWeek);
|
||||
itemHtml += '</h3>';
|
||||
itemHtml += '<div class="listItemBodyText secondary">' + getDisplayTime(a.StartHour) + ' - ' + getDisplayTime(a.EndHour) + '</div>';
|
||||
itemHtml += '</div>';
|
||||
itemHtml += '<button type="button" is="paper-icon-button-light" class="btnDelete listItemButton" data-index="' + index + '"><span class="material-icons delete"></span></button>';
|
||||
itemHtml += '</div>';
|
||||
index++;
|
||||
return itemHtml;
|
||||
}).join('');
|
||||
const accessScheduleList = page.querySelector('.accessScheduleList');
|
||||
accessScheduleList.innerHTML = html;
|
||||
$('.btnDelete', accessScheduleList).on('click', function () {
|
||||
deleteAccessSchedule(page, schedules, parseInt(this.getAttribute('data-index'), 10));
|
||||
});
|
||||
}
|
||||
|
||||
function onSaveComplete() {
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
}
|
||||
|
||||
function saveUser(user, page) {
|
||||
user.Policy.MaxParentalRating = $('#selectMaxParentalRating', page).val() || null;
|
||||
user.Policy.BlockUnratedItems = $('.chkUnratedItem', page).get().filter(function (i) {
|
||||
return i.checked;
|
||||
}).map(function (i) {
|
||||
return i.getAttribute('data-itemtype');
|
||||
});
|
||||
user.Policy.AccessSchedules = getSchedulesFromPage(page);
|
||||
user.Policy.BlockedTags = getBlockedTagsFromPage(page);
|
||||
ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
||||
onSaveComplete();
|
||||
});
|
||||
}
|
||||
|
||||
function getDisplayTime(hours) {
|
||||
let minutes = 0;
|
||||
const pct = hours % 1;
|
||||
|
||||
if (pct) {
|
||||
minutes = parseInt(60 * pct, 10);
|
||||
}
|
||||
|
||||
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
|
||||
}
|
||||
|
||||
function showSchedulePopup(page, schedule, index) {
|
||||
schedule = schedule || {};
|
||||
import('../../../components/accessSchedule/accessSchedule').then(({ default: accessschedule }) => {
|
||||
accessschedule.show({
|
||||
schedule: schedule
|
||||
}).then(function (updatedSchedule) {
|
||||
const schedules = getSchedulesFromPage(page);
|
||||
|
||||
if (index == -1) {
|
||||
index = schedules.length;
|
||||
}
|
||||
|
||||
schedules[index] = updatedSchedule;
|
||||
renderAccessSchedule(page, schedules);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getSchedulesFromPage(page) {
|
||||
return $('.liSchedule', page).map(function () {
|
||||
return {
|
||||
DayOfWeek: this.getAttribute('data-day'),
|
||||
StartHour: this.getAttribute('data-start'),
|
||||
EndHour: this.getAttribute('data-end')
|
||||
};
|
||||
}).get();
|
||||
}
|
||||
|
||||
function getBlockedTagsFromPage(page) {
|
||||
return $('.blockedTag', page).map(function () {
|
||||
return this.getAttribute('data-tag');
|
||||
}).get();
|
||||
}
|
||||
|
||||
function showBlockedTagPopup(page) {
|
||||
import('../../../components/prompt/prompt').then(({ default: prompt }) => {
|
||||
prompt({
|
||||
label: globalize.translate('LabelTag')
|
||||
}).then(function (value) {
|
||||
const tags = getBlockedTagsFromPage(page);
|
||||
|
||||
if (tags.indexOf(value) == -1) {
|
||||
tags.push(value);
|
||||
loadBlockedTags(page, tags);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.UserParentalControlPage = {
|
||||
onSubmit: function () {
|
||||
const page = $(this).parents('.page');
|
||||
loading.show();
|
||||
const userId = getParameterByName('userId');
|
||||
ApiClient.getUser(userId).then(function (result) {
|
||||
saveUser(result, page);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
$(document).on('pageinit', '#userParentalControlPage', function () {
|
||||
const page = this;
|
||||
$('.btnAddSchedule', page).on('click', function () {
|
||||
showSchedulePopup(page, {}, -1);
|
||||
});
|
||||
$('.btnAddBlockedTag', page).on('click', function () {
|
||||
showBlockedTagPopup(page);
|
||||
});
|
||||
$('.userParentalControlForm').off('submit', UserParentalControlPage.onSubmit).on('submit', UserParentalControlPage.onSubmit);
|
||||
}).on('pageshow', '#userParentalControlPage', function () {
|
||||
const page = this;
|
||||
loading.show();
|
||||
const userId = getParameterByName('userId');
|
||||
const promise1 = ApiClient.getUser(userId);
|
||||
const promise2 = ApiClient.getParentalRatings();
|
||||
Promise.all([promise1, promise2]).then(function (responses) {
|
||||
loadUser(page, responses[0], responses[1]);
|
||||
});
|
||||
});
|
||||
|
72
src/controllers/dashboard/users/userpassword.html
Normal file
72
src/controllers/dashboard/users/userpassword.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<div id="userPasswordPage" data-role="page" class="page type-interior userPasswordPage">
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer flex align-items-center">
|
||||
<h2 class="sectionTitle username"></h2>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/">${Help}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-role="controlgroup" data-type="horizontal" class="localnav" data-mini="true">
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('useredit.html', true);">${Profile}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userlibraryaccess.html', true);">${TabAccess}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userparentalcontrol.html', true);">${TabParentalControl}</a>
|
||||
<a is="emby-linkbutton" href="#" data-role="button" onclick="Dashboard.navigate('userpassword.html', true);" class="ui-btn-active">${HeaderPassword}</a>
|
||||
</div>
|
||||
|
||||
<div class="readOnlyContent">
|
||||
<form class="updatePasswordForm passwordSection hide" style="margin: 0 auto 2em;">
|
||||
<div class="detailSection">
|
||||
<div id="fldCurrentPassword" class="inputContainer hide">
|
||||
<input is="emby-input" type="password" id="txtCurrentPassword" label="${LabelCurrentPassword}" autocomplete="off" />
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="password" id="txtNewPassword" label="${LabelNewPassword}" autocomplete="off" />
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="password" id="txtNewPasswordConfirm" label="${LabelNewPasswordConfirm}" autocomplete="off" />
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block"><span>${Save}</span></button>
|
||||
<button is="emby-button" type="button" id="btnResetPassword" class="raised button-cancel block hide">
|
||||
<span>${ResetPassword}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<form class="localAccessForm localAccessSection" style="margin: 0 auto;">
|
||||
<div class="detailSection">
|
||||
<div class="detailSectionHeader">
|
||||
${HeaderEasyPinCode}
|
||||
</div>
|
||||
<br />
|
||||
<div>${EasyPasswordHelp}</div>
|
||||
<br />
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtEasyPassword" label="${LabelEasyPinCode}" autocomplete="off" pattern="[0-9]*" step="1" maxlength="5" />
|
||||
</div>
|
||||
<br />
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkEnableLocalEasyPassword" />
|
||||
<span>${LabelInNetworkSignInWithEasyPassword}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LabelInNetworkSignInWithEasyPasswordHelp}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnResetEasyPassword" class="raised button-cancel block hide">
|
||||
<span>${ButtonResetEasyPassword}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
179
src/controllers/dashboard/users/userpasswordpage.js
Normal file
179
src/controllers/dashboard/users/userpasswordpage.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
import loading from '../../../components/loading/loading';
|
||||
import libraryMenu from '../../../scripts/libraryMenu';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import '../../../elements/emby-button/emby-button';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import confirm from '../../../components/confirm/confirm';
|
||||
|
||||
function loadUser(page, params) {
|
||||
const userid = params.userId;
|
||||
ApiClient.getUser(userid).then(function (user) {
|
||||
Dashboard.getCurrentUser().then(function (loggedInUser) {
|
||||
libraryMenu.setTitle(user.Name);
|
||||
page.querySelector('.username').innerText = user.Name;
|
||||
let showPasswordSection = true;
|
||||
let showLocalAccessSection = false;
|
||||
|
||||
if (user.ConnectLinkType == 'Guest') {
|
||||
page.querySelector('.localAccessSection').classList.add('hide');
|
||||
showPasswordSection = false;
|
||||
} else if (user.HasConfiguredPassword) {
|
||||
page.querySelector('#btnResetPassword').classList.remove('hide');
|
||||
page.querySelector('#fldCurrentPassword').classList.remove('hide');
|
||||
showLocalAccessSection = true;
|
||||
} else {
|
||||
page.querySelector('#btnResetPassword').classList.add('hide');
|
||||
page.querySelector('#fldCurrentPassword').classList.add('hide');
|
||||
}
|
||||
|
||||
if (showPasswordSection && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
page.querySelector('.passwordSection').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.passwordSection').classList.add('hide');
|
||||
}
|
||||
|
||||
if (showLocalAccessSection && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
page.querySelector('.localAccessSection').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.localAccessSection').classList.add('hide');
|
||||
}
|
||||
|
||||
const txtEasyPassword = page.querySelector('#txtEasyPassword');
|
||||
txtEasyPassword.value = '';
|
||||
|
||||
if (user.HasConfiguredEasyPassword) {
|
||||
txtEasyPassword.placeholder = '******';
|
||||
page.querySelector('#btnResetEasyPassword').classList.remove('hide');
|
||||
} else {
|
||||
txtEasyPassword.removeAttribute('placeholder');
|
||||
txtEasyPassword.placeholder = '';
|
||||
page.querySelector('#btnResetEasyPassword').classList.add('hide');
|
||||
}
|
||||
|
||||
page.querySelector('.chkEnableLocalEasyPassword').checked = user.Configuration.EnableLocalPassword;
|
||||
|
||||
import('../../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
page.querySelector('#txtCurrentPassword').value = '';
|
||||
page.querySelector('#txtNewPassword').value = '';
|
||||
page.querySelector('#txtNewPasswordConfirm').value = '';
|
||||
}
|
||||
|
||||
export default function (view, params) {
|
||||
function saveEasyPassword() {
|
||||
const userId = params.userId;
|
||||
const easyPassword = view.querySelector('#txtEasyPassword').value;
|
||||
|
||||
if (easyPassword) {
|
||||
ApiClient.updateEasyPassword(userId, easyPassword).then(function () {
|
||||
onEasyPasswordSaved(userId);
|
||||
});
|
||||
} else {
|
||||
onEasyPasswordSaved(userId);
|
||||
}
|
||||
}
|
||||
|
||||
function onEasyPasswordSaved(userId) {
|
||||
ApiClient.getUser(userId).then(function (user) {
|
||||
user.Configuration.EnableLocalPassword = view.querySelector('.chkEnableLocalEasyPassword').checked;
|
||||
ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () {
|
||||
loading.hide();
|
||||
toast(globalize.translate('SettingsSaved'));
|
||||
|
||||
loadUser(view, params);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function savePassword() {
|
||||
const userId = params.userId;
|
||||
let currentPassword = view.querySelector('#txtCurrentPassword').value;
|
||||
const newPassword = view.querySelector('#txtNewPassword').value;
|
||||
|
||||
if (view.querySelector('#fldCurrentPassword').classList.contains('hide')) {
|
||||
// Firefox does not respect autocomplete=off, so clear it if the field is supposed to be hidden (and blank)
|
||||
// This should only happen when user.HasConfiguredPassword is false, but this information is not passed on
|
||||
currentPassword = '';
|
||||
}
|
||||
|
||||
ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () {
|
||||
loading.hide();
|
||||
toast(globalize.translate('PasswordSaved'));
|
||||
|
||||
loadUser(view, params);
|
||||
}, function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
title: globalize.translate('HeaderLoginFailure'),
|
||||
message: globalize.translate('MessageInvalidUser')
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
const form = this;
|
||||
|
||||
if (form.querySelector('#txtNewPassword').value != form.querySelector('#txtNewPasswordConfirm').value) {
|
||||
toast(globalize.translate('PasswordMatchError'));
|
||||
} else {
|
||||
loading.show();
|
||||
savePassword();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function onLocalAccessSubmit(e) {
|
||||
loading.show();
|
||||
saveEasyPassword();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
const msg = globalize.translate('PasswordResetConfirmation');
|
||||
confirm(msg, globalize.translate('ResetPassword')).then(function () {
|
||||
const userId = params.userId;
|
||||
loading.show();
|
||||
ApiClient.resetUserPassword(userId).then(function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
message: globalize.translate('PasswordResetComplete'),
|
||||
title: globalize.translate('ResetPassword')
|
||||
});
|
||||
loadUser(view, params);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetEasyPassword() {
|
||||
const msg = globalize.translate('PinCodeResetConfirmation');
|
||||
|
||||
confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () {
|
||||
const userId = params.userId;
|
||||
loading.show();
|
||||
ApiClient.resetEasyPassword(userId).then(function () {
|
||||
loading.hide();
|
||||
Dashboard.alert({
|
||||
message: globalize.translate('PinCodeResetComplete'),
|
||||
title: globalize.translate('HeaderPinCodeReset')
|
||||
});
|
||||
loadUser(view, params);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
view.querySelector('.updatePasswordForm').addEventListener('submit', onSubmit);
|
||||
view.querySelector('.localAccessForm').addEventListener('submit', onLocalAccessSubmit);
|
||||
view.querySelector('#btnResetEasyPassword').addEventListener('click', resetEasyPassword);
|
||||
view.querySelector('#btnResetPassword').addEventListener('click', resetPassword);
|
||||
view.addEventListener('viewshow', function () {
|
||||
loadUser(view, params);
|
||||
});
|
||||
}
|
||||
|
16
src/controllers/dashboard/users/userprofiles.html
Normal file
16
src/controllers/dashboard/users/userprofiles.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<div id="userProfilesPage" data-role="page" class="page type-interior userProfilesPage fullWidthContent">
|
||||
<div>
|
||||
<div class="content-primary">
|
||||
<div class="verticalSection verticalSection-extrabottompadding">
|
||||
<div class="sectionTitleContainer sectionTitleContainer-cards">
|
||||
<h2 class="sectionTitle sectionTitle-cards">${HeaderUsers}</h2>
|
||||
<button is="emby-button" type="button" class="fab btnAddUser submit sectionTitleButton" style="margin-left:1em;" title="${ButtonAddUser}">
|
||||
<span class="material-icons add"></span>
|
||||
</button>
|
||||
<a is="emby-linkbutton" rel="noopener noreferrer" style="margin-left:2em!important;" class="raised button-alt headerHelpButton" target="_blank" href="https://jellyfin.org/docs/general/server/users/adding-managing-users">${Help}</a>
|
||||
</div>
|
||||
<div class="localUsers itemsContainer vertical-wrap"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
184
src/controllers/dashboard/users/userprofilespage.js
Normal file
184
src/controllers/dashboard/users/userprofilespage.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
import loading from '../../../components/loading/loading';
|
||||
import dom from '../../../scripts/dom';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { getLocaleWithSuffix } from '../../../utils/dateFnsLocale.ts';
|
||||
import '../../../elements/emby-button/paper-icon-button-light';
|
||||
import '../../../components/cardbuilder/card.scss';
|
||||
import '../../../elements/emby-button/emby-button';
|
||||
import '../../../components/indicators/indicators.scss';
|
||||
import '../../../styles/flexstyles.scss';
|
||||
import Dashboard, { pageIdOn } from '../../../utils/dashboard';
|
||||
import confirm from '../../../components/confirm/confirm';
|
||||
import cardBuilder from '../../../components/cardbuilder/cardBuilder';
|
||||
|
||||
function deleteUser(page, id) {
|
||||
const msg = globalize.translate('DeleteUserConfirmation');
|
||||
|
||||
confirm({
|
||||
title: globalize.translate('DeleteUser'),
|
||||
text: msg,
|
||||
confirmText: globalize.translate('Delete'),
|
||||
primary: 'delete'
|
||||
}).then(function () {
|
||||
loading.show();
|
||||
ApiClient.deleteUser(id).then(function () {
|
||||
loadData(page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showUserMenu(elem) {
|
||||
const card = dom.parentWithClass(elem, 'card');
|
||||
const page = dom.parentWithClass(card, 'page');
|
||||
const userId = card.getAttribute('data-userid');
|
||||
const menuItems = [];
|
||||
menuItems.push({
|
||||
name: globalize.translate('ButtonOpen'),
|
||||
id: 'open',
|
||||
icon: 'mode_edit'
|
||||
});
|
||||
menuItems.push({
|
||||
name: globalize.translate('ButtonLibraryAccess'),
|
||||
id: 'access',
|
||||
icon: 'lock'
|
||||
});
|
||||
menuItems.push({
|
||||
name: globalize.translate('ButtonParentalControl'),
|
||||
id: 'parentalcontrol',
|
||||
icon: 'person'
|
||||
});
|
||||
menuItems.push({
|
||||
name: globalize.translate('Delete'),
|
||||
id: 'delete',
|
||||
icon: 'delete'
|
||||
});
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: card,
|
||||
callback: function (id) {
|
||||
switch (id) {
|
||||
case 'open':
|
||||
Dashboard.navigate('useredit.html?userId=' + userId);
|
||||
break;
|
||||
|
||||
case 'access':
|
||||
Dashboard.navigate('userlibraryaccess.html?userId=' + userId);
|
||||
break;
|
||||
|
||||
case 'parentalcontrol':
|
||||
Dashboard.navigate('userparentalcontrol.html?userId=' + userId);
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
deleteUser(page, userId);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUserHtml(user) {
|
||||
let html = '';
|
||||
let cssClass = 'card squareCard scalableCard squareCard-scalable';
|
||||
|
||||
if (user.Policy.IsDisabled) {
|
||||
cssClass += ' grayscale';
|
||||
}
|
||||
|
||||
html += "<div data-userid='" + user.Id + "' class='" + cssClass + "'>";
|
||||
html += '<div class="cardBox visualCardBox">';
|
||||
html += '<div class="cardScalable visualCardBox-cardScalable">';
|
||||
html += '<div class="cardPadder cardPadder-square"></div>';
|
||||
html += `<a is="emby-linkbutton" class="cardContent ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" href="#!/useredit.html?userId=${user.Id}">`;
|
||||
let imgUrl;
|
||||
|
||||
if (user.PrimaryImageTag) {
|
||||
imgUrl = ApiClient.getUserImageUrl(user.Id, {
|
||||
width: 300,
|
||||
tag: user.PrimaryImageTag,
|
||||
type: 'Primary'
|
||||
});
|
||||
}
|
||||
|
||||
let imageClass = 'cardImage';
|
||||
|
||||
if (user.Policy.IsDisabled) {
|
||||
imageClass += ' disabledUser';
|
||||
}
|
||||
|
||||
if (imgUrl) {
|
||||
html += '<div class="' + imageClass + '" style="background-image:url(\'' + imgUrl + "');\">";
|
||||
} else {
|
||||
html += `<div class="${imageClass} ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()} flex align-items-center justify-content-center">`;
|
||||
html += '<span class="material-icons cardImageIcon person"></span>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
html += '</a>';
|
||||
html += '</div>';
|
||||
html += '<div class="cardFooter visualCardBox-cardFooter">';
|
||||
html += '<div class="cardText flex align-items-center">';
|
||||
html += '<div class="flex-grow" style="overflow:hidden;text-overflow:ellipsis;">';
|
||||
html += user.Name;
|
||||
html += '</div>';
|
||||
html += '<button type="button" is="paper-icon-button-light" class="btnUserMenu flex-shrink-zero"><span class="material-icons more_vert"></span></button>';
|
||||
html += '</div>';
|
||||
html += '<div class="cardText cardText-secondary">';
|
||||
const lastSeen = getLastSeenText(user.LastActivityDate);
|
||||
html += lastSeen != '' ? lastSeen : ' ';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
return html + '</div>';
|
||||
}
|
||||
// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix
|
||||
// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences
|
||||
function getLastSeenText(lastActivityDate) {
|
||||
const localeWithSuffix = getLocaleWithSuffix();
|
||||
|
||||
if (lastActivityDate) {
|
||||
return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), localeWithSuffix));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function getUserSectionHtml(users) {
|
||||
return users.map(function (u__q) {
|
||||
return getUserHtml(u__q);
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderUsers(page, users) {
|
||||
page.querySelector('.localUsers').innerHTML = getUserSectionHtml(users);
|
||||
}
|
||||
|
||||
function loadData(page) {
|
||||
loading.show();
|
||||
ApiClient.getUsers().then(function (users) {
|
||||
renderUsers(page, users);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
pageIdOn('pageinit', 'userProfilesPage', function () {
|
||||
const page = this;
|
||||
page.querySelector('.btnAddUser').addEventListener('click', function() {
|
||||
Dashboard.navigate('usernew.html');
|
||||
});
|
||||
page.querySelector('.localUsers').addEventListener('click', function (e__e) {
|
||||
const btnUserMenu = dom.parentWithClass(e__e.target, 'btnUserMenu');
|
||||
|
||||
if (btnUserMenu) {
|
||||
showUserMenu(btnUserMenu);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
pageIdOn('pagebeforeshow', 'userProfilesPage', function () {
|
||||
loadData(this);
|
||||
});
|
||||
|
9
src/controllers/home.html
Normal file
9
src/controllers/home.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div id="indexPage" style="outline: none;" data-role="page" data-dom-cache="true" class="page homePage libraryPage allLibraryPage backdropPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie,series,book">
|
||||
|
||||
<div class="tabContent pageTabContent" id="homeTab" data-index="0">
|
||||
<div class="sections"></div>
|
||||
</div>
|
||||
<div class="tabContent pageTabContent" id="favoritesTab" data-index="1">
|
||||
<div class="sections"></div>
|
||||
</div>
|
||||
</div>
|
65
src/controllers/home.js
Normal file
65
src/controllers/home.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
import TabbedView from '../components/tabbedview/tabbedview';
|
||||
import globalize from '../scripts/globalize';
|
||||
import '../elements/emby-tabs/emby-tabs';
|
||||
import '../elements/emby-button/emby-button';
|
||||
import '../elements/emby-scroller/emby-scroller';
|
||||
import LibraryMenu from '../scripts/libraryMenu';
|
||||
|
||||
class HomeView extends TabbedView {
|
||||
setTitle() {
|
||||
LibraryMenu.setTitle(null);
|
||||
}
|
||||
|
||||
onPause() {
|
||||
super.onPause(this);
|
||||
document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader');
|
||||
}
|
||||
|
||||
onResume(options) {
|
||||
super.onResume(this, options);
|
||||
document.querySelector('.skinHeader').classList.add('noHomeButtonHeader');
|
||||
}
|
||||
|
||||
getDefaultTabIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
getTabs() {
|
||||
return [{
|
||||
name: globalize.translate('Home')
|
||||
}, {
|
||||
name: globalize.translate('Favorites')
|
||||
}];
|
||||
}
|
||||
|
||||
getTabController(index) {
|
||||
if (index == null) {
|
||||
throw new Error('index cannot be null');
|
||||
}
|
||||
|
||||
let depends = '';
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
depends = 'hometab';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
depends = 'favorites';
|
||||
}
|
||||
|
||||
const instance = this;
|
||||
return import(/* webpackChunkName: "[request]" */ `../controllers/${depends}`).then(({ default: controllerFactory }) => {
|
||||
let controller = instance.tabControllers[index];
|
||||
|
||||
if (!controller) {
|
||||
controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params);
|
||||
instance.tabControllers[index] = controller;
|
||||
}
|
||||
|
||||
return controller;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default HomeView;
|
|
@ -349,7 +349,7 @@ import LibraryMenu from '../scripts/libraryMenu';
|
|||
function showViewSettingsMenu() {
|
||||
const instance = this;
|
||||
|
||||
import('../components/viewSettings/viewSettings').then(({default: ViewSettings}) => {
|
||||
import('../components/viewSettings/viewSettings').then(({ default: ViewSettings }) => {
|
||||
new ViewSettings().show({
|
||||
settingsKey: instance.getSettingsKey(),
|
||||
settings: instance.getViewSettings(),
|
||||
|
@ -364,7 +364,7 @@ import LibraryMenu from '../scripts/libraryMenu';
|
|||
function showFilterMenu() {
|
||||
const instance = this;
|
||||
|
||||
import('../components/filtermenu/filtermenu').then(({default: FilterMenu}) => {
|
||||
import('../components/filtermenu/filtermenu').then(({ default: FilterMenu }) => {
|
||||
new FilterMenu().show({
|
||||
settingsKey: instance.getSettingsKey(),
|
||||
settings: instance.getFilters(),
|
||||
|
@ -383,7 +383,7 @@ import LibraryMenu from '../scripts/libraryMenu';
|
|||
function showSortMenu() {
|
||||
const instance = this;
|
||||
|
||||
import('../components/sortmenu/sortmenu').then(({default: SortMenu}) => {
|
||||
import('../components/sortmenu/sortmenu').then(({ default: SortMenu }) => {
|
||||
new SortMenu().show({
|
||||
settingsKey: instance.getSettingsKey(),
|
||||
settings: instance.getSortValues(),
|
||||
|
@ -401,7 +401,7 @@ import LibraryMenu from '../scripts/libraryMenu';
|
|||
function onNewItemClick() {
|
||||
const instance = this;
|
||||
|
||||
import('../components/playlisteditor/playlisteditor').then(({default: playlistEditor}) => {
|
||||
import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => {
|
||||
new playlistEditor({
|
||||
items: [],
|
||||
serverId: instance.params.serverId
|
||||
|
@ -772,7 +772,7 @@ class ItemsView {
|
|||
}
|
||||
|
||||
function autoFocus() {
|
||||
import('../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(view);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ export default function (view, params, tabContent) {
|
|||
}
|
||||
|
||||
function showFilterMenu(context) {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: FilterDialog}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: FilterDialog }) => {
|
||||
const filterDialog = new FilterDialog({
|
||||
query: getQuery(),
|
||||
mode: 'livetvchannels',
|
||||
|
@ -124,7 +124,7 @@ export default function (view, params, tabContent) {
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(context);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,7 +61,7 @@ function loadRecommendedPrograms(page) {
|
|||
});
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -273,7 +273,7 @@ export default function (view, params) {
|
|||
break;
|
||||
}
|
||||
|
||||
import(`../livetv/${depends}`).then(({default: controllerFactory}) => {
|
||||
import(`../livetv/${depends}`).then(({ default: controllerFactory }) => {
|
||||
let tabContent;
|
||||
|
||||
if (index === 0) {
|
||||
|
|
|
@ -9,7 +9,7 @@ function onListingsSubmitted() {
|
|||
}
|
||||
|
||||
function init(page, type, providerId) {
|
||||
import(`../components/tvproviders/${type}`).then(({default: factory}) => {
|
||||
import(`../components/tvproviders/${type}`).then(({ default: factory }) => {
|
||||
const instance = new factory(page, providerId, {});
|
||||
Events.on(instance, 'submitted', onListingsSubmitted);
|
||||
instance.init();
|
||||
|
@ -17,7 +17,7 @@ function init(page, type, providerId) {
|
|||
}
|
||||
|
||||
function loadTemplate(page, type, providerId) {
|
||||
import(`../components/tvproviders/${type}.template.html`).then(({default: html}) => {
|
||||
import(`../components/tvproviders/${type}.template.html`).then(({ default: html }) => {
|
||||
page.querySelector('.providerTemplate').innerHTML = globalize.translateHtml(html);
|
||||
init(page, type, providerId);
|
||||
});
|
||||
|
|
|
@ -64,7 +64,7 @@ $(document).on('pageinit', '#liveTvSettingsPage', function () {
|
|||
const page = this;
|
||||
$('.liveTvSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
|
||||
$('#btnSelectRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
|
@ -79,7 +79,7 @@ $(document).on('pageinit', '#liveTvSettingsPage', function () {
|
|||
});
|
||||
});
|
||||
$('#btnSelectMovieRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
|
@ -94,7 +94,7 @@ $(document).on('pageinit', '#liveTvSettingsPage', function () {
|
|||
});
|
||||
});
|
||||
$('#btnSelectSeriesRecordingPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
callback: function (path) {
|
||||
|
@ -109,7 +109,7 @@ $(document).on('pageinit', '#liveTvSettingsPage', function () {
|
|||
});
|
||||
});
|
||||
$('#btnSelectPostProcessorPath', page).on('click.selectDirectory', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
|
|
|
@ -147,7 +147,7 @@ function showProviderOptions(page, providerId, button) {
|
|||
id: 'map'
|
||||
});
|
||||
|
||||
import('../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: items,
|
||||
positionTo: button
|
||||
|
@ -165,7 +165,7 @@ function showProviderOptions(page, providerId, button) {
|
|||
}
|
||||
|
||||
function mapChannels(page, providerId) {
|
||||
import('../components/channelMapper/channelMapper').then(({default: channelMapper}) => {
|
||||
import('../components/channelMapper/channelMapper').then(({ default: channelMapper }) => {
|
||||
new channelMapper({
|
||||
serverId: ApiClient.serverInfo().Id,
|
||||
providerId: providerId
|
||||
|
@ -237,7 +237,7 @@ function addProvider(button) {
|
|||
id: 'xmltv'
|
||||
});
|
||||
|
||||
import('../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
positionTo: button,
|
||||
|
@ -263,7 +263,7 @@ function showDeviceMenu(button, tunerDeviceId) {
|
|||
id: 'edit'
|
||||
});
|
||||
|
||||
import('../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: items,
|
||||
positionTo: button
|
||||
|
|
|
@ -106,7 +106,7 @@ function submitForm(page) {
|
|||
}
|
||||
|
||||
function getDetectedDevice() {
|
||||
return import('../components/tunerPicker').then(({default: tunerPicker}) => {
|
||||
return import('../components/tunerPicker').then(({ default: tunerPicker }) => {
|
||||
return new tunerPicker().show({
|
||||
serverId: ApiClient.serverId()
|
||||
});
|
||||
|
@ -222,7 +222,7 @@ export default function (view, params) {
|
|||
});
|
||||
});
|
||||
view.querySelector('.btnSelectPath').addEventListener('click', function () {
|
||||
import('../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
|
||||
import('../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
|
||||
const picker = new DirectoryBrowser();
|
||||
picker.show({
|
||||
includeFiles: true,
|
||||
|
|
261
src/controllers/movies/moviecollections.js
Normal file
261
src/controllers/movies/moviecollections.js
Normal file
|
@ -0,0 +1,261 @@
|
|||
import loading from '../../components/loading/loading';
|
||||
import libraryBrowser from '../../scripts/libraryBrowser';
|
||||
import imageLoader from '../../components/images/imageLoader';
|
||||
import listView from '../../components/listview/listview';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
export default function (view, params, tabContent) {
|
||||
function getPageData() {
|
||||
const key = getSavedQueryKey();
|
||||
let pageData = data[key];
|
||||
|
||||
if (!pageData) {
|
||||
pageData = data[key] = {
|
||||
query: {
|
||||
SortBy: 'SortName',
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'BoxSet',
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,SortName',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
StartIndex: 0
|
||||
},
|
||||
view: libraryBrowser.getSavedView(key) || 'Poster'
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
function getQuery() {
|
||||
return getPageData().query;
|
||||
}
|
||||
|
||||
function getSavedQueryKey() {
|
||||
return params.topParentId + '-' + 'moviecollections';
|
||||
}
|
||||
|
||||
const onViewStyleChange = () => {
|
||||
const viewStyle = this.getCurrentViewStyle();
|
||||
const itemsContainer = tabContent.querySelector('.itemsContainer');
|
||||
|
||||
if (viewStyle == 'List') {
|
||||
itemsContainer.classList.add('vertical-list');
|
||||
itemsContainer.classList.remove('vertical-wrap');
|
||||
} else {
|
||||
itemsContainer.classList.remove('vertical-list');
|
||||
itemsContainer.classList.add('vertical-wrap');
|
||||
}
|
||||
|
||||
itemsContainer.innerHTML = '';
|
||||
};
|
||||
|
||||
const reloadItems = (page) => {
|
||||
loading.show();
|
||||
isLoading = true;
|
||||
const query = getQuery();
|
||||
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => {
|
||||
function onNextPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex += query.Limit;
|
||||
}
|
||||
reloadItems(tabContent);
|
||||
}
|
||||
|
||||
function onPreviousPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
||||
}
|
||||
reloadItems(tabContent);
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
let html;
|
||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||
startIndex: query.StartIndex,
|
||||
limit: query.Limit,
|
||||
totalRecordCount: result.TotalRecordCount,
|
||||
showLimit: false,
|
||||
updatePageSizeSetting: false,
|
||||
addLayoutButton: false,
|
||||
sortButton: false,
|
||||
filterButton: false
|
||||
});
|
||||
const viewStyle = this.getCurrentViewStyle();
|
||||
if (viewStyle == 'Thumb') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
overlayPlayButton: true,
|
||||
centerText: true,
|
||||
showTitle: true
|
||||
});
|
||||
} else if (viewStyle == 'ThumbCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
lazy: true,
|
||||
cardLayout: true,
|
||||
showTitle: true
|
||||
});
|
||||
} else if (viewStyle == 'Banner') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'banner',
|
||||
preferBanner: true,
|
||||
context: 'movies',
|
||||
lazy: true
|
||||
});
|
||||
} else if (viewStyle == 'List') {
|
||||
html = listView.getListViewHtml({
|
||||
items: result.Items,
|
||||
context: 'movies',
|
||||
sortBy: query.SortBy
|
||||
});
|
||||
} else if (viewStyle == 'PosterCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'auto',
|
||||
context: 'movies',
|
||||
showTitle: true,
|
||||
centerText: false,
|
||||
cardLayout: true
|
||||
});
|
||||
} else {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'auto',
|
||||
context: 'movies',
|
||||
centerText: true,
|
||||
overlayPlayButton: true,
|
||||
showTitle: true
|
||||
});
|
||||
}
|
||||
|
||||
let elems = tabContent.querySelectorAll('.paging');
|
||||
|
||||
for (const elem of elems) {
|
||||
elem.innerHTML = pagingHtml;
|
||||
}
|
||||
|
||||
elems = tabContent.querySelectorAll('.btnNextPage');
|
||||
for (const elem of elems) {
|
||||
elem.addEventListener('click', onNextPageClick);
|
||||
}
|
||||
|
||||
elems = tabContent.querySelectorAll('.btnPreviousPage');
|
||||
for (const elem of elems) {
|
||||
elem.addEventListener('click', onPreviousPageClick);
|
||||
}
|
||||
|
||||
if (!result.Items.length) {
|
||||
html = '';
|
||||
|
||||
html += '<div class="noItemsMessage centerMessage">';
|
||||
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
|
||||
html += '<p>' + globalize.translate('MessageNoCollectionsAvailable') + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
const itemsContainer = tabContent.querySelector('.itemsContainer');
|
||||
itemsContainer.innerHTML = html;
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
|
||||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const data = {};
|
||||
let isLoading = false;
|
||||
|
||||
this.getCurrentViewStyle = function () {
|
||||
return getPageData().view;
|
||||
};
|
||||
|
||||
const initPage = (tabElement) => {
|
||||
tabElement.querySelector('.btnSort').addEventListener('click', function (e) {
|
||||
libraryBrowser.showSortMenu({
|
||||
items: [{
|
||||
name: globalize.translate('Name'),
|
||||
id: 'SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionImdbRating'),
|
||||
id: 'CommunityRating,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionDateAdded'),
|
||||
id: 'DateCreated,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionParentalRating'),
|
||||
id: 'OfficialRating,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionReleaseDate'),
|
||||
id: 'PremiereDate,SortName'
|
||||
}],
|
||||
callback: function () {
|
||||
getQuery().StartIndex = 0;
|
||||
reloadItems(tabElement);
|
||||
},
|
||||
query: getQuery(),
|
||||
button: e.target
|
||||
});
|
||||
});
|
||||
const btnSelectView = tabElement.querySelector('.btnSelectView');
|
||||
btnSelectView.addEventListener('click', (e) => {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
|
||||
});
|
||||
btnSelectView.addEventListener('layoutchange', function (e) {
|
||||
const viewStyle = e.detail.viewStyle;
|
||||
getPageData().view = viewStyle;
|
||||
libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle);
|
||||
getQuery().StartIndex = 0;
|
||||
onViewStyleChange();
|
||||
reloadItems(tabElement);
|
||||
});
|
||||
tabElement.querySelector('.btnNewCollection').addEventListener('click', () => {
|
||||
import('../../components/collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => {
|
||||
const serverId = ApiClient.serverInfo().Id;
|
||||
const collectionEditor = new CollectionEditor();
|
||||
collectionEditor.show({
|
||||
items: [],
|
||||
serverId: serverId
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
initPage(tabContent);
|
||||
onViewStyleChange();
|
||||
|
||||
this.renderTab = function () {
|
||||
reloadItems(tabContent);
|
||||
};
|
||||
}
|
||||
|
221
src/controllers/movies/moviegenres.js
Normal file
221
src/controllers/movies/moviegenres.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
import escapeHtml from 'escape-html';
|
||||
import layoutManager from '../../components/layoutManager';
|
||||
import loading from '../../components/loading/loading';
|
||||
import libraryBrowser from '../../scripts/libraryBrowser';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import { appRouter } from '../../components/appRouter';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
|
||||
export default function (view, params, tabContent) {
|
||||
function getPageData() {
|
||||
const key = getSavedQueryKey();
|
||||
let pageData = data[key];
|
||||
|
||||
if (!pageData) {
|
||||
pageData = data[key] = {
|
||||
query: {
|
||||
SortBy: 'SortName',
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Recursive: true,
|
||||
EnableTotalRecordCount: false
|
||||
},
|
||||
view: 'Poster'
|
||||
};
|
||||
pageData.query.ParentId = params.topParentId;
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
function getQuery() {
|
||||
return getPageData().query;
|
||||
}
|
||||
|
||||
function getSavedQueryKey() {
|
||||
return params.topParentId + '-' + 'moviegenres';
|
||||
}
|
||||
|
||||
function getPromise() {
|
||||
loading.show();
|
||||
const query = getQuery();
|
||||
return ApiClient.getGenres(ApiClient.getCurrentUserId(), query);
|
||||
}
|
||||
|
||||
function enableScrollX() {
|
||||
return !layoutManager.desktop;
|
||||
}
|
||||
|
||||
function getThumbShape() {
|
||||
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
|
||||
}
|
||||
|
||||
function getPortraitShape() {
|
||||
return enableScrollX() ? 'overflowPortrait' : 'portrait';
|
||||
}
|
||||
|
||||
const fillItemsContainer = (entry) => {
|
||||
const elem = entry.target;
|
||||
const id = elem.getAttribute('data-id');
|
||||
const viewStyle = this.getCurrentViewStyle();
|
||||
let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9;
|
||||
|
||||
if (enableScrollX()) {
|
||||
limit = 10;
|
||||
}
|
||||
|
||||
const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary';
|
||||
const query = {
|
||||
SortBy: 'Random',
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: enableImageTypes,
|
||||
Limit: limit,
|
||||
GenreIds: id,
|
||||
EnableTotalRecordCount: false,
|
||||
ParentId: params.topParentId
|
||||
};
|
||||
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) {
|
||||
if (viewStyle == 'Thumb') {
|
||||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: elem,
|
||||
shape: getThumbShape(),
|
||||
preferThumb: true,
|
||||
showTitle: true,
|
||||
scalable: true,
|
||||
centerText: true,
|
||||
overlayMoreButton: true,
|
||||
allowBottomPadding: false
|
||||
});
|
||||
} else if (viewStyle == 'ThumbCard') {
|
||||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: elem,
|
||||
shape: getThumbShape(),
|
||||
preferThumb: true,
|
||||
showTitle: true,
|
||||
scalable: true,
|
||||
centerText: false,
|
||||
cardLayout: true,
|
||||
showYear: true
|
||||
});
|
||||
} else if (viewStyle == 'PosterCard') {
|
||||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: elem,
|
||||
shape: getPortraitShape(),
|
||||
showTitle: true,
|
||||
scalable: true,
|
||||
centerText: false,
|
||||
cardLayout: true,
|
||||
showYear: true
|
||||
});
|
||||
} else if (viewStyle == 'Poster') {
|
||||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: elem,
|
||||
shape: getPortraitShape(),
|
||||
scalable: true,
|
||||
overlayMoreButton: true,
|
||||
allowBottomPadding: true,
|
||||
showTitle: true,
|
||||
centerText: true,
|
||||
showYear: true
|
||||
});
|
||||
}
|
||||
if (result.Items.length >= query.Limit) {
|
||||
tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function reloadItems(context, promise) {
|
||||
const query = getQuery();
|
||||
promise.then(function (result) {
|
||||
const elem = context.querySelector('#items');
|
||||
let html = '';
|
||||
const items = result.Items;
|
||||
|
||||
for (let i = 0, length = items.length; i < length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
html += '<div class="verticalSection">';
|
||||
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
|
||||
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item, {
|
||||
context: 'movies',
|
||||
parentId: params.topParentId
|
||||
}) + '" class="more button-flat button-flat-mini sectionTitleTextButton btnMoreFromGenre' + item.Id + '">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards">';
|
||||
html += escapeHtml(item.Name);
|
||||
html += '</h2>';
|
||||
html += '<span class="material-icons hide chevron_right" aria-hidden="true"></span>';
|
||||
html += '</a>';
|
||||
html += '</div>';
|
||||
if (enableScrollX()) {
|
||||
let scrollXClass = 'scrollX hiddenScrollX';
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale';
|
||||
}
|
||||
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' lazy padded-left padded-right" data-id="' + item.Id + '">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap lazy padded-left padded-right" data-id="' + item.Id + '">';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (!result.Items.length) {
|
||||
html = '';
|
||||
|
||||
html += '<div class="noItemsMessage centerMessage">';
|
||||
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
|
||||
html += '<p>' + globalize.translate('MessageNoGenresAvailable') + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
elem.innerHTML = html;
|
||||
lazyLoader.lazyChildren(elem, fillItemsContainer);
|
||||
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
const fullyReload = () => {
|
||||
this.preRender();
|
||||
this.renderTab();
|
||||
};
|
||||
|
||||
const data = {};
|
||||
|
||||
this.getViewStyles = function () {
|
||||
return 'Poster,PosterCard,Thumb,ThumbCard'.split(',');
|
||||
};
|
||||
|
||||
this.getCurrentViewStyle = function () {
|
||||
return getPageData().view;
|
||||
};
|
||||
|
||||
this.setCurrentViewStyle = function (viewStyle) {
|
||||
getPageData().view = viewStyle;
|
||||
libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle);
|
||||
fullyReload();
|
||||
};
|
||||
|
||||
this.enableViewSelection = true;
|
||||
let promise;
|
||||
|
||||
this.preRender = function () {
|
||||
promise = getPromise();
|
||||
};
|
||||
|
||||
this.renderTab = function () {
|
||||
reloadItems(tabContent, promise);
|
||||
};
|
||||
}
|
||||
|
92
src/controllers/movies/movies.html
Normal file
92
src/controllers/movies/movies.html
Normal file
|
@ -0,0 +1,92 @@
|
|||
<div id="moviesPage" data-role="page" data-dom-cache="true" class="page libraryPage backdropPage collectionEditorPage pageWithAbsoluteTabs withTabs" data-backdroptype="movie">
|
||||
|
||||
<div class="pageTabContent" id="moviesTab" data-index="0">
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
<button is="paper-icon-button-light" class="btnShuffle autoSize hide" title="${Shuffle}"><span class="material-icons shuffle" aria-hidden="true"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy" aria-hidden="true"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha" aria-hidden="true"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${Filter}"><span class="material-icons filter_list" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="alphaPicker alphaPicker-fixed alphaPicker-vertical">
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pageTabContent" id="suggestionsTab" data-index="1">
|
||||
<div id="resumableSection" class="verticalSection hide">
|
||||
<div class="sectionTitleContainer sectionTitleContainer-cards">
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderContinueWatching}</h2>
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" id="resumableItems" class="itemsContainer padded-left padded-right">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verticalSection">
|
||||
<div class="sectionTitleContainer sectionTitleContainer-cards">
|
||||
<h2 class="sectionTitle sectionTitle-cards padded-left">${HeaderLatestMovies}</h2>
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" id="recentlyAddedItems" class="itemsContainer padded-left padded-right">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendations">
|
||||
</div>
|
||||
<div class="noItemsMessage hide padded-left padded-right">
|
||||
<br />
|
||||
<p>${MessageNoMovieSuggestionsAvailable}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pageTabContent" id="trailersTab" data-index="2">
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha" aria-hidden="true"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnFilter autoSize" title="${Filter}"><span class="material-icons filter_list" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
|
||||
<div class="alphaPicker alphaPicker-fixed alphaPicker-fixed-right alphaPicker-vertical">
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pageTabContent" id="favoritesTab" data-index="3">
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right">
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pageTabContent" id="collectionsTab" data-index="4">
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
<button is="paper-icon-button-light" class="btnSelectView autoSize" title="${ButtonSelectView}"><span class="material-icons view_comfy" aria-hidden="true"></span></button>
|
||||
<button is="paper-icon-button-light" class="btnSort autoSize" title="${Sort}"><span class="material-icons sort_by_alpha" aria-hidden="true"></span></button>
|
||||
<button type="button" is="paper-icon-button-light" class="btnNewCollection autoSize" title="${NewCollection}"><span class="material-icons add" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
|
||||
<div is="emby-itemscontainer" class="itemsContainer vertical-wrap centered padded-left padded-right" style="text-align:center;">
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center flex-wrap-wrap padded-top padded-left padded-right padded-bottom focuscontainer-x">
|
||||
<div class="paging"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pageTabContent" id="genresTab" data-index="5">
|
||||
<div id="items"></div>
|
||||
</div>
|
||||
</div>
|
324
src/controllers/movies/movies.js
Normal file
324
src/controllers/movies/movies.js
Normal file
|
@ -0,0 +1,324 @@
|
|||
import loading from '../../components/loading/loading';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import libraryBrowser from '../../scripts/libraryBrowser';
|
||||
import { AlphaPicker } from '../../components/alphaPicker/alphaPicker';
|
||||
import listView from '../../components/listview/listview';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import Events from '../../utils/events.ts';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
|
||||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
export default function (view, params, tabContent, options) {
|
||||
const onViewStyleChange = () => {
|
||||
if (this.getCurrentViewStyle() == 'List') {
|
||||
itemsContainer.classList.add('vertical-list');
|
||||
itemsContainer.classList.remove('vertical-wrap');
|
||||
} else {
|
||||
itemsContainer.classList.remove('vertical-list');
|
||||
itemsContainer.classList.add('vertical-wrap');
|
||||
}
|
||||
|
||||
itemsContainer.innerHTML = '';
|
||||
};
|
||||
|
||||
function fetchData() {
|
||||
isLoading = true;
|
||||
loading.show();
|
||||
return ApiClient.getItems(ApiClient.getCurrentUserId(), query);
|
||||
}
|
||||
|
||||
function shuffle() {
|
||||
ApiClient.getItem(
|
||||
ApiClient.getCurrentUserId(),
|
||||
params.topParentId
|
||||
).then((item) => {
|
||||
playbackManager.shuffle(item);
|
||||
});
|
||||
}
|
||||
|
||||
const afterRefresh = (result) => {
|
||||
function onNextPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex += query.Limit;
|
||||
}
|
||||
itemsContainer.refreshItems();
|
||||
}
|
||||
|
||||
function onPreviousPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
||||
}
|
||||
itemsContainer.refreshItems();
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
this.alphaPicker?.updateControls(query);
|
||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||
startIndex: query.StartIndex,
|
||||
limit: query.Limit,
|
||||
totalRecordCount: result.TotalRecordCount,
|
||||
showLimit: false,
|
||||
updatePageSizeSetting: false,
|
||||
addLayoutButton: false,
|
||||
sortButton: false,
|
||||
filterButton: false
|
||||
});
|
||||
|
||||
for (const elem of tabContent.querySelectorAll('.paging')) {
|
||||
elem.innerHTML = pagingHtml;
|
||||
}
|
||||
|
||||
for (const elem of tabContent.querySelectorAll('.btnNextPage')) {
|
||||
elem.addEventListener('click', onNextPageClick);
|
||||
}
|
||||
|
||||
for (const elem of tabContent.querySelectorAll('.btnPreviousPage')) {
|
||||
elem.addEventListener('click', onPreviousPageClick);
|
||||
}
|
||||
|
||||
tabContent.querySelector('.btnShuffle').classList.toggle('hide', result.TotalRecordCount < 1);
|
||||
|
||||
isLoading = false;
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(tabContent);
|
||||
});
|
||||
};
|
||||
|
||||
const getItemsHtml = (items) => {
|
||||
let html;
|
||||
const viewStyle = this.getCurrentViewStyle();
|
||||
|
||||
if (viewStyle == 'Thumb') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
lazy: true,
|
||||
overlayPlayButton: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
} else if (viewStyle == 'ThumbCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
lazy: true,
|
||||
cardLayout: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
} else if (viewStyle == 'Banner') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: 'banner',
|
||||
preferBanner: true,
|
||||
context: 'movies',
|
||||
lazy: true
|
||||
});
|
||||
} else if (viewStyle == 'List') {
|
||||
html = listView.getListViewHtml({
|
||||
items: items,
|
||||
context: 'movies',
|
||||
sortBy: query.SortBy
|
||||
});
|
||||
} else if (viewStyle == 'PosterCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: 'portrait',
|
||||
context: 'movies',
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true,
|
||||
lazy: true,
|
||||
cardLayout: true
|
||||
});
|
||||
} else {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: items,
|
||||
shape: 'portrait',
|
||||
context: 'movies',
|
||||
overlayPlayButton: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
};
|
||||
|
||||
const initPage = (tabElement) => {
|
||||
itemsContainer.fetchData = fetchData;
|
||||
itemsContainer.getItemsHtml = getItemsHtml;
|
||||
itemsContainer.afterRefresh = afterRefresh;
|
||||
const alphaPickerElement = tabElement.querySelector('.alphaPicker');
|
||||
|
||||
if (alphaPickerElement) {
|
||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||
const newValue = e.detail.value;
|
||||
if (newValue === '#') {
|
||||
query.NameLessThan = 'A';
|
||||
delete query.NameStartsWith;
|
||||
} else {
|
||||
query.NameStartsWith = newValue;
|
||||
delete query.NameLessThan;
|
||||
}
|
||||
query.StartIndex = 0;
|
||||
itemsContainer.refreshItems();
|
||||
});
|
||||
this.alphaPicker = new AlphaPicker({
|
||||
element: alphaPickerElement,
|
||||
valueChangeEvent: 'click'
|
||||
});
|
||||
|
||||
tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right');
|
||||
alphaPickerElement.classList.add('alphaPicker-fixed-right');
|
||||
itemsContainer.classList.add('padded-right-withalphapicker');
|
||||
}
|
||||
|
||||
const btnFilter = tabElement.querySelector('.btnFilter');
|
||||
|
||||
if (btnFilter) {
|
||||
btnFilter.addEventListener('click', () => {
|
||||
this.showFilterMenu();
|
||||
});
|
||||
}
|
||||
const btnSort = tabElement.querySelector('.btnSort');
|
||||
|
||||
if (btnSort) {
|
||||
btnSort.addEventListener('click', function (e) {
|
||||
libraryBrowser.showSortMenu({
|
||||
items: [{
|
||||
name: globalize.translate('Name'),
|
||||
id: 'SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionRandom'),
|
||||
id: 'Random'
|
||||
}, {
|
||||
name: globalize.translate('OptionImdbRating'),
|
||||
id: 'CommunityRating,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionCriticRating'),
|
||||
id: 'CriticRating,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionDateAdded'),
|
||||
id: 'DateCreated,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionDatePlayed'),
|
||||
id: 'DatePlayed,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionParentalRating'),
|
||||
id: 'OfficialRating,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionPlayCount'),
|
||||
id: 'PlayCount,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('OptionReleaseDate'),
|
||||
id: 'PremiereDate,SortName,ProductionYear'
|
||||
}, {
|
||||
name: globalize.translate('Runtime'),
|
||||
id: 'Runtime,SortName,ProductionYear'
|
||||
}],
|
||||
callback: function () {
|
||||
query.StartIndex = 0;
|
||||
userSettings.saveQuerySettings(savedQueryKey, query);
|
||||
itemsContainer.refreshItems();
|
||||
},
|
||||
query: query,
|
||||
button: e.target
|
||||
});
|
||||
});
|
||||
}
|
||||
const btnSelectView = tabElement.querySelector('.btnSelectView');
|
||||
btnSelectView.addEventListener('click', (e) => {
|
||||
libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(','));
|
||||
});
|
||||
btnSelectView.addEventListener('layoutchange', function (e) {
|
||||
const viewStyle = e.detail.viewStyle;
|
||||
userSettings.set(savedViewKey, viewStyle);
|
||||
query.StartIndex = 0;
|
||||
onViewStyleChange();
|
||||
itemsContainer.refreshItems();
|
||||
});
|
||||
|
||||
tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle);
|
||||
};
|
||||
|
||||
let itemsContainer = tabContent.querySelector('.itemsContainer');
|
||||
const savedQueryKey = params.topParentId + '-' + options.mode;
|
||||
const savedViewKey = savedQueryKey + '-view';
|
||||
let query = {
|
||||
SortBy: 'SortName,ProductionYear',
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
StartIndex: 0,
|
||||
ParentId: params.topParentId
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
let isLoading = false;
|
||||
|
||||
if (options.mode === 'favorites') {
|
||||
query.IsFavorite = true;
|
||||
}
|
||||
|
||||
query = userSettings.loadQuerySettings(savedQueryKey, query);
|
||||
|
||||
this.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: query,
|
||||
mode: 'movies',
|
||||
serverId: ApiClient.serverId()
|
||||
});
|
||||
Events.on(filterDialog, 'filterchange', () => {
|
||||
query.StartIndex = 0;
|
||||
itemsContainer.refreshItems();
|
||||
});
|
||||
filterDialog.show();
|
||||
});
|
||||
};
|
||||
|
||||
this.getCurrentViewStyle = function () {
|
||||
return userSettings.get(savedViewKey) || 'Poster';
|
||||
};
|
||||
|
||||
this.initTab = function () {
|
||||
initPage(tabContent);
|
||||
onViewStyleChange();
|
||||
};
|
||||
|
||||
this.renderTab = () => {
|
||||
itemsContainer.refreshItems();
|
||||
this.alphaPicker?.updateControls(query);
|
||||
};
|
||||
|
||||
this.destroy = function () {
|
||||
itemsContainer = null;
|
||||
};
|
||||
}
|
||||
|
425
src/controllers/movies/moviesrecommended.js
Normal file
425
src/controllers/movies/moviesrecommended.js
Normal file
|
@ -0,0 +1,425 @@
|
|||
import escapeHtml from 'escape-html';
|
||||
import layoutManager from '../../components/layoutManager';
|
||||
import inputManager from '../../scripts/inputManager';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import libraryMenu from '../../scripts/libraryMenu';
|
||||
import * as mainTabsManager from '../../components/maintabsmanager';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import dom from '../../scripts/dom';
|
||||
import imageLoader from '../../components/images/imageLoader';
|
||||
import { playbackManager } from '../../components/playback/playbackmanager';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import Dashboard from '../../utils/dashboard';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
import '../../elements/emby-scroller/emby-scroller';
|
||||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
import '../../elements/emby-tabs/emby-tabs';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
|
||||
function enableScrollX() {
|
||||
return !layoutManager.desktop;
|
||||
}
|
||||
|
||||
function getPortraitShape() {
|
||||
return enableScrollX() ? 'overflowPortrait' : 'portrait';
|
||||
}
|
||||
|
||||
function getThumbShape() {
|
||||
return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
|
||||
}
|
||||
|
||||
function loadLatest(page, userId, parentId) {
|
||||
const options = {
|
||||
IncludeItemTypes: 'Movie',
|
||||
Limit: 18,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
ParentId: parentId,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false
|
||||
};
|
||||
ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) {
|
||||
const allowBottomPadding = !enableScrollX();
|
||||
const container = page.querySelector('#recentlyAddedItems');
|
||||
cardBuilder.buildCards(items, {
|
||||
itemsContainer: container,
|
||||
shape: getPortraitShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: allowBottomPadding,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}
|
||||
|
||||
function loadResume(page, userId, parentId) {
|
||||
const screenWidth = dom.getWindowSize().innerWidth;
|
||||
const options = {
|
||||
SortBy: 'DatePlayed',
|
||||
SortOrder: 'Descending',
|
||||
IncludeItemTypes: 'Movie',
|
||||
Filters: 'IsResumable',
|
||||
Limit: screenWidth >= 1600 ? 5 : 3,
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
CollapseBoxSetItems: false,
|
||||
ParentId: parentId,
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
EnableTotalRecordCount: false
|
||||
};
|
||||
ApiClient.getItems(userId, options).then(function (result) {
|
||||
if (result.Items.length) {
|
||||
page.querySelector('#resumableSection').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('#resumableSection').classList.add('hide');
|
||||
}
|
||||
|
||||
const allowBottomPadding = !enableScrollX();
|
||||
const container = page.querySelector('#resumableItems');
|
||||
cardBuilder.buildCards(result.Items, {
|
||||
itemsContainer: container,
|
||||
preferThumb: true,
|
||||
shape: getThumbShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: allowBottomPadding,
|
||||
cardLayout: false,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}
|
||||
|
||||
function getRecommendationHtml(recommendation) {
|
||||
let html = '';
|
||||
let title = '';
|
||||
|
||||
switch (recommendation.RecommendationType) {
|
||||
case 'SimilarToRecentlyPlayed':
|
||||
title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case 'SimilarToLikedItem':
|
||||
title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case 'HasDirectorFromRecentlyPlayed':
|
||||
case 'HasLikedDirector':
|
||||
title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName);
|
||||
break;
|
||||
|
||||
case 'HasActorFromRecentlyPlayed':
|
||||
case 'HasLikedActor':
|
||||
title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName);
|
||||
break;
|
||||
}
|
||||
|
||||
html += '<div class="verticalSection">';
|
||||
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + escapeHtml(title) + '</h2>';
|
||||
const allowBottomPadding = true;
|
||||
|
||||
if (enableScrollX()) {
|
||||
html += '<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-mousewheel="false" data-centerfocus="true">';
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer scrollSlider focuscontainer-x">';
|
||||
} else {
|
||||
html += '<div is="emby-itemscontainer" class="itemsContainer focuscontainer-x padded-left padded-right vertical-wrap">';
|
||||
}
|
||||
|
||||
html += cardBuilder.getCardsHtml(recommendation.Items, {
|
||||
shape: getPortraitShape(),
|
||||
scalable: true,
|
||||
overlayPlayButton: true,
|
||||
allowBottomPadding: allowBottomPadding,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
|
||||
if (enableScrollX()) {
|
||||
html += '</div>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function loadSuggestions(page, userId) {
|
||||
const screenWidth = dom.getWindowSize().innerWidth;
|
||||
let itemLimit = 5;
|
||||
if (screenWidth >= 1600) {
|
||||
itemLimit = 8;
|
||||
} else if (screenWidth >= 1200) {
|
||||
itemLimit = 6;
|
||||
}
|
||||
|
||||
const url = ApiClient.getUrl('Movies/Recommendations', {
|
||||
userId: userId,
|
||||
categoryLimit: 6,
|
||||
ItemLimit: itemLimit,
|
||||
Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb'
|
||||
});
|
||||
ApiClient.getJSON(url).then(function (recommendations) {
|
||||
if (!recommendations.length) {
|
||||
page.querySelector('.noItemsMessage').classList.remove('hide');
|
||||
page.querySelector('.recommendations').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const html = recommendations.map(getRecommendationHtml).join('');
|
||||
page.querySelector('.noItemsMessage').classList.add('hide');
|
||||
const recs = page.querySelector('.recommendations');
|
||||
recs.innerHTML = html;
|
||||
imageLoader.lazyChildren(recs);
|
||||
|
||||
// FIXME: Wait for all sections to load
|
||||
autoFocus(page);
|
||||
});
|
||||
}
|
||||
|
||||
function autoFocus(page) {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
}
|
||||
|
||||
function setScrollClasses(elem, scrollX) {
|
||||
if (scrollX) {
|
||||
elem.classList.add('hiddenScrollX');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
elem.classList.add('smoothScrollX');
|
||||
elem.classList.add('padded-top-focusscale');
|
||||
elem.classList.add('padded-bottom-focusscale');
|
||||
}
|
||||
|
||||
elem.classList.add('scrollX');
|
||||
elem.classList.remove('vertical-wrap');
|
||||
} else {
|
||||
elem.classList.remove('hiddenScrollX');
|
||||
elem.classList.remove('smoothScrollX');
|
||||
elem.classList.remove('scrollX');
|
||||
elem.classList.add('vertical-wrap');
|
||||
}
|
||||
}
|
||||
|
||||
function initSuggestedTab(page, tabContent) {
|
||||
const containers = tabContent.querySelectorAll('.itemsContainer');
|
||||
|
||||
for (const container of containers) {
|
||||
setScrollClasses(container, enableScrollX());
|
||||
}
|
||||
}
|
||||
|
||||
function loadSuggestionsTab(view, params, tabContent) {
|
||||
const parentId = params.topParentId;
|
||||
const userId = ApiClient.getCurrentUserId();
|
||||
loadResume(tabContent, userId, parentId);
|
||||
loadLatest(tabContent, userId, parentId);
|
||||
loadSuggestions(tabContent, userId);
|
||||
}
|
||||
|
||||
function getTabs() {
|
||||
return [{
|
||||
name: globalize.translate('Movies')
|
||||
}, {
|
||||
name: globalize.translate('Suggestions')
|
||||
}, {
|
||||
name: globalize.translate('Trailers')
|
||||
}, {
|
||||
name: globalize.translate('Favorites')
|
||||
}, {
|
||||
name: globalize.translate('Collections')
|
||||
}, {
|
||||
name: globalize.translate('Genres')
|
||||
}];
|
||||
}
|
||||
|
||||
function getDefaultTabIndex(folderId) {
|
||||
switch (userSettings.get('landing-' + folderId)) {
|
||||
case 'suggestions':
|
||||
return 1;
|
||||
|
||||
case 'favorites':
|
||||
return 3;
|
||||
|
||||
case 'collections':
|
||||
return 4;
|
||||
|
||||
case 'genres':
|
||||
return 5;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export default function (view, params) {
|
||||
function onBeforeTabChange(e) {
|
||||
preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10));
|
||||
}
|
||||
|
||||
function onTabChange(e) {
|
||||
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
|
||||
loadTab(view, newIndex);
|
||||
}
|
||||
|
||||
function getTabContainers() {
|
||||
return view.querySelectorAll('.pageTabContent');
|
||||
}
|
||||
|
||||
function initTabs() {
|
||||
mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange);
|
||||
}
|
||||
|
||||
const getTabController = (page, index, callback) => {
|
||||
let depends = '';
|
||||
|
||||
switch (index) {
|
||||
case 0:
|
||||
depends = 'movies';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
depends = 'moviesrecommended.js';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
depends = 'movietrailers';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
depends = 'movies';
|
||||
break;
|
||||
|
||||
case 4:
|
||||
depends = 'moviecollections';
|
||||
break;
|
||||
|
||||
case 5:
|
||||
depends = 'moviegenres';
|
||||
break;
|
||||
}
|
||||
|
||||
import(`../movies/${depends}`).then(({ default: controllerFactory }) => {
|
||||
let tabContent;
|
||||
|
||||
if (index === suggestionsTabIndex) {
|
||||
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
|
||||
this.tabContent = tabContent;
|
||||
}
|
||||
|
||||
let controller = tabControllers[index];
|
||||
|
||||
if (!controller) {
|
||||
tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']");
|
||||
|
||||
if (index === suggestionsTabIndex) {
|
||||
controller = this;
|
||||
} else if (index == 0 || index == 3) {
|
||||
controller = new controllerFactory(view, params, tabContent, {
|
||||
mode: index ? 'favorites' : 'movies'
|
||||
});
|
||||
} else {
|
||||
controller = new controllerFactory(view, params, tabContent);
|
||||
}
|
||||
|
||||
tabControllers[index] = controller;
|
||||
|
||||
if (controller.initTab) {
|
||||
controller.initTab();
|
||||
}
|
||||
}
|
||||
|
||||
callback(controller);
|
||||
});
|
||||
};
|
||||
|
||||
function preLoadTab(page, index) {
|
||||
getTabController(page, index, function (controller) {
|
||||
if (renderedTabs.indexOf(index) == -1 && controller.preRender) {
|
||||
controller.preRender();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadTab(page, index) {
|
||||
currentTabIndex = index;
|
||||
getTabController(page, index, ((controller) => {
|
||||
if (renderedTabs.indexOf(index) == -1) {
|
||||
renderedTabs.push(index);
|
||||
controller.renderTab();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
function onPlaybackStop(e, state) {
|
||||
if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') {
|
||||
renderedTabs = [];
|
||||
mainTabsManager.getTabsElement().triggerTabChange();
|
||||
}
|
||||
}
|
||||
|
||||
function onInputCommand(e) {
|
||||
if (e.detail.command === 'search') {
|
||||
e.preventDefault();
|
||||
Dashboard.navigate('search.html?collectionType=movies&parentId=' + params.topParentId);
|
||||
}
|
||||
}
|
||||
|
||||
let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10);
|
||||
const suggestionsTabIndex = 1;
|
||||
|
||||
this.initTab = function () {
|
||||
const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
|
||||
initSuggestedTab(view, tabContent);
|
||||
};
|
||||
|
||||
this.renderTab = function () {
|
||||
const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']");
|
||||
loadSuggestionsTab(view, params, tabContent);
|
||||
};
|
||||
|
||||
const tabControllers = [];
|
||||
let renderedTabs = [];
|
||||
view.addEventListener('viewshow', function () {
|
||||
initTabs();
|
||||
if (!view.getAttribute('data-title')) {
|
||||
const parentId = params.topParentId;
|
||||
|
||||
if (parentId) {
|
||||
ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) {
|
||||
view.setAttribute('data-title', item.Name);
|
||||
libraryMenu.setTitle(item.Name);
|
||||
});
|
||||
} else {
|
||||
view.setAttribute('data-title', globalize.translate('Movies'));
|
||||
libraryMenu.setTitle(globalize.translate('Movies'));
|
||||
}
|
||||
}
|
||||
|
||||
Events.on(playbackManager, 'playbackstop', onPlaybackStop);
|
||||
inputManager.on(window, onInputCommand);
|
||||
});
|
||||
view.addEventListener('viewbeforehide', function () {
|
||||
inputManager.off(window, onInputCommand);
|
||||
});
|
||||
for (const tabController of tabControllers) {
|
||||
if (tabController.destroy) {
|
||||
tabController.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
272
src/controllers/movies/movietrailers.js
Normal file
272
src/controllers/movies/movietrailers.js
Normal file
|
@ -0,0 +1,272 @@
|
|||
import loading from '../../components/loading/loading';
|
||||
import libraryBrowser from '../../scripts/libraryBrowser';
|
||||
import imageLoader from '../../components/images/imageLoader';
|
||||
import { AlphaPicker } from '../../components/alphaPicker/alphaPicker';
|
||||
import listView from '../../components/listview/listview';
|
||||
import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import Events from '../../utils/events.ts';
|
||||
|
||||
import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
||||
|
||||
export default function (view, params, tabContent) {
|
||||
function getPageData() {
|
||||
const key = getSavedQueryKey();
|
||||
let pageData = data[key];
|
||||
|
||||
if (!pageData) {
|
||||
pageData = data[key] = {
|
||||
query: {
|
||||
SortBy: 'SortName',
|
||||
SortOrder: 'Ascending',
|
||||
IncludeItemTypes: 'Trailer',
|
||||
Recursive: true,
|
||||
Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo',
|
||||
ImageTypeLimit: 1,
|
||||
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
|
||||
StartIndex: 0
|
||||
},
|
||||
view: libraryBrowser.getSavedView(key) || 'Poster'
|
||||
};
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
||||
}
|
||||
|
||||
libraryBrowser.loadSavedQueryValues(key, pageData.query);
|
||||
}
|
||||
|
||||
return pageData;
|
||||
}
|
||||
|
||||
function getQuery() {
|
||||
return getPageData().query;
|
||||
}
|
||||
|
||||
function getSavedQueryKey() {
|
||||
return params.topParentId + '-' + 'trailers';
|
||||
}
|
||||
|
||||
const reloadItems = () => {
|
||||
loading.show();
|
||||
isLoading = true;
|
||||
const query = getQuery();
|
||||
ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => {
|
||||
function onNextPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex += query.Limit;
|
||||
}
|
||||
reloadItems();
|
||||
}
|
||||
|
||||
function onPreviousPageClick() {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (userSettings.libraryPageSize() > 0) {
|
||||
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
||||
}
|
||||
reloadItems();
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
this.alphaPicker?.updateControls(query);
|
||||
const pagingHtml = libraryBrowser.getQueryPagingHtml({
|
||||
startIndex: query.StartIndex,
|
||||
limit: query.Limit,
|
||||
totalRecordCount: result.TotalRecordCount,
|
||||
showLimit: false,
|
||||
updatePageSizeSetting: false,
|
||||
addLayoutButton: false,
|
||||
sortButton: false,
|
||||
filterButton: false
|
||||
});
|
||||
let html;
|
||||
const viewStyle = this.getCurrentViewStyle();
|
||||
|
||||
if (viewStyle == 'Thumb') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
overlayPlayButton: true
|
||||
});
|
||||
} else if (viewStyle == 'ThumbCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'backdrop',
|
||||
preferThumb: true,
|
||||
context: 'movies',
|
||||
cardLayout: true,
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
centerText: true
|
||||
});
|
||||
} else if (viewStyle == 'Banner') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'banner',
|
||||
preferBanner: true,
|
||||
context: 'movies'
|
||||
});
|
||||
} else if (viewStyle == 'List') {
|
||||
html = listView.getListViewHtml({
|
||||
items: result.Items,
|
||||
context: 'movies',
|
||||
sortBy: query.SortBy
|
||||
});
|
||||
} else if (viewStyle == 'PosterCard') {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'portrait',
|
||||
context: 'movies',
|
||||
showTitle: true,
|
||||
showYear: true,
|
||||
cardLayout: true,
|
||||
centerText: true
|
||||
});
|
||||
} else {
|
||||
html = cardBuilder.getCardsHtml({
|
||||
items: result.Items,
|
||||
shape: 'portrait',
|
||||
context: 'movies',
|
||||
centerText: true,
|
||||
overlayPlayButton: true,
|
||||
showTitle: true,
|
||||
showYear: true
|
||||
});
|
||||
}
|
||||
|
||||
let elems = tabContent.querySelectorAll('.paging');
|
||||
|
||||
for (const elem of elems) {
|
||||
elem.innerHTML = pagingHtml;
|
||||
}
|
||||
|
||||
elems = tabContent.querySelectorAll('.btnNextPage');
|
||||
for (const elem of elems) {
|
||||
elem.addEventListener('click', onNextPageClick);
|
||||
}
|
||||
|
||||
elems = tabContent.querySelectorAll('.btnPreviousPage');
|
||||
for (const elem of elems) {
|
||||
elem.addEventListener('click', onPreviousPageClick);
|
||||
}
|
||||
|
||||
if (!result.Items.length) {
|
||||
html = '';
|
||||
|
||||
html += '<div class="noItemsMessage centerMessage">';
|
||||
html += '<h1>' + globalize.translate('MessageNothingHere') + '</h1>';
|
||||
html += '<p>' + globalize.translate('MessageNoTrailersFound') + '</p>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
const itemsContainer = tabContent.querySelector('.itemsContainer');
|
||||
itemsContainer.innerHTML = html;
|
||||
imageLoader.lazyChildren(itemsContainer);
|
||||
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
|
||||
loading.hide();
|
||||
isLoading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const data = {};
|
||||
let isLoading = false;
|
||||
|
||||
this.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(),
|
||||
mode: 'movies',
|
||||
serverId: ApiClient.serverId()
|
||||
});
|
||||
Events.on(filterDialog, 'filterchange', function () {
|
||||
getQuery().StartIndex = 0;
|
||||
reloadItems();
|
||||
});
|
||||
filterDialog.show();
|
||||
});
|
||||
};
|
||||
|
||||
this.getCurrentViewStyle = function () {
|
||||
return getPageData().view;
|
||||
};
|
||||
|
||||
const initPage = (tabElement) => {
|
||||
const alphaPickerElement = tabElement.querySelector('.alphaPicker');
|
||||
const itemsContainer = tabElement.querySelector('.itemsContainer');
|
||||
alphaPickerElement.addEventListener('alphavaluechanged', function (e) {
|
||||
const newValue = e.detail.value;
|
||||
const query = getQuery();
|
||||
if (newValue === '#') {
|
||||
query.NameLessThan = 'A';
|
||||
delete query.NameStartsWith;
|
||||
} else {
|
||||
query.NameStartsWith = newValue;
|
||||
delete query.NameLessThan;
|
||||
}
|
||||
query.StartIndex = 0;
|
||||
reloadItems();
|
||||
});
|
||||
this.alphaPicker = new AlphaPicker({
|
||||
element: alphaPickerElement,
|
||||
valueChangeEvent: 'click'
|
||||
});
|
||||
|
||||
tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right');
|
||||
alphaPickerElement.classList.add('alphaPicker-fixed-right');
|
||||
itemsContainer.classList.add('padded-right-withalphapicker');
|
||||
|
||||
tabElement.querySelector('.btnFilter').addEventListener('click', () => {
|
||||
this.showFilterMenu();
|
||||
});
|
||||
tabElement.querySelector('.btnSort').addEventListener('click', function (e) {
|
||||
libraryBrowser.showSortMenu({
|
||||
items: [{
|
||||
name: globalize.translate('Name'),
|
||||
id: 'SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionImdbRating'),
|
||||
id: 'CommunityRating,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionDateAdded'),
|
||||
id: 'DateCreated,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionDatePlayed'),
|
||||
id: 'DatePlayed,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionParentalRating'),
|
||||
id: 'OfficialRating,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionPlayCount'),
|
||||
id: 'PlayCount,SortName'
|
||||
}, {
|
||||
name: globalize.translate('OptionReleaseDate'),
|
||||
id: 'PremiereDate,SortName'
|
||||
}],
|
||||
callback: function () {
|
||||
getQuery().StartIndex = 0;
|
||||
reloadItems();
|
||||
},
|
||||
query: getQuery(),
|
||||
button: e.target
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
initPage(tabContent);
|
||||
|
||||
this.renderTab = () => {
|
||||
reloadItems();
|
||||
this.alphaPicker?.updateControls(getQuery());
|
||||
};
|
||||
}
|
||||
|
|
@ -180,7 +180,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(tabContent);
|
||||
});
|
||||
});
|
||||
|
@ -191,7 +191,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
let isLoading = false;
|
||||
|
||||
this.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(),
|
||||
mode: 'albums',
|
||||
|
|
|
@ -162,7 +162,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(tabContent);
|
||||
});
|
||||
});
|
||||
|
@ -172,7 +172,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
let isLoading = false;
|
||||
|
||||
this.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(tabContent),
|
||||
mode: this.mode,
|
||||
|
|
|
@ -92,7 +92,7 @@ import loading from '../../components/loading/loading';
|
|||
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(context);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -63,7 +63,7 @@ import loading from '../../components/loading/loading';
|
|||
libraryBrowser.saveQueryValues(getSavedQueryKey(), query);
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(context);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,7 +75,7 @@ import Dashboard from '../../utils/dashboard';
|
|||
imageLoader.lazyChildren(elem);
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -171,7 +171,7 @@ import Dashboard from '../../utils/dashboard';
|
|||
loadRecentlyPlayed(tabContent, parentId);
|
||||
loadFrequentlyPlayed(tabContent, parentId);
|
||||
|
||||
import('../../components/favoriteitems').then(({default: favoriteItems}) => {
|
||||
import('../../components/favoriteitems').then(({ default: favoriteItems }) => {
|
||||
favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']);
|
||||
});
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ import Dashboard from '../../utils/dashboard';
|
|||
break;
|
||||
}
|
||||
|
||||
import(`../music/${depends}`).then(({default: controllerFactory}) => {
|
||||
import(`../music/${depends}`).then(({ default: controllerFactory }) => {
|
||||
let tabContent;
|
||||
|
||||
if (index == 1) {
|
||||
|
|
|
@ -124,7 +124,7 @@ export default function (view, params, tabContent) {
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -135,7 +135,7 @@ export default function (view, params, tabContent) {
|
|||
let isLoading = false;
|
||||
|
||||
self.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(tabContent),
|
||||
mode: 'songs',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import escapeHtml from 'escape-html';
|
||||
import { playbackManager } from '../../../components/playback/playbackmanager';
|
||||
import SyncPlay from '../../../plugins/syncPlay/core';
|
||||
import browser from '../../../scripts/browser';
|
||||
import dom from '../../../scripts/dom';
|
||||
import inputManager from '../../../scripts/inputManager';
|
||||
|
@ -25,6 +24,8 @@ import SubtitleSync from '../../../components/subtitlesync/subtitlesync';
|
|||
import { appRouter } from '../../../components/appRouter';
|
||||
import LibraryMenu from '../../../scripts/libraryMenu';
|
||||
import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components/backdrop/backdrop';
|
||||
import { pluginManager } from '../../../components/pluginManager';
|
||||
import { PluginType } from '../../../types/plugin.ts';
|
||||
|
||||
/* eslint-disable indent */
|
||||
const TICKS_PER_MINUTE = 600000000;
|
||||
|
@ -64,7 +65,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
|
||||
ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(function (user) {
|
||||
if (user.Policy.EnableLiveTvManagement) {
|
||||
import('../../../components/recordingcreator/recordingbutton').then(({default: RecordingButton}) => {
|
||||
import('../../../components/recordingcreator/recordingbutton').then(({ default: RecordingButton }) => {
|
||||
if (recordingButtonManager) {
|
||||
recordingButtonManager.refreshItem(item);
|
||||
return;
|
||||
|
@ -216,7 +217,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
let title = itemName;
|
||||
if (item.PremiereDate) {
|
||||
try {
|
||||
const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false});
|
||||
const year = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false });
|
||||
title += ` (${year})`;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -622,7 +623,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
}
|
||||
|
||||
function showComingUpNext(player) {
|
||||
import('../../../components/upnextdialog/upnextdialog').then(({default: UpNextDialog}) => {
|
||||
import('../../../components/upnextdialog/upnextdialog').then(({ default: UpNextDialog }) => {
|
||||
if (!(currentVisibleMenu || currentUpNextDialog)) {
|
||||
currentVisibleMenu = 'upnext';
|
||||
comingUpNextDisplayed = true;
|
||||
|
@ -896,8 +897,8 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
const state = playbackManager.getPlayerState(player);
|
||||
|
||||
// show subtitle offset feature only if player and media support it
|
||||
const showSubOffset = playbackManager.supportSubtitleOffset(player) &&
|
||||
playbackManager.canHandleOffsetOnCurrentSubtitle(player);
|
||||
const showSubOffset = playbackManager.supportSubtitleOffset(player)
|
||||
&& playbackManager.canHandleOffsetOnCurrentSubtitle(player);
|
||||
|
||||
playerSettingsMenu.show({
|
||||
mediaType: 'Video',
|
||||
|
@ -929,7 +930,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
}
|
||||
|
||||
function toggleStats() {
|
||||
import('../../../components/playerstats/playerstats').then(({default: PlayerStats}) => {
|
||||
import('../../../components/playerstats/playerstats').then(({ default: PlayerStats }) => {
|
||||
const player = currentPlayer;
|
||||
|
||||
if (player) {
|
||||
|
@ -969,7 +970,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
});
|
||||
const positionTo = this;
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
items: menuItems,
|
||||
title: globalize.translate('Audio'),
|
||||
|
@ -1086,7 +1087,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
|
||||
const positionTo = this;
|
||||
|
||||
import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => {
|
||||
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
|
||||
actionsheet.show({
|
||||
title: globalize.translate('Subtitles'),
|
||||
items: menuItems,
|
||||
|
@ -1774,38 +1775,39 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components
|
|||
}, iconVisibilityTime);
|
||||
};
|
||||
|
||||
Events.on(SyncPlay.Manager, 'enabled', (event, enabled) => {
|
||||
if (enabled) {
|
||||
// SyncPlay enabled
|
||||
} else {
|
||||
const syncPlayIcon = view.querySelector('#syncPlayIcon');
|
||||
syncPlayIcon.style.visibility = 'hidden';
|
||||
}
|
||||
});
|
||||
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
|
||||
if (SyncPlay) {
|
||||
Events.on(SyncPlay.Manager, 'enabled', (_event, enabled) => {
|
||||
if (!enabled) {
|
||||
const syncPlayIcon = view.querySelector('#syncPlayIcon');
|
||||
syncPlayIcon.style.visibility = 'hidden';
|
||||
}
|
||||
});
|
||||
|
||||
Events.on(SyncPlay.Manager, 'notify-osd', (event, action) => {
|
||||
showIcon(action);
|
||||
});
|
||||
Events.on(SyncPlay.Manager, 'notify-osd', (_event, action) => {
|
||||
showIcon(action);
|
||||
});
|
||||
|
||||
Events.on(SyncPlay.Manager, 'group-state-update', (event, state, reason) => {
|
||||
if (state === 'Playing' && reason === 'Unpause') {
|
||||
showIcon('schedule-play');
|
||||
} else if (state === 'Playing' && reason === 'Ready') {
|
||||
showIcon('schedule-play');
|
||||
} else if (state === 'Paused' && reason === 'Pause') {
|
||||
showIcon('pause');
|
||||
} else if (state === 'Paused' && reason === 'Ready') {
|
||||
showIcon('clear');
|
||||
} else if (state === 'Waiting' && reason === 'Seek') {
|
||||
showIcon('seek');
|
||||
} else if (state === 'Waiting' && reason === 'Buffer') {
|
||||
showIcon('buffering');
|
||||
} else if (state === 'Waiting' && reason === 'Pause') {
|
||||
showIcon('wait-pause');
|
||||
} else if (state === 'Waiting' && reason === 'Unpause') {
|
||||
showIcon('wait-unpause');
|
||||
}
|
||||
});
|
||||
Events.on(SyncPlay.Manager, 'group-state-update', (_event, state, reason) => {
|
||||
if (state === 'Playing' && reason === 'Unpause') {
|
||||
showIcon('schedule-play');
|
||||
} else if (state === 'Playing' && reason === 'Ready') {
|
||||
showIcon('schedule-play');
|
||||
} else if (state === 'Paused' && reason === 'Pause') {
|
||||
showIcon('pause');
|
||||
} else if (state === 'Paused' && reason === 'Ready') {
|
||||
showIcon('clear');
|
||||
} else if (state === 'Waiting' && reason === 'Seek') {
|
||||
showIcon('seek');
|
||||
} else if (state === 'Waiting' && reason === 'Buffer') {
|
||||
showIcon('buffering');
|
||||
} else if (state === 'Waiting' && reason === 'Pause') {
|
||||
showIcon('wait-pause');
|
||||
} else if (state === 'Waiting' && reason === 'Unpause') {
|
||||
showIcon('wait-unpause');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
|
|
|
@ -54,7 +54,7 @@ import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionSta
|
|||
view.querySelector('.addServerForm').addEventListener('submit', onServerSubmit);
|
||||
view.querySelector('.btnCancel').addEventListener('click', goBack);
|
||||
|
||||
import('../../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(view);
|
||||
});
|
||||
|
||||
|
@ -65,7 +65,7 @@ import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionSta
|
|||
}
|
||||
|
||||
function goBack() {
|
||||
import('../../../components/appRouter').then(({appRouter}) => {
|
||||
import('../../../components/appRouter').then(({ appRouter }) => {
|
||||
appRouter.back();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -201,7 +201,7 @@ import './login.scss';
|
|||
view.querySelector('.manualLoginForm').classList.add('hide');
|
||||
view.querySelector('.btnManual').classList.remove('hide');
|
||||
|
||||
import('../../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(view);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -162,7 +162,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -173,7 +173,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
let isLoading = false;
|
||||
|
||||
self.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(tabContent),
|
||||
mode: 'episodes',
|
||||
|
|
|
@ -269,7 +269,7 @@ import autoFocuser from '../../components/autoFocuser';
|
|||
break;
|
||||
}
|
||||
|
||||
import(`../shows/${depends}`).then(({default: controllerFactory}) => {
|
||||
import(`../shows/${depends}`).then(({ default: controllerFactory }) => {
|
||||
let tabContent;
|
||||
|
||||
if (index === 1) {
|
||||
|
|
|
@ -191,7 +191,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
loading.hide();
|
||||
isLoading = false;
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(page);
|
||||
});
|
||||
});
|
||||
|
@ -201,7 +201,7 @@ import '../../elements/emby-itemscontainer/emby-itemscontainer';
|
|||
let isLoading = false;
|
||||
|
||||
this.showFilterMenu = function () {
|
||||
import('../../components/filterdialog/filterdialog').then(({default: filterDialogFactory}) => {
|
||||
import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => {
|
||||
const filterDialog = new filterDialogFactory({
|
||||
query: getQuery(tabContent),
|
||||
mode: 'series',
|
||||
|
|
|
@ -50,7 +50,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
|
|||
});
|
||||
loading.hide();
|
||||
|
||||
import('../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(context);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,7 +21,7 @@ export default function (view) {
|
|||
view.querySelector('form').addEventListener('submit', submit);
|
||||
view.querySelector('.btnSave').classList.remove('hide');
|
||||
|
||||
import('../../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(view);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -68,7 +68,7 @@ export default function (view, params) {
|
|||
page.querySelector('.lnkControlsPreferences').classList.add('hide');
|
||||
}
|
||||
|
||||
import('../../../components/autoFocuser').then(({default: autoFocuser}) => {
|
||||
import('../../../components/autoFocuser').then(({ default: autoFocuser }) => {
|
||||
autoFocuser.autoFocus(view);
|
||||
});
|
||||
});
|
||||
|
|
69
src/controllers/user/profile/index.html
Normal file
69
src/controllers/user/profile/index.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
<div id="userImagePage" data-role="page" class="page libraryPage userPreferencesPage userPasswordPage noSecondaryNavPage" data-title="${Profile}" data-menubutton="false">
|
||||
<div class="padded-left padded-right padded-bottom-page">
|
||||
<div class="readOnlyContent" style="margin: 0 auto; padding: 0 1em;">
|
||||
<div style="position:relative;display:inline-block;max-width:200px;">
|
||||
<input id="uploadImage" type="file" accept="image/*" style="position:absolute;right:0;width:100%;height:100%;opacity:0;" />
|
||||
<div id="image" style="width:200px;height:200px;background-repeat:no-repeat;background-position:center;border-radius:100%;background-size:cover;"></div>
|
||||
</div>
|
||||
<div style="vertical-align:top;margin:1em 2em;display:inline-block;">
|
||||
<h2 class="username" style="margin:0;font-size:xx-large;"></h2>
|
||||
<br/>
|
||||
<button is="emby-button" type="button" class="raised hide" id="btnAddImage">
|
||||
<span>${ButtonAddImage}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" class="raised hide" id="btnDeleteImage">
|
||||
<span>${DeleteImage}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="updatePasswordForm passwordSection userProfileSettingsForm hide" style="margin: 3em auto 0;">
|
||||
<div class="verticalSection">
|
||||
<h2 class="sectionTitle">
|
||||
${HeaderPassword}
|
||||
</h2>
|
||||
<div id="fldCurrentPassword" class="inputContainer hide">
|
||||
<input is="emby-input" type="password" id="txtCurrentPassword" label="${LabelCurrentPassword}" autocomplete="off" />
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="password" id="txtNewPassword" label="${LabelNewPassword}" autocomplete="off" />
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="password" id="txtNewPasswordConfirm" label="${LabelNewPasswordConfirm}" autocomplete="off" />
|
||||
</div>
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnResetPassword" class="raised cancel block hide">
|
||||
<span>${ResetPassword}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form class="localAccessForm localAccessSection userProfileSettingsForm hide" style="margin: 3em auto 0;">
|
||||
<div class="verticalSection">
|
||||
<h2 class="sectionTitle">${HeaderEasyPinCode}</h2>
|
||||
<div>${EasyPasswordHelp}</div>
|
||||
<br />
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtEasyPassword" label="${LabelEasyPinCode}" autocomplete="off" pattern="[0-9]*" step="1" maxlength="5" />
|
||||
</div>
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkEnableLocalEasyPassword" />
|
||||
<span>${LabelInNetworkSignInWithEasyPassword}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${LabelInNetworkSignInWithEasyPasswordHelp}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Save}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" id="btnResetEasyPassword" class="raised cancel block hide">
|
||||
<span>${ButtonResetEasyPassword}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
105
src/controllers/user/profile/index.js
Normal file
105
src/controllers/user/profile/index.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
import UserPasswordPage from '../../dashboard/users/userpasswordpage';
|
||||
import loading from '../../../components/loading/loading';
|
||||
import libraryMenu from '../../../scripts/libraryMenu';
|
||||
import { appHost } from '../../../components/apphost';
|
||||
import globalize from '../../../scripts/globalize';
|
||||
import '../../../elements/emby-button/emby-button';
|
||||
import Dashboard from '../../../utils/dashboard';
|
||||
import toast from '../../../components/toast/toast';
|
||||
import confirm from '../../../components/confirm/confirm';
|
||||
import { getParameterByName } from '../../../utils/url.ts';
|
||||
|
||||
function reloadUser(page) {
|
||||
const userId = getParameterByName('userId');
|
||||
loading.show();
|
||||
ApiClient.getUser(userId).then(function (user) {
|
||||
page.querySelector('.username').innerText = user.Name;
|
||||
libraryMenu.setTitle(user.Name);
|
||||
|
||||
let imageUrl = 'assets/img/avatar.png';
|
||||
if (user.PrimaryImageTag) {
|
||||
imageUrl = ApiClient.getUserImageUrl(user.Id, {
|
||||
tag: user.PrimaryImageTag,
|
||||
type: 'Primary'
|
||||
});
|
||||
}
|
||||
|
||||
const userImage = page.querySelector('#image');
|
||||
userImage.style.backgroundImage = 'url(' + imageUrl + ')';
|
||||
|
||||
Dashboard.getCurrentUser().then(function (loggedInUser) {
|
||||
if (user.PrimaryImageTag) {
|
||||
page.querySelector('#btnAddImage').classList.add('hide');
|
||||
page.querySelector('#btnDeleteImage').classList.remove('hide');
|
||||
} else if (appHost.supports('fileinput') && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||
page.querySelector('#btnDeleteImage').classList.add('hide');
|
||||
page.querySelector('#btnAddImage').classList.remove('hide');
|
||||
}
|
||||
});
|
||||
loading.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function onFileReaderError(evt) {
|
||||
loading.hide();
|
||||
switch (evt.target.error.code) {
|
||||
case evt.target.error.NOT_FOUND_ERR:
|
||||
toast(globalize.translate('FileNotFound'));
|
||||
break;
|
||||
case evt.target.error.ABORT_ERR:
|
||||
onFileReaderAbort();
|
||||
break;
|
||||
case evt.target.error.NOT_READABLE_ERR:
|
||||
default:
|
||||
toast(globalize.translate('FileReadError'));
|
||||
}
|
||||
}
|
||||
|
||||
function onFileReaderAbort() {
|
||||
loading.hide();
|
||||
toast(globalize.translate('FileReadCancelled'));
|
||||
}
|
||||
|
||||
function setFiles(page, files) {
|
||||
const userImage = page.querySelector('#image');
|
||||
const file = files[0];
|
||||
|
||||
if (!file || !file.type.match('image.*')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onerror = onFileReaderError;
|
||||
reader.onabort = onFileReaderAbort;
|
||||
reader.onload = function (evt) {
|
||||
userImage.style.backgroundImage = 'url(' + evt.target.result + ')';
|
||||
const userId = getParameterByName('userId');
|
||||
ApiClient.uploadUserImage(userId, 'Primary', file).then(function () {
|
||||
loading.hide();
|
||||
reloadUser(page);
|
||||
});
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
export default function (view, params) {
|
||||
reloadUser(view);
|
||||
new UserPasswordPage(view, params);
|
||||
view.querySelector('#btnDeleteImage').addEventListener('click', function () {
|
||||
confirm(globalize.translate('DeleteImageConfirmation'), globalize.translate('DeleteImage')).then(function () {
|
||||
loading.show();
|
||||
const userId = getParameterByName('userId');
|
||||
ApiClient.deleteUserImage(userId, 'primary').then(function () {
|
||||
loading.hide();
|
||||
reloadUser(view);
|
||||
});
|
||||
});
|
||||
});
|
||||
view.querySelector('#btnAddImage').addEventListener('click', function () {
|
||||
view.querySelector('#uploadImage').click();
|
||||
});
|
||||
view.querySelector('#uploadImage').addEventListener('change', function (evt) {
|
||||
setFiles(view, evt.target.files);
|
||||
});
|
||||
}
|
|
@ -13,7 +13,7 @@ type IProps = {
|
|||
isLinkVisible?: boolean;
|
||||
url?: string;
|
||||
}
|
||||
const SectionTitleContainer: FunctionComponent<IProps> = ({SectionClassName, title, isBtnVisible = false, btnId, btnClassName, btnTitle, btnIcon, isLinkVisible = true, url}: IProps) => {
|
||||
const SectionTitleContainer: FunctionComponent<IProps> = ({ SectionClassName, title, isBtnVisible = false, btnId, btnClassName, btnTitle, btnIcon, isLinkVisible = true, url }: IProps) => {
|
||||
return (
|
||||
<div className={`${SectionClassName} sectionTitleContainer flex align-items-center`}>
|
||||
<h2 className='sectionTitle'>
|
||||
|
|
|
@ -72,7 +72,7 @@ import Sortable from 'sortablejs';
|
|||
}
|
||||
|
||||
const self = this;
|
||||
import('../../components/multiSelect/multiSelect').then(({default: MultiSelect}) => {
|
||||
import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => {
|
||||
self.multiSelect = new MultiSelect({
|
||||
container: self,
|
||||
bindOnClick: false
|
||||
|
|
|
@ -229,8 +229,8 @@ const Scroller: FC<ScrollerProps> = ({
|
|||
return (
|
||||
<>
|
||||
{
|
||||
showControls && scrollState.scrollWidth > scrollState.scrollSize + 20 &&
|
||||
<ScrollButtons
|
||||
showControls && scrollState.scrollWidth > scrollState.scrollSize + 20
|
||||
&& <ScrollButtons
|
||||
scrollRef={scrollRef}
|
||||
scrollerFactoryRef={scrollerFactoryRef}
|
||||
scrollState={scrollState}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue