Merge branch 'master' into enable-airplay-audioplayer

This commit is contained in:
stamatovg 2023-04-20 12:00:25 +03:00 committed by GitHub
commit 9a69156487
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
264 changed files with 53855 additions and 48500 deletions

View file

@ -6,7 +6,6 @@
"./dist/libraries/pdf.worker.js",
"./dist/libraries/worker-bundle.js",
"./dist/libraries/wasm-gen/libarchive.js",
"./dist/node_modules.@jellyfin.libass-wasm.*.chunk.js",
"./dist/serviceworker.js"
]
}

6
.github/CODEOWNERS vendored
View file

@ -1,6 +1,2 @@
.ci @dkanada @EraYaN
* @jellyfin/web
.github @jellyfin/core
fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
deployment @joshuaboniface

View file

@ -19,13 +19,13 @@ jobs:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Initialize CodeQL
uses: github/codeql-action/init@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9
uses: github/codeql-action/init@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9
uses: github/codeql-action/autobuild@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9
uses: github/codeql-action/analyze@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12

View file

@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify as seen
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1
uses: peter-evans/create-or-update-comment@3383acd359705b10cb1eeef05c0e88c056ea4666 # v3.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
@ -26,3 +26,11 @@ jobs:
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
- name: Comment on failure
if: failure()
uses: peter-evans/create-or-update-comment@3383acd359705b10cb1eeef05c0e88c056ea4666 # v3.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
I'm sorry @${{ github.event.comment.user.login }}, I'm afraid I can't do that.

View file

@ -13,7 +13,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@ -37,7 +37,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@ -58,7 +58,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@ -82,7 +82,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0

View file

@ -13,7 +13,7 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0

View file

@ -60,6 +60,8 @@
- [edvwib](https://github.com/edvwib)
- [Rob Farraher](https://github.com/farraherbg)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme)
- [Anantharaju S](https://github.com/Anantharajus)
- [Merlin Sievers](https://github.com/dann-merlin)
# Emby Contributors

View file

@ -12,14 +12,7 @@ module.exports = {
corejs: 3
}
],
'@babel/preset-react',
[
'@babel/preset-typescript',
{
isTSX: true,
allExtensions: true
}
]
'@babel/preset-react'
],
plugins: [
'@babel/plugin-proposal-class-properties',

View file

@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare CentOS environment
RUN yum update -y \
&& yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core autoconf automake glibc-devel gcc-c++ make \
&& yum install -y rpmdevtools git autoconf automake glibc-devel gcc-c++ make \
&& curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - \
&& yum install -y nodejs

5152
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,22 +5,21 @@
"repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@babel/core": "7.21.3",
"@babel/core": "7.21.4",
"@babel/eslint-parser": "7.21.3",
"@babel/eslint-plugin": "7.19.1",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-private-methods": "7.18.6",
"@babel/plugin-transform-modules-umd": "7.18.6",
"@babel/preset-env": "7.20.2",
"@babel/preset-env": "7.21.4",
"@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.21.0",
"@types/escape-html": "1.0.2",
"@types/loadable__component": "5.13.4",
"@types/lodash-es": "4.17.7",
"@types/react": "17.0.53",
"@types/react": "17.0.58",
"@types/react-dom": "17.0.19",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.56.0",
"@typescript-eslint/eslint-plugin": "5.58.0",
"@typescript-eslint/parser": "5.58.0",
"@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.14",
"babel-loader": "9.1.2",
@ -30,10 +29,10 @@
"copy-webpack-plugin": "11.0.0",
"cross-env": "7.0.3",
"css-loader": "6.7.3",
"cssnano": "5.1.15",
"es-check": "7.1.0",
"eslint": "8.36.0",
"eslint-plugin-compat": "4.1.2",
"cssnano": "6.0.0",
"es-check": "7.1.1",
"eslint": "8.38.0",
"eslint-plugin-compat": "4.1.4",
"eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
@ -46,23 +45,23 @@
"html-webpack-plugin": "5.5.0",
"mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.21",
"postcss-loader": "7.1.0",
"postcss-preset-env": "8.0.1",
"postcss-loader": "7.2.4",
"postcss-preset-env": "8.3.1",
"postcss-scss": "4.0.6",
"sass": "1.60.0",
"sass-loader": "13.2.1",
"sass": "1.62.0",
"sass-loader": "13.2.2",
"source-map-loader": "4.0.1",
"style-loader": "3.3.2",
"stylelint": "15.3.0",
"stylelint": "15.4.0",
"stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "6.0.3",
"stylelint-scss": "4.5.0",
"stylelint-scss": "4.6.0",
"ts-loader": "9.4.2",
"typescript": "5.0.2",
"webpack": "5.76.3",
"typescript": "5.0.4",
"webpack": "5.79.0",
"webpack-cli": "5.0.1",
"webpack-dev-server": "4.13.1",
"webpack-dev-server": "4.13.2",
"webpack-merge": "5.8.0",
"workbox-webpack-plugin": "6.5.4",
"worker-loader": "3.0.8"
@ -74,23 +73,24 @@
"@fontsource/noto-sans-kr": "4.5.12",
"@fontsource/noto-sans-sc": "4.5.12",
"@fontsource/noto-sans-tc": "4.5.12",
"@jellyfin/libass-wasm": "4.1.1",
"@jellyfin/sdk": "unstable",
"@loadable/component": "5.15.3",
"blurhash": "2.0.5",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.3.2",
"core-js": "3.29.1",
"core-js": "3.30.0",
"date-fns": "2.29.3",
"dompurify": "3.0.1",
"epubjs": "0.3.93",
"escape-html": "1.0.3",
"event-target-polyfill": "github:ThaUnknown/event-target-polyfill",
"fast-text-encoding": "1.0.6",
"flv.js": "1.6.2",
"headroom.js": "0.12.0",
"history": "5.3.0",
"hls.js": "1.2.4",
"hls.js": "1.4.0",
"intersection-observer": "0.12.2",
"jassub": "1.5.12",
"jellyfin-apiclient": "1.10.0",
"jquery": "3.6.4",
"jstree": "3.3.15",
@ -99,10 +99,10 @@
"marked": "4.3.0",
"material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1",
"pdfjs-dist": "2.16.105",
"pdfjs-dist": "3.5.141",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "6.9.0",
"react-router-dom": "6.10.0",
"resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2",
"sortablejs": "1.15.0",
@ -131,8 +131,8 @@
"scripts": {
"start": "npm run serve",
"serve": "webpack serve --config webpack.dev.js",
"build:development": "webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js",
"build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js",
"build:check": "tsc --noEmit",
"escheck": "es-check",
"lint": "eslint \"./\"",

View file

@ -1,15 +1,38 @@
import { History } from '@remix-run/router';
import React from 'react';
import React, { useEffect } 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');
useEffect(() => {
Promise.all([
// Initialize the UI components after first render
import('./scripts/libraryMenu'),
import('./scripts/autoBackdrops')
]);
}, []);
return (
<ApiProvider>
<HistoryRouter history={history}>
<AppRoutes />
<div className='backdropContainer' />
<div className='backgroundContainer' />
<div className='mainDrawer hide'>
<div className='mainDrawer-scrollContainer scrollContainer focuscontainer-y' />
</div>
<div className='skinHeader focuscontainer-x' />
<div className='mainAnimatedPages skinBody' />
<div className='skinBody'>
{layoutMode === 'experimental' ? <ExperimentalAppRoutes /> : <AppRoutes /> }
</div>
<div className='mainDrawerHandle' />
</HistoryRouter>
</ApiProvider>
);

View file

@ -1,6 +1,3 @@
/* eslint-disable indent */
/**
* Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule
@ -91,8 +88,6 @@ import template from './accessSchedule.template.html';
});
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -11,8 +11,6 @@ import alert from './alert';
import { getLocale } from '../utils/dateFnsLocale.ts';
import { toBoolean } from '../utils/string.ts';
/* eslint-disable indent */
function getEntryHtml(entry, apiClient) {
let html = '';
html += '<div class="listItem listItem-border">';
@ -169,5 +167,3 @@ class ActivityLog {
}
export default ActivityLog;
/* eslint-enable indent */

View file

@ -3,8 +3,6 @@ import browser from '../scripts/browser';
import dialog from './dialog/dialog';
import globalize from '../scripts/globalize';
/* eslint-disable indent */
function useNativeAlert() {
// webOS seems to block modals
// Tizen 2.x seems to block modals
@ -43,5 +41,3 @@ import globalize from '../scripts/globalize';
return dialog.show(options);
}
}
/* eslint-enable indent */

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
/**
* Module alphaPicker.
* @module components/alphaPicker/alphaPicker
@ -320,5 +318,4 @@ import 'material-design-icons-iconfont';
}
}
/* eslint-enable indent */
export default AlphaPicker;

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
/**
* Module for performing auto-focus.
* @module components/autoFocuser
@ -94,8 +92,6 @@ import layoutManager from './layoutManager';
return focusedElement;
}
/* eslint-enable indent */
export default {
isEnabled: isEnabled,
enable: enable,

View file

@ -7,8 +7,6 @@ import ServerConnections from '../ServerConnections';
import './backdrop.scss';
/* eslint-disable indent */
function enableAnimation() {
return !browser.slow;
}
@ -282,8 +280,6 @@ import './backdrop.scss';
}
}
/* eslint-enable indent */
/**
* @enum TransparencyLevel
*/

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for building cards from item data.
@ -1753,8 +1752,6 @@ import { appRouter } from '../appRouter';
}
}
/* eslint-enable indent */
export default {
getCardsHtml: getCardsHtml,
getDefaultBackgroundClass: getDefaultBackgroundClass,

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for building cards from item data.
@ -128,8 +127,6 @@ import ServerConnections from '../ServerConnections';
imageLoader.lazyChildren(options.itemsContainer);
}
/* eslint-enable indent */
export default {
buildChapterCards: buildChapterCards
};

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for building cards from item data.
@ -20,8 +19,6 @@ import cardBuilder from './cardBuilder';
cardBuilder.buildCards(items, options);
}
/* eslint-enable indent */
export default {
buildPeopleCards: buildPeopleCards
};

View file

@ -16,8 +16,6 @@ import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
/* eslint-disable indent */
let currentServerId;
function onSubmit(e) {
@ -265,5 +263,4 @@ import toast from '../toast/toast';
}
}
/* eslint-enable indent */
export default CollectionEditor;

View file

@ -13,8 +13,6 @@ import '../formdialog.scss';
import '../../styles/flexstyles.scss';
import template from './dialog.template.html';
/* eslint-disable indent */
function showDialog(options = { dialogOptions: {}, buttons: [] }) {
const dialogOptions = {
removeOnClose: true,
@ -135,7 +133,6 @@ import template from './dialog.template.html';
return showDialog(options);
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -9,8 +9,6 @@ import dom from '../../scripts/dom';
import './dialoghelper.scss';
import '../../styles/scrollstyles.scss';
/* eslint-disable indent */
let globalOnOpenCallback;
function enableAnimation() {
@ -250,7 +248,6 @@ import '../../styles/scrollstyles.scss';
}
function isOpened(dlg) {
//return dlg.opened;
return !dlg.classList.contains('hide');
}
@ -508,8 +505,6 @@ import '../../styles/scrollstyles.scss';
globalOnOpenCallback = val;
}
/* eslint-enable indent */
export default {
open: open,
close: close,

View file

@ -18,8 +18,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './displaySettings.template.html';
/* eslint-disable indent */
function fillThemes(select, selectedTheme) {
skinManager.getThemes().then(themes => {
select.innerHTML = themes.map(t => {
@ -251,5 +249,4 @@ import template from './displaySettings.template.html';
}
}
/* eslint-enable indent */
export default DisplaySettings;

View file

@ -9,8 +9,6 @@ import { getParameterByName } from '../utils/url.ts';
import '../styles/scrollstyles.scss';
import '../elements/emby-itemscontainer/emby-itemscontainer';
/* eslint-disable indent */
function enableScrollX() {
return !layoutManager.desktop;
}
@ -240,5 +238,3 @@ import '../elements/emby-itemscontainer/emby-itemscontainer';
export default {
render: loadSections
};
/* eslint-enable indent */

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
export function getFetchPromise(request) {
const headers = request.headers || {};
@ -107,4 +106,3 @@
throw err;
});
}
/* eslint-enable indent */

View file

@ -8,7 +8,6 @@ import './style.scss';
import ServerConnections from '../ServerConnections';
import template from './filterdialog.template.html';
/* eslint-disable indent */
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
const elem = context.querySelector(selector);
if (items.length) {
@ -419,6 +418,4 @@ import template from './filterdialog.template.html';
}
}
/* eslint-enable indent */
export default FilterDialog;

View file

@ -297,10 +297,8 @@ class FilterMenu {
}
if (submitted) {
//if (!options.onChange) {
saveValues(dlg, options.settings, options.settingsKey, options.setfilters);
return resolve();
//}
}
return resolve();
});

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
import dom from '../scripts/dom';
import scrollManager from './scrollManager';
@ -464,8 +462,6 @@ import scrollManager from './scrollManager';
}
}
/* eslint-enable indent */
export default {
autoFocus: autoFocus,
focus: focus,

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
import dom from '../scripts/dom';
import { appRouter } from './appRouter';
import Dashboard from '../utils/dashboard';
@ -43,5 +41,3 @@ import ServerConnections from './ServerConnections';
onGroupedCardClick(e, groupedCard);
}
}
/* eslint-enable indent */

View file

@ -14,8 +14,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './homeScreenSettings.template.html';
/* eslint-disable indent */
const numConfigurableSections = 7;
function renderViews(page, user, result) {
@ -506,6 +504,4 @@ import template from './homeScreenSettings.template.html';
}
}
/* eslint-enable indent */
export default HomeScreenSettings;

View file

@ -13,8 +13,6 @@ import './homesections.scss';
import Dashboard from '../../utils/dashboard';
import ServerConnections from '../ServerConnections';
/* eslint-disable indent */
export function getDefaultSection(index) {
switch (index) {
case 0:
@ -750,4 +748,3 @@ export default {
resume: resume
};
/* eslint-enable indent */

View file

@ -1,6 +1,3 @@
/* eslint-disable indent */
import appSettings from '../scripts/settings/appSettings' ;
import browser from '../scripts/browser';
import Events from '../utils/events.ts';
@ -51,18 +48,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;
@ -391,5 +380,3 @@ import Events from '../utils/events.ts';
return ranges;
}
/* eslint-enable indent */

View file

@ -15,8 +15,6 @@ import '../cardbuilder/card.scss';
import ServerConnections from '../ServerConnections';
import template from './imageDownloader.template.html';
/* eslint-disable indent */
const enableFocusTransform = !browser.slow && !browser.edge;
let currentItemId;
@ -397,4 +395,3 @@ export default {
show: show
};
/* eslint-enable indent */

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for image Options Editor.
@ -109,5 +108,4 @@ export class editor {
}
}
/* eslint-enable indent */
export default editor;

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for imageUploader.
@ -181,7 +180,6 @@ import template from './imageUploader.template.html';
});
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -18,8 +18,6 @@ import alert from '../alert';
import confirm from '../confirm/confirm';
import template from './imageeditor.template.html';
/* eslint-disable indent */
const enableFocusTransform = !browser.slow && !browser.edge;
let currentItem;
@ -465,4 +463,3 @@ export default {
show
};
/* eslint-enable indent */

View file

@ -17,7 +17,6 @@ worker.addEventListener(
}
}
);
/* eslint-disable indent */
export function lazyImage(elem, source = elem.getAttribute('data-src')) {
if (!source) {
@ -247,7 +246,6 @@ worker.addEventListener(
lazyImage(element);
}
/* eslint-enable indent */
export default {
setLazyImage: setLazyImage,
fillImages: fillImages,

View file

@ -9,7 +9,6 @@ import { playbackManager } from './playback/playbackmanager';
import ServerConnections from './ServerConnections';
import toast from './toast/toast';
/* eslint-disable indent */
export function getCommands(options) {
const item = options.item;
const user = options.user;
@ -638,8 +637,6 @@ import toast from './toast/toast';
});
}
/* eslint-enable indent */
export default {
getCommands: getCommands,
show: show

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for display media info.
@ -252,7 +251,6 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
return loadMediaInfo(itemId, serverId);
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for itemidentifier media item.
@ -489,7 +488,6 @@ import datetime from '../../scripts/datetime';
});
}
/* eslint-enable indent */
export default {
show: show,
showFindNew: showFindNew

View file

@ -1,4 +1,4 @@
/* eslint-disable indent */
export class LazyLoader {
constructor(options) {
this.options = options;
@ -63,7 +63,6 @@
unveilElements(elem.getElementsByClassName('lazy'), elem, callback);
}
/* eslint-enable indent */
export default {
LazyLoader: LazyLoader,
lazyChildren: lazyChildren

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for library options editor.
@ -613,7 +612,6 @@ import template from './libraryoptionseditor.template.html';
let currentLibraryOptions;
let currentAvailableOptions;
/* eslint-enable indent */
export default {
embed: embed,
setContentType: setContentType,

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for display list view.
@ -495,7 +494,6 @@ import ServerConnections from '../ServerConnections';
return outerHtml;
}
/* eslint-enable indent */
export default {
getListViewHtml: getListViewHtml
};

View file

@ -4,8 +4,6 @@ import Events from '../utils/events.ts';
import '../elements/emby-tabs/emby-tabs';
import '../elements/emby-button/emby-button';
/* eslint-disable indent */
let tabOwnerView;
const queryScope = document.querySelector('.skinHeader');
let headerTabsContainer;
@ -199,5 +197,3 @@ import '../elements/emby-button/emby-button';
export function getTabsElement() {
return document.querySelector('.tabs-viewmenubar');
}
/* eslint-enable indent */

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for media library creator.
@ -87,7 +86,9 @@ import template from './mediaLibraryCreator.template.html';
const index = this.selectedIndex;
if (index != -1) {
const name = this.options[index].innerHTML.replace('*', '').replace('&amp;', '&');
const name = this.options[index].innerHTML
.replaceAll('*', '')
.replaceAll('&amp;', '&');
$('#txtValue', dlg).val(name);
}
}
@ -223,5 +224,4 @@ export class showEditor {
let hasChanges = false;
let isCreating = false;
/* eslint-enable indent */
export default showEditor;

View file

@ -1,4 +1,3 @@
/* eslint-disable indent */
/**
* Module for media library editor.
@ -232,5 +231,4 @@ export class showEditor {
let hasChanges = false;
let isCreating = false;
/* eslint-enable indent */
export default showEditor;

View file

@ -10,7 +10,6 @@ import '../guide/programs.scss';
import '../../elements/emby-button/emby-button';
import * as userSettings from '../../scripts/settings/userSettings';
/* eslint-disable indent */
function getTimerIndicator(item) {
let status;
@ -577,8 +576,6 @@ import * as userSettings from '../../scripts/settings/userSettings';
return list;
}
/* eslint-enable indent */
export default {
getMediaInfoHtml: getPrimaryMediaInfoHtml,
getEndsAt: getEndsAt,

View file

@ -23,8 +23,6 @@ import toast from '../toast/toast';
import { appRouter } from '../appRouter';
import template from './metadataEditor.template.html';
/* eslint-disable indent */
let currentContext;
let metadataEditorInfo;
let currentItem;
@ -1113,4 +1111,3 @@ import template from './metadataEditor.template.html';
}
};
/* eslint-enable indent */

View file

@ -8,8 +8,6 @@ import '../../elements/emby-select/emby-select';
import '../formdialog.scss';
import template from './personEditor.template.html';
/* eslint-disable indent */
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
@ -98,4 +96,3 @@ export default {
show: show
};
/* eslint-enable indent */

View file

@ -11,8 +11,6 @@ import confirm from '../confirm/confirm';
import itemHelper from '../itemHelper';
import datetime from '../../scripts/datetime';
/* eslint-disable indent */
let selectedItems = [];
let selectedElements = [];
let currentSelectionCommandsPanel;
@ -205,13 +203,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) {
@ -573,4 +564,3 @@ import datetime from '../../scripts/datetime';
};
}
/* eslint-enable indent */

View file

@ -2,6 +2,7 @@ import serverNotifications from '../../scripts/serverNotifications';
import { playbackManager } from '../playback/playbackmanager';
import Events from '../../utils/events.ts';
import globalize from '../../scripts/globalize';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
import NotificationIcon from './notificationicon.png';
@ -130,7 +131,7 @@ function onLibraryChanged(data, apiClient) {
newItems.length = 12;
}
apiClient.getItems(apiClient.getCurrentUserId(), {
getItems(apiClient, apiClient.getCurrentUserId(), {
Recursive: true,
Limit: 3,

View file

@ -17,8 +17,6 @@ import './nowPlayingBar.scss';
import '../../elements/emby-slider/emby-slider';
import { appRouter } from '../appRouter';
/* eslint-disable indent */
let currentPlayer;
let currentPlayerSupportedCommands = [];
@ -172,19 +170,23 @@ import { appRouter } from '../appRouter';
elem.querySelector('.previousTrackButton').addEventListener('click', function (e) {
if (currentPlayer) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) {
// Cancel this event if doubleclick is fired
if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') {
// Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event
if (e.detail > 1 ) {
return;
}
// Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move
// to the previous track, unless we are at the first track so no previous track exists.
if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) {
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager, however, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
} else {
playbackManager.previousTrack(currentPlayer);
return;
}
}
playbackManager.previousTrack(currentPlayer);
}
});
elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () {
@ -792,5 +794,3 @@ import { appRouter } from '../appRouter';
}
}
});
/* eslint-enable indent */

View file

@ -1,6 +1,5 @@
import appSettings from '../scripts/settings/appSettings';
import { pluginManager } from './pluginManager';
/* eslint-disable indent */
class PackageManager {
#packagesList = [];
@ -135,6 +134,4 @@ import { pluginManager } from './pluginManager';
}
}
/* eslint-enable indent */
export default new PackageManager();

View file

@ -4,8 +4,6 @@ import Events from '../../utils/events.ts';
import ServerConnections from '../ServerConnections';
import shell from '../../scripts/shell';
/* eslint-disable indent */
// Reports media playback to the device for lock screen control
let currentPlayer;
@ -259,4 +257,3 @@ import shell from '../../scripts/shell';
bindToPlayer(playbackManager.getCurrentPlayer());
/* eslint-enable indent */

View file

@ -13,6 +13,7 @@ import ServerConnections from '../ServerConnections';
import alert from '../alert';
import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
const UNLIMITED_ITEMS = -1;
@ -127,7 +128,7 @@ function getItemsForPlayback(serverId, query) {
query.EnableTotalRecordCount = false;
query.CollapseBoxSetItems = false;
return apiClient.getItems(apiClient.getCurrentUserId(), query);
return getItems(apiClient, apiClient.getCurrentUserId(), query);
}
}
@ -1038,7 +1039,6 @@ class PlaybackManager {
}
}
//var mediaType = item.MediaType;
return getPlayer(item, getDefaultPlayOptions()) != null;
};

View file

@ -12,8 +12,6 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast';
import template from './playbackSettings.template.html';
/* eslint-disable indent */
function fillSkipLengths(select) {
const options = [5, 10, 15, 20, 25, 30];
@ -359,5 +357,4 @@ import template from './playbackSettings.template.html';
}
}
/* eslint-enable indent */
export default PlaybackSettings;

View file

@ -9,8 +9,6 @@ import { PluginType } from '../../types/plugin.ts';
import './playerstats.scss';
import ServerConnections from '../ServerConnections';
/* eslint-disable indent */
function init(instance) {
const parent = document.createElement('div');
@ -527,6 +525,4 @@ class PlayerStats {
}
}
/* eslint-enable indent */
export default PlayerStats;

View file

@ -18,8 +18,6 @@ import 'material-design-icons-iconfont';
import '../formdialog.scss';
import ServerConnections from '../ServerConnections';
/* eslint-disable indent */
let currentServerId;
function onSubmit(e) {
@ -282,5 +280,4 @@ import ServerConnections from '../ServerConnections';
}
}
/* eslint-enable indent */
export default showEditor;

View file

@ -325,10 +325,11 @@ export default function () {
if (layoutManager.mobile) {
const playingVideo = playbackManager.isPlayingVideo() && item !== null;
const playingAudio = !playbackManager.isPlayingVideo() && item !== null;
buttonVisible(context.querySelector('.btnRepeat'), playingAudio);
buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio);
buttonVisible(context.querySelector('.btnRewind'), playingVideo);
buttonVisible(context.querySelector('.btnFastForward'), playingVideo);
const playingAudioBook = playingAudio && item.Type == 'AudioBook';
buttonVisible(context.querySelector('.btnRepeat'), playingAudio && !playingAudioBook);
buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio && !playingAudioBook);
buttonVisible(context.querySelector('.btnRewind'), playingVideo || playingAudioBook);
buttonVisible(context.querySelector('.btnFastForward'), playingVideo || playingAudioBook);
buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnShuffleQueue'), playingVideo);
buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnRepeat'), playingVideo);
} else {
@ -763,19 +764,23 @@ export default function () {
context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) {
if (currentPlayer) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) {
// Cancel this event if doubleclick is fired
if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') {
// Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event
if (e.detail > 1 ) {
return;
}
// Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move
// to the previous track, unless we are at the first track so no previous track exists.
if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) {
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager. However, setting this here gives instant visual feedback.
// This is done automatically by playbackManager, however, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
} else {
playbackManager.previousTrack(currentPlayer);
return;
}
}
playbackManager.previousTrack(currentPlayer);
}
});
context.querySelector('.btnPreviousTrack').addEventListener('dblclick', function () {

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
/**
* Module for controlling scroll behavior.
* @module components/scrollManager
@ -600,8 +598,6 @@ import layoutManager from './layoutManager';
}, { capture: true });
}
/* eslint-enable indent */
export default {
isEnabled: isEnabled,
scrollTo: scrollTo,

View file

@ -1,5 +1,3 @@
/* eslint-disable indent */
/**
* Module shortcuts.
* @module components/shortcuts
@ -392,8 +390,6 @@ import toast from './toast/toast';
return html;
}
/* eslint-enable indent */
export default {
on: on,
off: off,

View 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;

View file

@ -10,8 +10,6 @@ import './upnextdialog.scss';
import '../../elements/emby-button/emby-button';
import '../../styles/flexstyles.scss';
/* eslint-disable indent */
const transitionEndEventName = dom.whichTransitionEvent();
function getHtml() {
@ -69,9 +67,9 @@ import '../../styles/flexstyles.scss';
const elem = instance.options.parent;
elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, {
criticRating: false,
criticRating: true,
originalAirDate: false,
starRating: false,
starRating: true,
subtitles: false
});
@ -243,4 +241,3 @@ class UpNextDialog {
export default UpNextDialog;
/* eslint-enable indent */

View file

@ -2,7 +2,13 @@ import { importModule } from '@uupaa/dynamic-import-polyfill';
import './viewManager/viewContainer.scss';
import Dashboard from '../utils/dashboard';
/* eslint-disable indent */
const getMainAnimatedPages = () => {
if (!mainAnimatedPages) {
mainAnimatedPages = document.querySelector('.mainAnimatedPages');
}
return mainAnimatedPages;
};
function setControllerClass(view, options) {
if (options.controllerFactory) {
@ -55,6 +61,11 @@ import Dashboard from '../utils/dashboard';
view.classList.add('mainAnimatedPage');
if (!getMainAnimatedPages()) {
console.warn('[viewContainer] main animated pages element is not present');
return;
}
if (currentPage) {
if (newViewInfo.hasScript && window.$) {
mainAnimatedPages.removeChild(currentPage);
@ -225,20 +236,18 @@ import Dashboard from '../utils/dashboard';
export function reset() {
allPages = [];
currentUrls = [];
mainAnimatedPages.innerHTML = '';
if (mainAnimatedPages) mainAnimatedPages.innerHTML = '';
selectedPageIndex = -1;
}
let onBeforeChange;
const mainAnimatedPages = document.querySelector('.mainAnimatedPages');
let mainAnimatedPages;
let allPages = [];
let currentUrls = [];
const pageContainerCount = 3;
let selectedPageIndex = -1;
reset();
mainAnimatedPages.classList.remove('hide');
/* eslint-enable indent */
getMainAnimatedPages()?.classList.remove('hide');
export default {
loadView: loadView,

View file

@ -6,8 +6,6 @@ import '../../elements/emby-button/emby-button';
import confirm from '../../components/confirm/confirm';
import { pageIdOn } from '../../utils/dashboard';
/* eslint-disable indent */
function revoke(page, key) {
confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () {
loading.show();
@ -87,4 +85,3 @@ import { pageIdOn } from '../../utils/dashboard';
loadData(this);
});
/* eslint-enable indent */

View file

@ -24,8 +24,6 @@ import ServerConnections from '../../components/ServerConnections';
import alert from '../../components/alert';
import confirm from '../../components/confirm/confirm';
/* eslint-disable indent */
function showPlaybackInfo(btn, session) {
let title;
const text = [];
@ -846,4 +844,3 @@ import confirm from '../../components/confirm/confirm';
});
}
/* eslint-enable indent */

View file

@ -5,8 +5,6 @@ import '../../../elements/emby-button/emby-button';
import Dashboard from '../../../utils/dashboard';
import { getParameterByName } from '../../../utils/url.ts';
/* eslint-disable indent */
function load(page, device, deviceOptions) {
page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || '';
page.querySelector('.reportedName', page).innerText = device.Name || '';
@ -54,4 +52,3 @@ import { getParameterByName } from '../../../utils/url.ts';
view.addEventListener('viewshow', loadData);
}
/* eslint-enable indent */

View file

@ -12,8 +12,6 @@ import '../../../components/cardbuilder/card.scss';
import Dashboard from '../../../utils/dashboard';
import confirm from '../../../components/confirm/confirm';
/* eslint-disable indent */
// Local cache of loaded
let deviceIds = [];
@ -167,4 +165,4 @@ import confirm from '../../../components/confirm/confirm';
deleteAllDevices(view);
});
}
/* eslint-enable indent */

View file

@ -11,8 +11,6 @@ import Dashboard from '../../../utils/dashboard';
import toast from '../../../components/toast/toast';
import { getParameterByName } from '../../../utils/url.ts';
/* eslint-disable indent */
function loadProfile(page) {
loading.show();
const promise1 = getProfile();
@ -836,4 +834,3 @@ import { getParameterByName } from '../../../utils/url.ts';
}
};
/* eslint-enable indent */

View file

@ -7,8 +7,6 @@ import '../../../components/listview/listview.scss';
import '../../../elements/emby-button/emby-button';
import confirm from '../../../components/confirm/confirm';
/* eslint-disable indent */
function loadProfiles(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) {
@ -93,4 +91,3 @@ import confirm from '../../../components/confirm/confirm';
loadProfiles(this);
});
/* eslint-enable indent */

View file

@ -5,8 +5,6 @@ import libraryMenu from '../../../scripts/libraryMenu';
import globalize from '../../../scripts/globalize';
import Dashboard from '../../../utils/dashboard';
/* eslint-disable indent */
function loadPage(page, config, users) {
page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo;
page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog;
@ -60,4 +58,3 @@ import Dashboard from '../../../utils/dashboard';
});
});
/* eslint-enable indent */

View file

@ -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>

View file

@ -6,8 +6,6 @@ import libraryMenu from '../../scripts/libraryMenu';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';
/* eslint-disable indent */
function loadPage(page, config, systemInfo) {
Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) {
c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1;
@ -299,4 +297,3 @@ import alert from '../../components/alert';
});
});
/* eslint-enable indent */

View file

@ -10,8 +10,6 @@ import '../../elements/emby-button/emby-button';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';
/* eslint-disable indent */
function loadPage(page, config, languageOptions, systemInfo) {
page.querySelector('#txtServerName').value = systemInfo.ServerName;
page.querySelector('#txtCachePath').value = systemInfo.CachePath || '';
@ -116,4 +114,3 @@ import alert from '../../components/alert';
});
}
/* eslint-enable indent */

View file

@ -12,8 +12,6 @@ import Dashboard, { pageClassOn, pageIdOn } from '../../utils/dashboard';
import confirm from '../../components/confirm/confirm';
import cardBuilder from '../../components/cardbuilder/cardBuilder';
/* eslint-disable indent */
function addVirtualFolder(page) {
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => {
new medialibrarycreator({
@ -407,4 +405,3 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder';
});
});
/* eslint-enable indent */

View file

@ -5,8 +5,6 @@ import '../../elements/emby-checkbox/emby-checkbox';
import '../../elements/emby-button/emby-button';
import Dashboard from '../../utils/dashboard';
/* eslint-disable indent */
function getTabs() {
return [{
href: '#/library.html',
@ -71,4 +69,3 @@ import Dashboard from '../../utils/dashboard';
});
}
/* eslint-enable indent */

View file

@ -7,8 +7,6 @@ import '../../styles/flexstyles.scss';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';
/* eslint-disable indent */
function onSubmit(event) {
event.preventDefault();
loading.show();
@ -63,5 +61,3 @@ import alert from '../../components/alert';
loading.hide();
});
}
/* eslint-enable indent */

View file

@ -30,8 +30,6 @@ function populateImageResolutionOptions(select) {
select.innerHTML = html;
}
/* eslint-disable indent */
function populateLanguages(select) {
return ApiClient.getCultures().then(function(languages) {
let html = '';
@ -114,4 +112,3 @@ function populateImageResolutionOptions(select) {
loadPage(this);
});
/* eslint-enable indent */

View file

@ -6,8 +6,6 @@ import globalize from '../../scripts/globalize';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';
/* eslint-disable indent */
function loadPage(page, config, users) {
let html = '<option value="" selected="selected">' + globalize.translate('None') + '</option>';
html += users.map(function (user) {
@ -76,4 +74,3 @@ import alert from '../../components/alert';
});
});
/* eslint-enable indent */

View file

@ -5,8 +5,6 @@ import '../../elements/emby-select/emby-select';
import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert';
/* eslint-disable indent */
function onSubmit(e) {
const form = this;
const localAddress = form.querySelector('#txtLocalAddress').value;
@ -206,4 +204,3 @@ import alert from '../../components/alert';
});
}
/* eslint-enable indent */

View file

@ -4,8 +4,6 @@ import libraryMenu from '../../scripts/libraryMenu';
import globalize from '../../scripts/globalize';
import Dashboard from '../../utils/dashboard';
/* eslint-disable indent */
function loadPage(page, config) {
$('#txtMinResumePct', page).val(config.MinResumePct);
$('#txtMaxResumePct', page).val(config.MaxResumePct);
@ -55,4 +53,3 @@ import Dashboard from '../../utils/dashboard';
});
});
/* eslint-enable indent */

View file

@ -9,8 +9,6 @@ import '../../../elements/emby-select/emby-select';
import confirm from '../../../components/confirm/confirm';
import { getParameterByName } from '../../../utils/url.ts';
/* eslint-disable indent */
function fillTimeOfDay(select) {
const options = [];
@ -243,4 +241,3 @@ import { getParameterByName } from '../../../utils/url.ts';
});
}
/* eslint-enable indent */

View file

@ -9,8 +9,6 @@ import Events from '../../../utils/events.ts';
import '../../../components/listview/listview.scss';
import '../../../elements/emby-button/emby-button';
/* eslint-disable indent */
function reloadList(page) {
ApiClient.getScheduledTasks({
isHidden: false
@ -199,4 +197,3 @@ import '../../../elements/emby-button/emby-button';
});
}
/* eslint-enable indent */

View file

@ -2,8 +2,6 @@ import ActivityLog from '../../components/activitylog';
import globalize from '../../scripts/globalize';
import { toBoolean } from '../../utils/string.ts';
/* eslint-disable indent */
export default function (view, params) {
let activityLog;
@ -32,4 +30,3 @@ import { toBoolean } from '../../utils/string.ts';
});
}
/* eslint-enable indent */

View file

@ -4,8 +4,6 @@ import loading from '../../components/loading/loading';
import globalize from '../../scripts/globalize';
import Dashboard from '../../utils/dashboard';
/* eslint-disable indent */
function loadPage(page, config) {
$('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || '');
loading.hide();
@ -46,4 +44,3 @@ import Dashboard from '../../utils/dashboard';
});
});
/* eslint-enable indent */

View 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>

View 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);
});

View 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>

View 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);
});
});

View 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>

View 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);
});

View 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>

View 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]);
});
});

View 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>

View 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);
});
}

View 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>

View 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 : '&nbsp;';
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);
});

View file

@ -9,8 +9,6 @@ import '../elements/emby-itemscontainer/emby-itemscontainer';
import '../elements/emby-scroller/emby-scroller';
import ServerConnections from '../components/ServerConnections';
/* eslint-disable indent */
function enableScrollX() {
return true;
}
@ -318,4 +316,3 @@ class FavoritesTab {
export default FavoritesTab;
/* eslint-enable indent */

Some files were not shown because too many files have changed in this diff Show more