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