Merge branch 'master' into audio-normalization

This commit is contained in:
TelepathicWalrus
2023-04-22 18:59:40 +01:00
committed by GitHub
320 changed files with 55219 additions and 49479 deletions
-1
View File
@@ -6,7 +6,6 @@
"./dist/libraries/pdf.worker.js", "./dist/libraries/pdf.worker.js",
"./dist/libraries/worker-bundle.js", "./dist/libraries/worker-bundle.js",
"./dist/libraries/wasm-gen/libarchive.js", "./dist/libraries/wasm-gen/libarchive.js",
"./dist/node_modules.@jellyfin.libass-wasm.*.chunk.js",
"./dist/serviceworker.js" "./dist/serviceworker.js"
] ]
} }
+2 -1
View File
@@ -64,7 +64,9 @@ module.exports = {
'no-var': ['error'], 'no-var': ['error'],
'no-void': ['error', { 'allowAsStatement': true }], 'no-void': ['error', { 'allowAsStatement': true }],
'no-warning-comments': ['warn', { 'terms': ['fixme', 'hack', 'xxx'] }], 'no-warning-comments': ['warn', { 'terms': ['fixme', 'hack', 'xxx'] }],
'object-curly-spacing': ['error', 'always'],
'one-var': ['error', 'never'], 'one-var': ['error', 'never'],
'operator-linebreak': ['error', 'before', { overrides: { '?': 'after', ':': 'after', '=': 'after' } }],
'padded-blocks': ['error', 'never'], 'padded-blocks': ['error', 'never'],
'prefer-const': ['error', { 'destructuring': 'all' }], 'prefer-const': ['error', { 'destructuring': 'all' }],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
@@ -267,7 +269,6 @@ module.exports = {
'no-useless-constructor': ['off'], 'no-useless-constructor': ['off'],
'@typescript-eslint/no-useless-constructor': ['error'], '@typescript-eslint/no-useless-constructor': ['error'],
'max-params': ['error', 7],
'sonarjs/cognitive-complexity': ['warn'] 'sonarjs/cognitive-complexity': ['warn']
} }
} }
+1 -5
View File
@@ -1,6 +1,2 @@
.ci @dkanada @EraYaN * @jellyfin/web
.github @jellyfin/core .github @jellyfin/core
fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
deployment @joshuaboniface
+4 -4
View File
@@ -19,13 +19,13 @@ jobs:
language: [ 'javascript' ] language: [ 'javascript' ]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2.2.7 uses: github/codeql-action/init@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2.2.7 uses: github/codeql-action/autobuild@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2.2.7 uses: github/codeql-action/analyze@b2c19fb9a2a485599ccf4ed5d65527d94bc57226 # v2.3.0
+10 -2
View File
@@ -12,13 +12,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Notify as seen - 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: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }} comment-id: ${{ github.event.comment.id }}
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@@ -26,3 +26,11 @@ jobs:
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8 uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
env: env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
- name: Comment on failure
if: failure()
uses: peter-evans/create-or-update-comment@3383acd359705b10cb1eeef05c0e88c056ea4666 # v3.0.0
with:
token: ${{ secrets.JF_BOT_TOKEN }}
issue-number: ${{ github.event.issue.number }}
body: |
I'm sorry @${{ github.event.comment.user.login }}, I'm afraid I can't do that.
+4 -4
View File
@@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@@ -37,7 +37,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@@ -58,7 +58,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
@@ -82,7 +82,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b # v7.0.0 - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75 operations-per-run: 75
@@ -37,7 +37,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b # v7.0.0 - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75 operations-per-run: 75
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+2
View File
@@ -61,6 +61,8 @@
- [Rob Farraher](https://github.com/farraherbg) - [Rob Farraher](https://github.com/farraherbg)
- [TelepathicWalrus](https://github.com/TelepathicWalrus) - [TelepathicWalrus](https://github.com/TelepathicWalrus)
- [Pier-Luc Ducharme](https://github.com/pl-ducharme) - [Pier-Luc Ducharme](https://github.com/pl-ducharme)
- [Anantharaju S](https://github.com/Anantharajus)
- [Merlin Sievers](https://github.com/dann-merlin)
# Emby Contributors # Emby Contributors
+1 -8
View File
@@ -12,14 +12,7 @@ module.exports = {
corejs: 3 corejs: 3
} }
], ],
'@babel/preset-react', '@babel/preset-react'
[
'@babel/preset-typescript',
{
isTSX: true,
allExtensions: true
}
]
], ],
plugins: [ plugins: [
'@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-class-properties',
+2 -2
View File
@@ -1,4 +1,4 @@
FROM centos:8 FROM quay.io/centos/centos:stream8
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin
@@ -12,7 +12,7 @@ ENV IS_DOCKER=YES
# Prepare CentOS environment # Prepare CentOS environment
RUN yum update -y \ RUN yum update -y \
&& yum install -y epel-release \ && yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core autoconf automake glibc-devel gcc-c++ make \ && yum install -y rpmdevtools git autoconf automake glibc-devel gcc-c++ make \
&& curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - \ && curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - \
&& yum install -y nodejs && yum install -y nodejs
+3574 -2171
View File
File diff suppressed because it is too large Load Diff
+36 -36
View File
@@ -5,24 +5,23 @@
"repository": "https://github.com/jellyfin/jellyfin-web", "repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"devDependencies": { "devDependencies": {
"@babel/core": "7.21.0", "@babel/core": "7.21.4",
"@babel/eslint-parser": "7.19.1", "@babel/eslint-parser": "7.21.3",
"@babel/eslint-plugin": "7.19.1", "@babel/eslint-plugin": "7.19.1",
"@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6",
"@babel/plugin-transform-modules-umd": "7.18.6", "@babel/plugin-transform-modules-umd": "7.18.6",
"@babel/preset-env": "7.20.2", "@babel/preset-env": "7.21.4",
"@babel/preset-react": "7.18.6", "@babel/preset-react": "7.18.6",
"@babel/preset-typescript": "7.21.0",
"@types/escape-html": "1.0.2", "@types/escape-html": "1.0.2",
"@types/loadable__component": "5.13.4", "@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": "17.0.58",
"@types/react-dom": "17.0.19", "@types/react-dom": "17.0.19",
"@typescript-eslint/eslint-plugin": "5.54.1", "@typescript-eslint/eslint-plugin": "5.58.0",
"@typescript-eslint/parser": "5.54.1", "@typescript-eslint/parser": "5.58.0",
"@uupaa/dynamic-import-polyfill": "1.0.2", "@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.14",
"babel-loader": "9.1.2", "babel-loader": "9.1.2",
"babel-plugin-dynamic-import-polyfill": "1.0.0", "babel-plugin-dynamic-import-polyfill": "1.0.0",
"clean-webpack-plugin": "4.0.0", "clean-webpack-plugin": "4.0.0",
@@ -30,39 +29,39 @@
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"css-loader": "6.7.3", "css-loader": "6.7.3",
"cssnano": "5.1.15", "cssnano": "6.0.0",
"es-check": "7.1.0", "es-check": "7.1.1",
"eslint": "8.35.0", "eslint": "8.38.0",
"eslint-plugin-compat": "4.1.2", "eslint-plugin-compat": "4.1.4",
"eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1", "eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-react": "7.32.2", "eslint-plugin-react": "7.32.2",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-sonarjs": "0.18.0", "eslint-plugin-sonarjs": "0.19.0",
"expose-loader": "4.0.0", "expose-loader": "4.1.0",
"html-loader": "4.2.0", "html-loader": "4.2.0",
"html-webpack-plugin": "5.5.0", "html-webpack-plugin": "5.5.0",
"mini-css-extract-plugin": "2.7.3", "mini-css-extract-plugin": "2.7.5",
"postcss": "8.4.21", "postcss": "8.4.21",
"postcss-loader": "7.0.2", "postcss-loader": "7.2.4",
"postcss-preset-env": "8.0.1", "postcss-preset-env": "8.3.1",
"postcss-scss": "4.0.6", "postcss-scss": "4.0.6",
"sass": "1.58.3", "sass": "1.62.0",
"sass-loader": "13.2.0", "sass-loader": "13.2.2",
"source-map-loader": "4.0.1", "source-map-loader": "4.0.1",
"style-loader": "3.3.1", "style-loader": "3.3.2",
"stylelint": "15.2.0", "stylelint": "15.4.0",
"stylelint-config-rational-order": "0.1.2", "stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1", "stylelint-no-browser-hacks": "1.2.1",
"stylelint-order": "6.0.3", "stylelint-order": "6.0.3",
"stylelint-scss": "4.4.0", "stylelint-scss": "4.6.0",
"ts-loader": "9.4.2", "ts-loader": "9.4.2",
"typescript": "4.9.5", "typescript": "5.0.4",
"webpack": "5.76.0", "webpack": "5.79.0",
"webpack-cli": "5.0.1", "webpack-cli": "5.0.1",
"webpack-dev-server": "4.11.1", "webpack-dev-server": "4.13.2",
"webpack-merge": "5.8.0", "webpack-merge": "5.8.0",
"workbox-webpack-plugin": "6.5.4", "workbox-webpack-plugin": "6.5.4",
"worker-loader": "3.0.8" "worker-loader": "3.0.8"
@@ -74,35 +73,36 @@
"@fontsource/noto-sans-kr": "4.5.12", "@fontsource/noto-sans-kr": "4.5.12",
"@fontsource/noto-sans-sc": "4.5.12", "@fontsource/noto-sans-sc": "4.5.12",
"@fontsource/noto-sans-tc": "4.5.12", "@fontsource/noto-sans-tc": "4.5.12",
"@jellyfin/libass-wasm": "4.1.1",
"@jellyfin/sdk": "unstable", "@jellyfin/sdk": "unstable",
"@loadable/component": "5.15.3", "@loadable/component": "5.15.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.3.2", "classnames": "2.3.2",
"core-js": "3.29.0", "core-js": "3.30.1",
"date-fns": "2.29.3", "date-fns": "2.29.3",
"dompurify": "3.0.1", "dompurify": "3.0.1",
"epubjs": "0.4.2", "epubjs": "0.3.93",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"event-target-polyfill": "github:ThaUnknown/event-target-polyfill",
"fast-text-encoding": "1.0.6", "fast-text-encoding": "1.0.6",
"flv.js": "1.6.2", "flv.js": "1.6.2",
"headroom.js": "0.12.0", "headroom.js": "0.12.0",
"history": "5.3.0", "history": "5.3.0",
"hls.js": "1.2.4", "hls.js": "1.4.0",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"jassub": "1.5.12",
"jellyfin-apiclient": "1.10.0", "jellyfin-apiclient": "1.10.0",
"jquery": "3.6.3", "jquery": "3.6.4",
"jstree": "3.3.15", "jstree": "3.3.15",
"libarchive.js": "1.3.0", "libarchive.js": "1.3.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"marked": "4.2.12", "marked": "4.3.0",
"material-design-icons-iconfont": "6.7.0", "material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"pdfjs-dist": "2.16.105", "pdfjs-dist": "3.5.141",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-router-dom": "6.8.2", "react-router-dom": "6.10.0",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2", "screenfull": "6.0.2",
"sortablejs": "1.15.0", "sortablejs": "1.15.0",
@@ -131,8 +131,8 @@
"scripts": { "scripts": {
"start": "npm run serve", "start": "npm run serve",
"serve": "webpack serve --config webpack.dev.js", "serve": "webpack serve --config webpack.dev.js",
"build:development": "webpack --config webpack.dev.js", "build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js", "build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js",
"build:check": "tsc --noEmit", "build:check": "tsc --noEmit",
"escheck": "es-check", "escheck": "es-check",
"lint": "eslint \"./\"", "lint": "eslint \"./\"",
+2 -2
View File
@@ -7,8 +7,8 @@ const config = () => ({
plugins: [ plugins: [
// Explicitly specify browserslist to override ones from node_modules // Explicitly specify browserslist to override ones from node_modules
// For example, Swiper has it in its package.json // For example, Swiper has it in its package.json
postcssPresetEnv({browsers: packageConfig.browserslist}), postcssPresetEnv({ browsers: packageConfig.browserslist }),
autoprefixer({overrideBrowserslist: packageConfig.browserslist}), autoprefixer({ overrideBrowserslist: packageConfig.browserslist }),
cssnano() cssnano()
] ]
}); });
+26 -3
View File
@@ -1,15 +1,38 @@
import { History } from '@remix-run/router'; import { History } from '@remix-run/router';
import React from 'react'; import React, { useEffect } from 'react';
import { HistoryRouter } from './components/HistoryRouter'; import { HistoryRouter } from './components/HistoryRouter';
import { ApiProvider } from './hooks/useApi'; import { ApiProvider } from './hooks/useApi';
import AppRoutes from './routes/index'; import { AppRoutes, ExperimentalAppRoutes } from './routes';
const App = ({ history }: { history: History }) => { const App = ({ history }: { history: History }) => {
const layoutMode = localStorage.getItem('layout');
useEffect(() => {
Promise.all([
// Initialize the UI components after first render
import('./scripts/libraryMenu'),
import('./scripts/autoBackdrops')
]);
}, []);
return ( return (
<ApiProvider> <ApiProvider>
<HistoryRouter history={history}> <HistoryRouter history={history}>
<AppRoutes /> <div className='backdropContainer' />
<div className='backgroundContainer' />
<div className='mainDrawer hide'>
<div className='mainDrawer-scrollContainer scrollContainer focuscontainer-y' />
</div>
<div className='skinHeader focuscontainer-x' />
<div className='mainAnimatedPages skinBody' />
<div className='skinBody'>
{layoutMode === 'experimental' ? <ExperimentalAppRoutes /> : <AppRoutes /> }
</div>
<div className='mainDrawerHandle' />
</HistoryRouter> </HistoryRouter>
</ApiProvider> </ApiProvider>
); );
+65 -70
View File
@@ -1,6 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for controlling user parental control from. * Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule * @module components/accessSchedule/accessSchedule
@@ -14,84 +11,82 @@ import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.scss'; import '../formdialog.scss';
import template from './accessSchedule.template.html'; import template from './accessSchedule.template.html';
function getDisplayTime(hours) { function getDisplayTime(hours) {
let minutes = 0; let minutes = 0;
const pct = hours % 1; const pct = hours % 1;
if (pct) { if (pct) {
minutes = parseInt(60 * pct, 10); minutes = parseInt(60 * pct, 10);
}
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
} }
function populateHours(context) { return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
let html = ''; }
for (let i = 0; i < 24; i += 0.5) { function populateHours(context) {
html += `<option value="${i}">${getDisplayTime(i)}</option>`; let html = '';
}
html += `<option value="24">${getDisplayTime(0)}</option>`; for (let i = 0; i < 24; i += 0.5) {
context.querySelector('#selectStart').innerHTML = html; html += `<option value="${i}">${getDisplayTime(i)}</option>`;
context.querySelector('#selectEnd').innerHTML = html;
} }
function loadSchedule(context, {DayOfWeek, StartHour, EndHour}) { html += `<option value="24">${getDisplayTime(0)}</option>`;
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday'; context.querySelector('#selectStart').innerHTML = html;
context.querySelector('#selectStart').value = StartHour || 0; context.querySelector('#selectEnd').innerHTML = html;
context.querySelector('#selectEnd').value = EndHour || 0; }
function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) {
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday';
context.querySelector('#selectStart').value = StartHour || 0;
context.querySelector('#selectEnd').value = EndHour || 0;
}
function submitSchedule(context, options) {
const updatedSchedule = {
DayOfWeek: context.querySelector('#selectDay').value,
StartHour: context.querySelector('#selectStart').value,
EndHour: context.querySelector('#selectEnd').value
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
return;
} }
function submitSchedule(context, options) { context.submitted = true;
const updatedSchedule = { options.schedule = Object.assign(options.schedule, updatedSchedule);
DayOfWeek: context.querySelector('#selectDay').value, dialogHelper.close(context);
StartHour: context.querySelector('#selectStart').value, }
EndHour: context.querySelector('#selectEnd').value
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { export function show(options) {
alert(globalize.translate('ErrorStartHourGreaterThanEnd')); return new Promise((resolve, reject) => {
return; const dlg = dialogHelper.createDialog({
} removeOnClose: true,
size: 'small'
context.submitted = true;
options.schedule = Object.assign(options.schedule, updatedSchedule);
dialogHelper.close(context);
}
export function show(options) {
return new Promise((resolve, reject) => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateHtml(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', () => {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', event => {
submitSchedule(dlg, options);
event.preventDefault();
return false;
});
}); });
} dlg.classList.add('formDialog');
let html = '';
/* eslint-enable indent */ html += globalize.translateHtml(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', () => {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', event => {
submitSchedule(dlg, options);
event.preventDefault();
return false;
});
});
}
export default { export default {
show: show show: show
+103 -107
View File
@@ -11,130 +11,128 @@ import alert from './alert';
import { getLocale } from '../utils/dateFnsLocale.ts'; import { getLocale } from '../utils/dateFnsLocale.ts';
import { toBoolean } from '../utils/string.ts'; import { toBoolean } from '../utils/string.ts';
/* eslint-disable indent */ function getEntryHtml(entry, apiClient) {
let html = '';
html += '<div class="listItem listItem-border">';
let color = '#00a4dc';
let icon = 'notifications';
function getEntryHtml(entry, apiClient) { if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') {
let html = ''; color = '#cc0000';
html += '<div class="listItem listItem-border">'; icon = 'notification_important';
let color = '#00a4dc'; }
let icon = 'notifications';
if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') { if (entry.UserId && entry.UserPrimaryImageTag) {
color = '#cc0000'; html += '<span class="listItemIcon material-icons dvr" aria-hidden="true" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, {
icon = 'notification_important'; type: 'Primary',
} tag: entry.UserPrimaryImageTag
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\"></span>";
} else {
html += '<span class="listItemIcon material-icons ' + icon + '" aria-hidden="true" style="background-color:' + color + '"></span>';
}
if (entry.UserId && entry.UserPrimaryImageTag) { html += '<div class="listItemBody three-line">';
html += '<span class="listItemIcon material-icons dvr" aria-hidden="true" style="width:2em!important;height:2em!important;padding:0;color:transparent;background-color:' + color + ";background-image:url('" + apiClient.getUserImageUrl(entry.UserId, { html += '<div class="listItemBodyText">';
type: 'Primary', html += escapeHtml(entry.Name);
tag: entry.UserPrimaryImageTag html += '</div>';
}) + "');background-repeat:no-repeat;background-position:center center;background-size: cover;\"></span>"; html += '<div class="listItemBodyText secondary">';
} else { html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() });
html += '<span class="listItemIcon material-icons ' + icon + '" aria-hidden="true" style="background-color:' + color + '"></span>'; html += '</div>';
} html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += escapeHtml(entry.ShortOverview || '');
html += '</div>';
html += '</div>';
html += '<div class="listItemBody three-line">'; if (entry.Overview) {
html += '<div class="listItemBodyText">'; html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
html += escapeHtml(entry.Name);
html += '</div>';
html += '<div class="listItemBodyText secondary">';
html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() });
html += '</div>';
html += '<div class="listItemBodyText secondary listItemBodyText-nowrap">';
html += escapeHtml(entry.ShortOverview || '');
html += '</div>';
html += '</div>';
if (entry.Overview) {
html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
<span class="material-icons info" aria-hidden="true"></span> <span class="material-icons info" aria-hidden="true"></span>
</button>`; </button>`;
}
html += '</div>';
return html;
} }
function renderList(elem, apiClient, result) { html += '</div>';
elem.innerHTML = result.Items.map(function (i) {
return getEntryHtml(i, apiClient); return html;
}).join(''); }
function renderList(elem, apiClient, result) {
elem.innerHTML = result.Items.map(function (i) {
return getEntryHtml(i, apiClient);
}).join('');
}
function reloadData(instance, elem, apiClient, startIndex, limit) {
if (startIndex == null) {
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10);
} }
function reloadData(instance, elem, apiClient, startIndex, limit) { limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10);
if (startIndex == null) { const minDate = new Date();
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true);
}
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); // TODO: Use date-fns
const minDate = new Date(); if (hasUserId) {
const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
} else {
// TODO: Use date-fns minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back
if (hasUserId) {
minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
} else {
minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back
}
ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', {
startIndex: startIndex,
limit: limit,
minDate: minDate.toISOString(),
hasUserId: hasUserId
})).then(function (result) {
elem.setAttribute('data-activitystartindex', startIndex);
elem.setAttribute('data-activitylimit', limit);
if (!startIndex) {
const activityContainer = dom.parentWithClass(elem, 'activityContainer');
if (activityContainer) {
if (result.Items.length) {
activityContainer.classList.remove('hide');
} else {
activityContainer.classList.add('hide');
}
}
}
instance.items = result.Items;
renderList(elem, apiClient, result);
});
} }
function onActivityLogUpdate(e, apiClient) { ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', {
const options = this.options; startIndex: startIndex,
limit: limit,
minDate: minDate.toISOString(),
hasUserId: hasUserId
})).then(function (result) {
elem.setAttribute('data-activitystartindex', startIndex);
elem.setAttribute('data-activitylimit', limit);
if (!startIndex) {
const activityContainer = dom.parentWithClass(elem, 'activityContainer');
if (options && options.serverId === apiClient.serverId()) { if (activityContainer) {
reloadData(this, options.element, apiClient); if (result.Items.length) {
} activityContainer.classList.remove('hide');
} } else {
activityContainer.classList.add('hide');
function onListClick(e) {
const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
if (btnEntryInfo) {
const id = btnEntryInfo.getAttribute('data-id');
const items = this.items;
if (items) {
const item = items.filter(function (i) {
return i.Id.toString() === id;
})[0];
if (item) {
showItemOverview(item);
} }
} }
} }
}
function showItemOverview(item) { instance.items = result.Items;
alert({ renderList(elem, apiClient, result);
text: item.Overview });
}); }
function onActivityLogUpdate(e, apiClient) {
const options = this.options;
if (options && options.serverId === apiClient.serverId()) {
reloadData(this, options.element, apiClient);
} }
}
function onListClick(e) {
const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
if (btnEntryInfo) {
const id = btnEntryInfo.getAttribute('data-id');
const items = this.items;
if (items) {
const item = items.filter(function (i) {
return i.Id.toString() === id;
})[0];
if (item) {
showItemOverview(item);
}
}
}
}
function showItemOverview(item) {
alert({
text: item.Overview
});
}
class ActivityLog { class ActivityLog {
constructor(options) { constructor(options) {
@@ -169,5 +167,3 @@ class ActivityLog {
} }
export default ActivityLog; export default ActivityLog;
/* eslint-enable indent */
+29 -33
View File
@@ -3,45 +3,41 @@ import browser from '../scripts/browser';
import dialog from './dialog/dialog'; import dialog from './dialog/dialog';
import globalize from '../scripts/globalize'; import globalize from '../scripts/globalize';
/* eslint-disable indent */ function useNativeAlert() {
// webOS seems to block modals
function useNativeAlert() { // Tizen 2.x seems to block modals
// webOS seems to block modals return !browser.web0s
// Tizen 2.x seems to block modals
return !browser.web0s
&& !(browser.tizenVersion && browser.tizenVersion < 3) && !(browser.tizenVersion && browser.tizenVersion < 3)
&& browser.tv && browser.tv
&& window.alert; && window.alert;
}
export default async function (text, title) {
let options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
} }
export default async function (text, title) { await appRouter.ready();
let options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
}
await appRouter.ready(); if (useNativeAlert()) {
alert((options.text || '').replaceAll('<br/>', '\n'));
return Promise.resolve();
} else {
const items = [];
if (useNativeAlert()) { items.push({
alert((options.text || '').replaceAll('<br/>', '\n')); name: globalize.translate('ButtonGotIt'),
return Promise.resolve(); id: 'ok',
} else { type: 'submit'
const items = []; });
items.push({ options.buttons = items;
name: globalize.translate('ButtonGotIt'), return dialog.show(options);
id: 'ok',
type: 'submit'
});
options.buttons = items;
return dialog.show(options);
}
} }
}
/* eslint-enable indent */
+257 -260
View File
@@ -1,5 +1,3 @@
/* eslint-disable indent */
/** /**
* Module alphaPicker. * Module alphaPicker.
* @module components/alphaPicker/alphaPicker * @module components/alphaPicker/alphaPicker
@@ -13,312 +11,311 @@ import './style.scss';
import '../../elements/emby-button/paper-icon-button-light'; import '../../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont'; import 'material-design-icons-iconfont';
const selectedButtonClass = 'alphaPickerButton-selected'; const selectedButtonClass = 'alphaPickerButton-selected';
function focus() { function focus() {
const scope = this; const scope = this;
const selected = scope.querySelector(`.${selectedButtonClass}`); const selected = scope.querySelector(`.${selectedButtonClass}`);
if (selected) { if (selected) {
focusManager.focus(selected); focusManager.focus(selected);
} else { } else {
focusManager.autoFocus(scope, true); focusManager.autoFocus(scope, true);
} }
}
function getAlphaPickerButtonClassName(vertical) {
let alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
} }
function getAlphaPickerButtonClassName(vertical) { if (vertical) {
let alphaPickerButtonClassName = 'alphaPickerButton'; alphaPickerButtonClassName += ' alphaPickerButton-vertical';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
}
if (vertical) {
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
}
return alphaPickerButtonClassName;
} }
function getLetterButton(l, vertical) { return alphaPickerButtonClassName;
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`; }
function getLetterButton(l, vertical) {
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
}
function mapLetters(letters, vertical) {
return letters.map(l => {
return getLetterButton(l, vertical);
});
}
function render(element, options) {
element.classList.add('alphaPicker');
if (layoutManager.tv) {
element.classList.add('alphaPicker-tv');
} }
function mapLetters(letters, vertical) { const vertical = element.classList.contains('alphaPicker-vertical');
return letters.map(l => {
return getLetterButton(l, vertical); if (!vertical) {
}); element.classList.add('focuscontainer-x');
} }
function render(element, options) { let html = '';
element.classList.add('alphaPicker'); let letters;
if (layoutManager.tv) { const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
element.classList.add('alphaPicker-tv');
}
const vertical = element.classList.contains('alphaPicker-vertical'); let rowClassName = 'alphaPickerRow';
if (!vertical) { if (vertical) {
element.classList.add('focuscontainer-x'); rowClassName += ' alphaPickerRow-vertical';
} }
let html = ''; html += `<div class="${rowClassName}">`;
let letters; if (options.mode === 'keyboard') {
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonSpace')}"><span class="material-icons alphaPickerButtonIcon space_bar" aria-hidden="true"></span></button>`;
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical); } else {
letters = ['#'];
let rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += `<div class="${rowClassName}">`;
if (options.mode === 'keyboard') {
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonSpace')}"><span class="material-icons alphaPickerButtonIcon space_bar" aria-hidden="true"></span></button>`;
} else {
letters = ['#'];
html += mapLetters(letters, vertical).join('');
}
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
html += mapLetters(letters, vertical).join(''); html += mapLetters(letters, vertical).join('');
if (options.mode === 'keyboard') {
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonBackspace')}"><span class="material-icons alphaPickerButtonIcon backspace" aria-hidden="true"></span></button>`;
html += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
html += `<div class="${rowClassName}">`;
html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
} else {
html += '</div>';
}
element.innerHTML = html;
element.classList.add('focusable');
element.focus = focus;
} }
export class AlphaPicker { letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
constructor(options) { html += mapLetters(letters, vertical).join('');
const self = this;
this.options = options; if (options.mode === 'keyboard') {
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}" aria-label="${globalize.translate('ButtonBackspace')}"><span class="material-icons alphaPickerButtonIcon backspace" aria-hidden="true"></span></button>`;
html += '</div>';
const element = options.element; letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const itemsContainer = options.itemsContainer; html += `<div class="${rowClassName}">`;
const itemClass = options.itemClass; html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
} else {
html += '</div>';
}
let itemFocusValue; element.innerHTML = html;
let itemFocusTimeout;
function onItemFocusTimeout() { element.classList.add('focusable');
itemFocusTimeout = null; element.focus = focus;
self.value(itemFocusValue); }
}
let alphaFocusedElement; export class AlphaPicker {
let alphaFocusTimeout; constructor(options) {
const self = this;
function onAlphaFocusTimeout() { this.options = options;
alphaFocusTimeout = null;
if (document.activeElement === alphaFocusedElement) { const element = options.element;
const value = alphaFocusedElement.getAttribute('data-value'); const itemsContainer = options.itemsContainer;
self.value(value, true); const itemClass = options.itemClass;
}
}
function onAlphaPickerInKeyboardModeClick(e) { let itemFocusValue;
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); let itemFocusTimeout;
if (alphaPickerButton) { function onItemFocusTimeout() {
const value = alphaPickerButton.getAttribute('data-value'); itemFocusTimeout = null;
self.value(itemFocusValue);
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
cancelable: false,
detail: {
value
}
}));
}
}
function onAlphaPickerClick(e) {
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
const value = alphaPickerButton.getAttribute('data-value');
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
this.value(null, true);
} else {
this.value(value, true);
}
}
}
function onAlphaPickerFocusIn(e) {
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
alphaFocusedElement = alphaPickerButton;
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
}
}
function onItemsFocusIn(e) {
const item = dom.parentWithClass(e.target, itemClass);
if (item) {
const prefix = item.getAttribute('data-prefix');
if (prefix && prefix.length) {
itemFocusValue = prefix[0];
if (itemFocusTimeout) {
clearTimeout(itemFocusTimeout);
}
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
}
}
}
this.enabled = function (enabled) {
if (enabled) {
if (itemsContainer) {
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
}
if (options.mode === 'keyboard') {
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
}
if (options.valueChangeEvent !== 'click') {
element.addEventListener('focus', onAlphaPickerFocusIn, true);
} else {
element.addEventListener('click', onAlphaPickerClick.bind(this));
}
} else {
if (itemsContainer) {
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
}
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
element.removeEventListener('click', onAlphaPickerClick.bind(this));
}
};
render(element, options);
this.enabled(true);
this.visible(true);
} }
value(value, applyValue) { let alphaFocusedElement;
const element = this.options.element; let alphaFocusTimeout;
let btn;
let selected;
if (value !== undefined) { function onAlphaFocusTimeout() {
if (value != null) { alphaFocusTimeout = null;
value = value.toUpperCase();
this._currentValue = value;
if (this.options.mode !== 'keyboard') { if (document.activeElement === alphaFocusedElement) {
selected = element.querySelector(`.${selectedButtonClass}`); const value = alphaFocusedElement.getAttribute('data-value');
self.value(value, true);
try {
btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`);
} catch (err) {
console.error('error in querySelector:', err);
}
if (btn && btn !== selected) {
btn.classList.add(selectedButtonClass);
}
if (selected && selected !== btn) {
selected.classList.remove(selectedButtonClass);
}
}
} else {
this._currentValue = value;
selected = element.querySelector(`.${selectedButtonClass}`);
if (selected) {
selected.classList.remove(selectedButtonClass);
}
}
} }
}
if (applyValue) { function onAlphaPickerInKeyboardModeClick(e) {
element.dispatchEvent(new CustomEvent('alphavaluechanged', { const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
const value = alphaPickerButton.getAttribute('data-value');
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
cancelable: false, cancelable: false,
detail: { detail: {
value value
} }
})); }));
} }
return this._currentValue;
} }
on(name, fn) { function onAlphaPickerClick(e) {
const element = this.options.element; const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
element.addEventListener(name, fn);
if (alphaPickerButton) {
const value = alphaPickerButton.getAttribute('data-value');
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
this.value(null, true);
} else {
this.value(value, true);
}
}
} }
off(name, fn) { function onAlphaPickerFocusIn(e) {
const element = this.options.element; if (alphaFocusTimeout) {
element.removeEventListener(name, fn); clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
alphaFocusedElement = alphaPickerButton;
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
}
} }
updateControls(query) { function onItemsFocusIn(e) {
if (query.NameLessThan) { const item = dom.parentWithClass(e.target, itemClass);
this.value('#');
if (item) {
const prefix = item.getAttribute('data-prefix');
if (prefix && prefix.length) {
itemFocusValue = prefix[0];
if (itemFocusTimeout) {
clearTimeout(itemFocusTimeout);
}
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
}
}
}
this.enabled = function (enabled) {
if (enabled) {
if (itemsContainer) {
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
}
if (options.mode === 'keyboard') {
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
}
if (options.valueChangeEvent !== 'click') {
element.addEventListener('focus', onAlphaPickerFocusIn, true);
} else {
element.addEventListener('click', onAlphaPickerClick.bind(this));
}
} else { } else {
this.value(query.NameStartsWith); if (itemsContainer) {
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
}
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
element.removeEventListener('click', onAlphaPickerClick.bind(this));
} }
};
this.visible(query.SortBy.indexOf('SortName') !== -1); render(element, options);
}
visible(visible) { this.enabled(true);
const element = this.options.element; this.visible(true);
element.style.visibility = visible ? 'visible' : 'hidden';
}
values() {
const element = this.options.element;
const elems = element.querySelectorAll('.alphaPickerButton');
const values = [];
for (let i = 0, length = elems.length; i < length; i++) {
values.push(elems[i].getAttribute('data-value'));
}
return values;
}
focus() {
const element = this.options.element;
focusManager.autoFocus(element, true);
}
destroy() {
const element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
}
} }
/* eslint-enable indent */ value(value, applyValue) {
const element = this.options.element;
let btn;
let selected;
if (value !== undefined) {
if (value != null) {
value = value.toUpperCase();
this._currentValue = value;
if (this.options.mode !== 'keyboard') {
selected = element.querySelector(`.${selectedButtonClass}`);
try {
btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`);
} catch (err) {
console.error('error in querySelector:', err);
}
if (btn && btn !== selected) {
btn.classList.add(selectedButtonClass);
}
if (selected && selected !== btn) {
selected.classList.remove(selectedButtonClass);
}
}
} else {
this._currentValue = value;
selected = element.querySelector(`.${selectedButtonClass}`);
if (selected) {
selected.classList.remove(selectedButtonClass);
}
}
}
if (applyValue) {
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
cancelable: false,
detail: {
value
}
}));
}
return this._currentValue;
}
on(name, fn) {
const element = this.options.element;
element.addEventListener(name, fn);
}
off(name, fn) {
const element = this.options.element;
element.removeEventListener(name, fn);
}
updateControls(query) {
if (query.NameLessThan) {
this.value('#');
} else {
this.value(query.NameStartsWith);
}
this.visible(query.SortBy.indexOf('SortName') !== -1);
}
visible(visible) {
const element = this.options.element;
element.style.visibility = visible ? 'visible' : 'hidden';
}
values() {
const element = this.options.element;
const elems = element.querySelectorAll('.alphaPickerButton');
const values = [];
for (let i = 0, length = elems.length; i < length; i++) {
values.push(elems[i].getAttribute('data-value'));
}
return values;
}
focus() {
const element = this.options.element;
focusManager.autoFocus(element, true);
}
destroy() {
const element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
}
}
export default AlphaPicker; export default AlphaPicker;
+10 -10
View File
@@ -309,8 +309,8 @@ function askForExit() {
exitPromise = actionsheet.show({ exitPromise = actionsheet.show({
title: globalize.translate('MessageConfirmAppExit'), title: globalize.translate('MessageConfirmAppExit'),
items: [ items: [
{id: 'yes', name: globalize.translate('Yes')}, { id: 'yes', name: globalize.translate('Yes') },
{id: 'no', name: globalize.translate('No')} { id: 'no', name: globalize.translate('No') }
] ]
}).then(function (value) { }).then(function (value) {
if (value === 'yes') { if (value === 'yes') {
@@ -366,20 +366,20 @@ export const appHost = {
}; };
}, },
deviceName: function () { deviceName: function () {
return window.NativeShell?.AppHost?.deviceName return window.NativeShell?.AppHost?.deviceName ?
? window.NativeShell.AppHost.deviceName() : getDeviceName(); window.NativeShell.AppHost.deviceName() : getDeviceName();
}, },
deviceId: function () { deviceId: function () {
return window.NativeShell?.AppHost?.deviceId return window.NativeShell?.AppHost?.deviceId ?
? window.NativeShell.AppHost.deviceId() : getDeviceId(); window.NativeShell.AppHost.deviceId() : getDeviceId();
}, },
appName: function () { appName: function () {
return window.NativeShell?.AppHost?.appName return window.NativeShell?.AppHost?.appName ?
? window.NativeShell.AppHost.appName() : appName; window.NativeShell.AppHost.appName() : appName;
}, },
appVersion: function () { appVersion: function () {
return window.NativeShell?.AppHost?.appVersion return window.NativeShell?.AppHost?.appVersion ?
? window.NativeShell.AppHost.appVersion() : Package.version; window.NativeShell.AppHost.appVersion() : Package.version;
}, },
getPushTokenInfo: function () { getPushTokenInfo: function () {
return {}; return {};
+69 -73
View File
@@ -1,5 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for performing auto-focus. * Module for performing auto-focus.
* @module components/autoFocuser * @module components/autoFocuser
@@ -8,93 +6,91 @@
import focusManager from './focusManager'; import focusManager from './focusManager';
import layoutManager from './layoutManager'; import layoutManager from './layoutManager';
/** /**
* Previously selected element. * Previously selected element.
*/ */
let activeElement; let activeElement;
/** /**
* Returns _true_ if AutoFocuser is enabled. * Returns _true_ if AutoFocuser is enabled.
*/ */
export function isEnabled() { export function isEnabled() {
return layoutManager.tv; return layoutManager.tv;
} }
/** /**
* Start AutoFocuser. * Start AutoFocuser.
*/ */
export function enable() { export function enable() {
if (!isEnabled()) { if (!isEnabled()) {
return; return;
}
window.addEventListener('focusin', function (e) {
activeElement = e.target;
});
console.debug('AutoFocuser enabled');
} }
/** window.addEventListener('focusin', function (e) {
activeElement = e.target;
});
console.debug('AutoFocuser enabled');
}
/**
* Set focus on a suitable element, taking into account the previously selected. * Set focus on a suitable element, taking into account the previously selected.
* @param {HTMLElement} [container] - Element to limit scope. * @param {HTMLElement} [container] - Element to limit scope.
* @returns {HTMLElement} Focused element. * @returns {HTMLElement} Focused element.
*/ */
export function autoFocus(container) { export function autoFocus(container) {
if (!isEnabled()) { if (!isEnabled()) {
return null; return null;
}
container = container || document.body;
let candidates = [];
if (activeElement) {
// These elements are recreated
if (activeElement.classList.contains('btnPreviousPage')) {
candidates.push(container.querySelector('.btnPreviousPage'));
candidates.push(container.querySelector('.btnNextPage'));
} else if (activeElement.classList.contains('btnNextPage')) {
candidates.push(container.querySelector('.btnNextPage'));
candidates.push(container.querySelector('.btnPreviousPage'));
} else if (activeElement.classList.contains('btnSelectView')) {
candidates.push(container.querySelector('.btnSelectView'));
}
candidates.push(activeElement);
}
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay')));
let focusedElement;
candidates.every(function (element) {
if (focusManager.isCurrentlyFocusable(element)) {
focusManager.focus(element);
focusedElement = element;
return false;
}
return true;
});
if (!focusedElement) {
// FIXME: Multiple itemsContainers
const itemsContainer = container.querySelector('.itemsContainer');
if (itemsContainer) {
focusedElement = focusManager.autoFocus(itemsContainer);
}
}
if (!focusedElement) {
focusedElement = focusManager.autoFocus(container);
}
return focusedElement;
} }
/* eslint-enable indent */ container = container || document.body;
let candidates = [];
if (activeElement) {
// These elements are recreated
if (activeElement.classList.contains('btnPreviousPage')) {
candidates.push(container.querySelector('.btnPreviousPage'));
candidates.push(container.querySelector('.btnNextPage'));
} else if (activeElement.classList.contains('btnNextPage')) {
candidates.push(container.querySelector('.btnNextPage'));
candidates.push(container.querySelector('.btnPreviousPage'));
} else if (activeElement.classList.contains('btnSelectView')) {
candidates.push(container.querySelector('.btnSelectView'));
}
candidates.push(activeElement);
}
candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay')));
let focusedElement;
candidates.every(function (element) {
if (focusManager.isCurrentlyFocusable(element)) {
focusManager.focus(element);
focusedElement = element;
return false;
}
return true;
});
if (!focusedElement) {
// FIXME: Multiple itemsContainers
const itemsContainer = container.querySelector('.itemsContainer');
if (itemsContainer) {
focusedElement = focusManager.autoFocus(itemsContainer);
}
}
if (!focusedElement) {
focusedElement = focusManager.autoFocus(container);
}
return focusedElement;
}
export default { export default {
isEnabled: isEnabled, isEnabled: isEnabled,
+237 -241
View File
@@ -7,282 +7,278 @@ import ServerConnections from '../ServerConnections';
import './backdrop.scss'; import './backdrop.scss';
/* eslint-disable indent */ function enableAnimation() {
return !browser.slow;
}
function enableAnimation() { function enableRotation() {
return !browser.slow; return !browser.tv
}
function enableRotation() {
return !browser.tv
// Causes high cpu usage // Causes high cpu usage
&& !browser.firefox; && !browser.firefox;
} }
class Backdrop { class Backdrop {
load(url, parent, existingBackdropImage) { load(url, parent, existingBackdropImage) {
const img = new Image(); const img = new Image();
const self = this; const self = this;
img.onload = () => { img.onload = () => {
if (self.isDestroyed) { if (self.isDestroyed) {
return;
}
const backdropImage = document.createElement('div');
backdropImage.classList.add('backdropImage');
backdropImage.classList.add('displayingBackdropImage');
backdropImage.style.backgroundImage = `url('${url}')`;
backdropImage.setAttribute('data-url', url);
backdropImage.classList.add('backdropImageFadeIn');
parent.appendChild(backdropImage);
if (!enableAnimation()) {
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
internalBackdrop(true);
return;
}
const onAnimationComplete = () => {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null;
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
internalBackdrop(true);
};
img.src = url;
}
cancelAnimation() {
const elem = this.currentAnimatingElement;
if (elem) {
elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
}
}
destroy() {
this.isDestroyed = true;
this.cancelAnimation();
}
}
let backdropContainer;
function getBackdropContainer() {
if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer');
}
if (!backdropContainer) {
backdropContainer = document.createElement('div');
backdropContainer.classList.add('backdropContainer');
document.body.insertBefore(backdropContainer, document.body.firstChild);
}
return backdropContainer;
}
export function clearBackdrop(clearAll) {
clearRotation();
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
elem.innerHTML = '';
if (clearAll) {
hasExternalBackdrop = false;
}
internalBackdrop(false);
}
let backgroundContainer;
function getBackgroundContainer() {
if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer');
}
return backgroundContainer;
}
function setBackgroundContainerBackgroundEnabled() {
if (hasInternalBackdrop || hasExternalBackdrop) {
getBackgroundContainer().classList.add('withBackdrop');
} else {
getBackgroundContainer().classList.remove('withBackdrop');
}
}
let hasInternalBackdrop;
function internalBackdrop(isEnabled) {
hasInternalBackdrop = isEnabled;
setBackgroundContainerBackgroundEnabled();
}
let hasExternalBackdrop;
export function externalBackdrop(isEnabled) {
hasExternalBackdrop = isEnabled;
setBackgroundContainerBackgroundEnabled();
}
let currentLoadingBackdrop;
function setBackdropImage(url) {
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
if (existingBackdropImage.getAttribute('data-url') === url) {
return; return;
} }
existingBackdropImage.classList.remove('displayingBackdropImage');
}
const instance = new Backdrop(); const backdropImage = document.createElement('div');
instance.load(url, elem, existingBackdropImage); backdropImage.classList.add('backdropImage');
currentLoadingBackdrop = instance; backdropImage.classList.add('displayingBackdropImage');
} backdropImage.style.backgroundImage = `url('${url}')`;
backdropImage.setAttribute('data-url', url);
function getItemImageUrls(item, imageOptions) { backdropImage.classList.add('backdropImageFadeIn');
imageOptions = imageOptions || {}; parent.appendChild(backdropImage);
const apiClient = ServerConnections.getApiClient(item.ServerId); if (!enableAnimation()) {
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { if (existingBackdropImage && existingBackdropImage.parentNode) {
return item.BackdropImageTags.map((imgTag, index) => { existingBackdropImage.parentNode.removeChild(existingBackdropImage);
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { }
type: 'Backdrop', internalBackdrop(true);
tag: imgTag, return;
maxWidth: dom.getScreenWidth(), }
index: index
})); const onAnimationComplete = () => {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null;
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
}); });
}
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { internalBackdrop(true);
return item.ParentBackdropImageTags.map((imgTag, index) => {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: 'Backdrop',
tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index
}));
});
}
return [];
}
function getImageUrls(items, imageOptions) {
const list = [];
const onImg = img => {
list.push(img);
}; };
for (let i = 0, length = items.length; i < length; i++) { img.src = url;
const itemImages = getItemImageUrls(items[i], imageOptions);
itemImages.forEach(onImg);
}
return list;
} }
function enabled() { cancelAnimation() {
return userSettings.enableBackdrops(); const elem = this.currentAnimatingElement;
} if (elem) {
elem.classList.remove('backdropImageFadeIn');
let rotationInterval; this.currentAnimatingElement = null;
let currentRotatingImages = [];
let currentRotationIndex = -1;
export function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) {
const images = getImageUrls(items, imageOptions);
if (images.length) {
startRotation(images, enableImageRotation);
} else {
clearBackdrop();
}
} }
} }
function startRotation(images, enableImageRotation) { destroy() {
if (isEqual(images, currentRotatingImages)) { this.isDestroyed = true;
this.cancelAnimation();
}
}
let backdropContainer;
function getBackdropContainer() {
if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer');
}
if (!backdropContainer) {
backdropContainer = document.createElement('div');
backdropContainer.classList.add('backdropContainer');
document.body.insertBefore(backdropContainer, document.body.firstChild);
}
return backdropContainer;
}
export function clearBackdrop(clearAll) {
clearRotation();
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
elem.innerHTML = '';
if (clearAll) {
hasExternalBackdrop = false;
}
internalBackdrop(false);
}
let backgroundContainer;
function getBackgroundContainer() {
if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer');
}
return backgroundContainer;
}
function setBackgroundContainerBackgroundEnabled() {
if (hasInternalBackdrop || hasExternalBackdrop) {
getBackgroundContainer().classList.add('withBackdrop');
} else {
getBackgroundContainer().classList.remove('withBackdrop');
}
}
let hasInternalBackdrop;
function internalBackdrop(isEnabled) {
hasInternalBackdrop = isEnabled;
setBackgroundContainerBackgroundEnabled();
}
let hasExternalBackdrop;
export function externalBackdrop(isEnabled) {
hasExternalBackdrop = isEnabled;
setBackgroundContainerBackgroundEnabled();
}
let currentLoadingBackdrop;
function setBackdropImage(url) {
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
const elem = getBackdropContainer();
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
if (existingBackdropImage.getAttribute('data-url') === url) {
return; return;
} }
existingBackdropImage.classList.remove('displayingBackdropImage');
clearRotation();
currentRotatingImages = images;
currentRotationIndex = -1;
if (images.length > 1 && enableImageRotation !== false && enableRotation()) {
rotationInterval = setInterval(onRotationInterval, 24000);
}
onRotationInterval();
} }
function onRotationInterval() { const instance = new Backdrop();
if (playbackManager.isPlayingLocally(['Video'])) { instance.load(url, elem, existingBackdropImage);
return; currentLoadingBackdrop = instance;
} }
let newIndex = currentRotationIndex + 1; function getItemImageUrls(item, imageOptions) {
if (newIndex >= currentRotatingImages.length) { imageOptions = imageOptions || {};
newIndex = 0;
}
currentRotationIndex = newIndex; const apiClient = ServerConnections.getApiClient(item.ServerId);
setBackdropImage(currentRotatingImages[newIndex]); if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
return item.BackdropImageTags.map((imgTag, index) => {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: 'Backdrop',
tag: imgTag,
maxWidth: dom.getScreenWidth(),
index: index
}));
});
} }
function clearRotation() { if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
const interval = rotationInterval; return item.ParentBackdropImageTags.map((imgTag, index) => {
if (interval) { return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
clearInterval(interval); type: 'Backdrop',
} tag: imgTag,
maxWidth: dom.getScreenWidth(),
rotationInterval = null; index: index
currentRotatingImages = []; }));
currentRotationIndex = -1; });
} }
export function setBackdrop(url, imageOptions) { return [];
if (url && typeof url !== 'string') { }
url = getImageUrls([url], imageOptions)[0];
}
if (url) { function getImageUrls(items, imageOptions) {
clearRotation(); const list = [];
setBackdropImage(url); const onImg = img => {
list.push(img);
};
for (let i = 0, length = items.length; i < length; i++) {
const itemImages = getItemImageUrls(items[i], imageOptions);
itemImages.forEach(onImg);
}
return list;
}
function enabled() {
return userSettings.enableBackdrops();
}
let rotationInterval;
let currentRotatingImages = [];
let currentRotationIndex = -1;
export function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) {
const images = getImageUrls(items, imageOptions);
if (images.length) {
startRotation(images, enableImageRotation);
} else { } else {
clearBackdrop(); clearBackdrop();
} }
} }
}
/* eslint-enable indent */ function startRotation(images, enableImageRotation) {
if (isEqual(images, currentRotatingImages)) {
return;
}
clearRotation();
currentRotatingImages = images;
currentRotationIndex = -1;
if (images.length > 1 && enableImageRotation !== false && enableRotation()) {
rotationInterval = setInterval(onRotationInterval, 24000);
}
onRotationInterval();
}
function onRotationInterval() {
if (playbackManager.isPlayingLocally(['Video'])) {
return;
}
let newIndex = currentRotationIndex + 1;
if (newIndex >= currentRotatingImages.length) {
newIndex = 0;
}
currentRotationIndex = newIndex;
setBackdropImage(currentRotatingImages[newIndex]);
}
function clearRotation() {
const interval = rotationInterval;
if (interval) {
clearInterval(interval);
}
rotationInterval = null;
currentRotatingImages = [];
currentRotationIndex = -1;
}
export function setBackdrop(url, imageOptions) {
if (url && typeof url !== 'string') {
url = getImageUrls([url], imageOptions)[0];
}
if (url) {
clearRotation();
setBackdropImage(url);
} else {
clearBackdrop();
}
}
/** /**
* @enum TransparencyLevel * @enum TransparencyLevel
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for building cards from item data. * Module for building cards from item data.
@@ -12,123 +11,121 @@ import layoutManager from '../layoutManager';
import browser from '../../scripts/browser'; import browser from '../../scripts/browser';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
const enableFocusTransform = !browser.slow && !browser.edge; const enableFocusTransform = !browser.slow && !browser.edge;
function buildChapterCardsHtml(item, chapters, options) { function buildChapterCardsHtml(item, chapters, options) {
// TODO move card creation code to Card component // TODO move card creation code to Card component
let className = 'card itemAction chapterCard'; let className = 'card itemAction chapterCard';
if (layoutManager.tv) { if (layoutManager.tv) {
className += ' show-focus'; className += ' show-focus';
if (enableFocusTransform) { if (enableFocusTransform) {
className += ' show-animation'; className += ' show-animation';
}
} }
const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
const videoStream = mediaStreams.filter(({Type}) => {
return Type === 'Video';
})[0] || {};
let shape = (options.backdropShape || 'backdrop');
if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) {
shape = (options.squareShape || 'square');
}
className += ` ${shape}Card`;
if (options.block || options.rows) {
className += ' block';
}
let html = '';
let itemsInRow = 0;
const apiClient = ServerConnections.getApiClient(item.ServerId);
for (let i = 0, length = chapters.length; i < length; i++) {
if (options.rows && itemsInRow === 0) {
html += '<div class="cardColumn">';
}
const chapter = chapters[i];
html += buildChapterCard(item, apiClient, chapter, i, options, className, shape);
itemsInRow++;
if (options.rows && itemsInRow >= options.rows) {
itemsInRow = 0;
html += '</div>';
}
}
return html;
} }
function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) { const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
if (ImageTag) { const videoStream = mediaStreams.filter(({ Type }) => {
return apiClient.getScaledImageUrl(Id, { return Type === 'Video';
})[0] || {};
maxWidth: maxWidth, let shape = (options.backdropShape || 'backdrop');
tag: ImageTag,
type: 'Chapter',
index
});
}
return null; if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) {
shape = (options.squareShape || 'square');
} }
function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) { className += ` ${shape}Card`;
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; if (options.block || options.rows) {
if (coverImage) { className += ' block';
cardImageContainerClass += ' coveredImage';
}
const dataAttributes = ` data-action="play" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-positionticks="${chapter.StartPositionTicks}"`;
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
if (!imgUrl) {
cardImageContainer += '<span class="material-icons cardImageIcon local_movies" aria-hidden="true"></span>';
}
let nameHtml = '';
nameHtml += `<div class="cardText">${escapeHtml(chapter.Name)}</div>`;
nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
const cardBoxCssClass = 'cardBox';
const cardScalableClass = 'cardScalable';
return `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
} }
export function buildChapterCards(item, chapters, options) { let html = '';
if (options.parentContainer) { let itemsInRow = 0;
// Abort if the container has been disposed
if (!document.body.contains(options.parentContainer)) {
return;
}
if (chapters.length) { const apiClient = ServerConnections.getApiClient(item.ServerId);
options.parentContainer.classList.remove('hide');
} else { for (let i = 0, length = chapters.length; i < length; i++) {
options.parentContainer.classList.add('hide'); if (options.rows && itemsInRow === 0) {
return; html += '<div class="cardColumn">';
}
} }
const html = buildChapterCardsHtml(item, chapters, options); const chapter = chapters[i];
options.itemsContainer.innerHTML = html; html += buildChapterCard(item, apiClient, chapter, i, options, className, shape);
itemsInRow++;
imageLoader.lazyChildren(options.itemsContainer); if (options.rows && itemsInRow >= options.rows) {
itemsInRow = 0;
html += '</div>';
}
} }
/* eslint-enable indent */ return html;
}
function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) {
if (ImageTag) {
return apiClient.getScaledImageUrl(Id, {
maxWidth: maxWidth,
tag: ImageTag,
type: 'Chapter',
index
});
}
return null;
}
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';
if (coverImage) {
cardImageContainerClass += ' coveredImage';
}
const dataAttributes = ` data-action="play" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-positionticks="${chapter.StartPositionTicks}"`;
let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
if (!imgUrl) {
cardImageContainer += '<span class="material-icons cardImageIcon local_movies" aria-hidden="true"></span>';
}
let nameHtml = '';
nameHtml += `<div class="cardText">${escapeHtml(chapter.Name)}</div>`;
nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
const cardBoxCssClass = 'cardBox';
const cardScalableClass = 'cardScalable';
return `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
}
export function buildChapterCards(item, chapters, options) {
if (options.parentContainer) {
// Abort if the container has been disposed
if (!document.body.contains(options.parentContainer)) {
return;
}
if (chapters.length) {
options.parentContainer.classList.remove('hide');
} else {
options.parentContainer.classList.add('hide');
return;
}
}
const html = buildChapterCardsHtml(item, chapters, options);
options.itemsContainer.innerHTML = html;
imageLoader.lazyChildren(options.itemsContainer);
}
export default { export default {
buildChapterCards: buildChapterCards buildChapterCards: buildChapterCards
+12 -15
View File
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for building cards from item data. * Module for building cards from item data.
@@ -7,20 +6,18 @@
import cardBuilder from './cardBuilder'; import cardBuilder from './cardBuilder';
export function buildPeopleCards(items, options) { export function buildPeopleCards(items, options) {
options = Object.assign(options || {}, { options = Object.assign(options || {}, {
cardLayout: false, cardLayout: false,
centerText: true, centerText: true,
showTitle: true, showTitle: true,
cardFooterAside: 'none', cardFooterAside: 'none',
showPersonRoleOrType: true, showPersonRoleOrType: true,
cardCssClass: 'personCard', cardCssClass: 'personCard',
defaultCardImageIcon: 'person' defaultCardImageIcon: 'person'
}); });
cardBuilder.buildCards(items, options); cardBuilder.buildCards(items, options);
} }
/* eslint-enable indent */
export default { export default {
buildPeopleCards: buildPeopleCards buildPeopleCards: buildPeopleCards
@@ -16,254 +16,251 @@ import '../../styles/flexstyles.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
/* eslint-disable indent */ let currentServerId;
let currentServerId; function onSubmit(e) {
loading.show();
function onSubmit(e) { const panel = dom.parentWithClass(this, 'dialog');
loading.show();
const panel = dom.parentWithClass(this, 'dialog'); const collectionId = panel.querySelector('#selectCollectionToAddTo').value;
const collectionId = panel.querySelector('#selectCollectionToAddTo').value; const apiClient = ServerConnections.getApiClient(currentServerId);
const apiClient = ServerConnections.getApiClient(currentServerId); if (collectionId) {
addToCollection(apiClient, panel, collectionId);
if (collectionId) { } else {
addToCollection(apiClient, panel, collectionId); createCollection(apiClient, panel);
} else {
createCollection(apiClient, panel);
}
e.preventDefault();
return false;
} }
function createCollection(apiClient, dlg) { e.preventDefault();
const url = apiClient.getUrl('Collections', { return false;
}
Name: dlg.querySelector('#txtNewCollectionName').value, function createCollection(apiClient, dlg) {
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, const url = apiClient.getUrl('Collections', {
Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
});
apiClient.ajax({ Name: dlg.querySelector('#txtNewCollectionName').value,
type: 'POST', IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
url: url, Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
dataType: 'json' });
}).then(result => { apiClient.ajax({
loading.hide(); type: 'POST',
url: url,
dataType: 'json'
const id = result.Id; }).then(result => {
loading.hide();
dlg.submitted = true; const id = result.Id;
dialogHelper.close(dlg);
redirectToCollection(apiClient, id);
});
}
function redirectToCollection(apiClient, id) { dlg.submitted = true;
appRouter.showItem(id, apiClient.serverId()); dialogHelper.close(dlg);
} redirectToCollection(apiClient, id);
});
}
function addToCollection(apiClient, dlg, id) { function redirectToCollection(apiClient, id) {
const url = apiClient.getUrl(`Collections/${id}/Items`, { appRouter.showItem(id, apiClient.serverId());
}
Ids: dlg.querySelector('.fldSelectedItemIds').value || '' function addToCollection(apiClient, dlg, id) {
}); const url = apiClient.getUrl(`Collections/${id}/Items`, {
apiClient.ajax({ Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
type: 'POST', });
url: url
}).then(() => { apiClient.ajax({
loading.hide(); type: 'POST',
url: url
dlg.submitted = true; }).then(() => {
dialogHelper.close(dlg); loading.hide();
toast(globalize.translate('MessageItemsAdded')); dlg.submitted = true;
}); dialogHelper.close(dlg);
}
function triggerChange(select) { toast(globalize.translate('MessageItemsAdded'));
select.dispatchEvent(new CustomEvent('change', {})); });
} }
function populateCollections(panel) { function triggerChange(select) {
loading.show(); select.dispatchEvent(new CustomEvent('change', {}));
}
const select = panel.querySelector('#selectCollectionToAddTo'); function populateCollections(panel) {
loading.show();
panel.querySelector('.newCollectionInfo').classList.add('hide'); const select = panel.querySelector('#selectCollectionToAddTo');
const options = { panel.querySelector('.newCollectionInfo').classList.add('hide');
Recursive: true, const options = {
IncludeItemTypes: 'BoxSet',
SortBy: 'SortName',
EnableTotalRecordCount: false
};
const apiClient = ServerConnections.getApiClient(currentServerId); Recursive: true,
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { IncludeItemTypes: 'BoxSet',
let html = ''; SortBy: 'SortName',
EnableTotalRecordCount: false
};
html += `<option value="">${globalize.translate('OptionNew')}</option>`; const apiClient = ServerConnections.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
html += result.Items.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
});
select.innerHTML = html;
select.value = '';
triggerChange(select);
loading.hide();
});
}
function getEditorHtml() {
let html = ''; let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">'; html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form class="newCollectionForm" style="margin:auto;">';
html += '<div>'; html += result.Items.map(i => {
html += globalize.translate('NewCollectionHelp'); return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
html += '</div>';
html += '<div class="fldSelectCollection">';
html += '<br/>';
html += '<br/>';
html += '<div class="selectContainer">';
html += `<select is="emby-select" label="${globalize.translate('LabelCollection')}" id="selectCollectionToAddTo" autofocus></select>`;
html += '</div>';
html += '</div>';
html += '<div class="newCollectionInfo">';
html += '<div class="inputContainer">';
html += `<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="${globalize.translate('LabelName')}" />`;
html += `<div class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
html += '</div>';
html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
html += '</label>';
// newCollectionInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide');
content.querySelector('#txtNewCollectionName').removeAttribute('required');
} else {
content.querySelector('.newCollectionInfo').classList.remove('hide');
content.querySelector('#txtNewCollectionName').setAttribute('required', 'required');
}
}); });
content.querySelector('form').addEventListener('submit', onSubmit); select.innerHTML = html;
select.value = '';
triggerChange(select);
content.querySelector('.fldSelectedItemIds', content).value = items.join(','); loading.hide();
});
}
if (items.length) { function getEditorHtml() {
content.querySelector('.fldSelectCollection').classList.remove('hide'); let html = '';
populateCollections(content);
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form class="newCollectionForm" style="margin:auto;">';
html += '<div>';
html += globalize.translate('NewCollectionHelp');
html += '</div>';
html += '<div class="fldSelectCollection">';
html += '<br/>';
html += '<br/>';
html += '<div class="selectContainer">';
html += `<select is="emby-select" label="${globalize.translate('LabelCollection')}" id="selectCollectionToAddTo" autofocus></select>`;
html += '</div>';
html += '</div>';
html += '<div class="newCollectionInfo">';
html += '<div class="inputContainer">';
html += `<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="${globalize.translate('LabelName')}" />`;
html += `<div class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
html += '</div>';
html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
html += '</label>';
// newCollectionInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide');
content.querySelector('#txtNewCollectionName').removeAttribute('required');
} else { } else {
content.querySelector('.fldSelectCollection').classList.add('hide'); content.querySelector('.newCollectionInfo').classList.remove('hide');
content.querySelector('#txtNewCollectionName').setAttribute('required', 'required');
const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
selectCollectionToAddTo.innerHTML = '';
selectCollectionToAddTo.value = '';
triggerChange(selectCollectionToAddTo);
} }
} });
function centerFocus(elem, horiz, on) { content.querySelector('form').addEventListener('submit', onSubmit);
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off'; content.querySelector('.fldSelectedItemIds', content).value = items.join(',');
scrollHelper.centerFocus[fn](elem, horiz);
if (items.length) {
content.querySelector('.fldSelectCollection').classList.remove('hide');
populateCollections(content);
} else {
content.querySelector('.fldSelectCollection').classList.add('hide');
const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
selectCollectionToAddTo.innerHTML = '';
selectCollectionToAddTo.value = '';
triggerChange(selectCollectionToAddTo);
}
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
class CollectionEditor {
show(options) {
const items = options.items || {};
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
}); });
} }
}
class CollectionEditor {
show(options) {
const items = options.items || {};
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
}
}
/* eslint-enable indent */
export default CollectionEditor; export default CollectionEditor;
+1 -1
View File
@@ -22,7 +22,7 @@ const Filter: FC<FilterProps> = ({
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const showFilterMenu = useCallback(() => { const showFilterMenu = useCallback(() => {
import('../filtermenu/filtermenu').then(({default: FilterMenu}) => { import('../filtermenu/filtermenu').then(({ default: FilterMenu }) => {
const filterMenu = new FilterMenu(); const filterMenu = new FilterMenu();
filterMenu.show({ filterMenu.show({
settings: viewQuerySettings, settings: viewQuerySettings,
+1 -1
View File
@@ -6,7 +6,7 @@ const NewCollection: FC = () => {
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const showCollectionEditor = useCallback(() => { const showCollectionEditor = useCallback(() => {
import('../collectionEditor/collectionEditor').then(({default: CollectionEditor}) => { import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => {
const serverId = window.ApiClient.serverId(); const serverId = window.ApiClient.serverId();
const collectionEditor = new CollectionEditor(); const collectionEditor = new CollectionEditor();
collectionEditor.show({ collectionEditor.show({
+1 -1
View File
@@ -16,7 +16,7 @@ const SelectView: FC<SelectViewProps> = ({
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const showViewSettingsMenu = useCallback(() => { const showViewSettingsMenu = useCallback(() => {
import('../viewSettings/viewSettings').then(({default: ViewSettings}) => { import('../viewSettings/viewSettings').then(({ default: ViewSettings }) => {
const viewsettings = new ViewSettings(); const viewsettings = new ViewSettings();
viewsettings.show({ viewsettings.show({
settings: viewQuerySettings, settings: viewQuerySettings,
+1 -1
View File
@@ -19,7 +19,7 @@ const Sort: FC<SortProps> = ({
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const showSortMenu = useCallback(() => { const showSortMenu = useCallback(() => {
import('../sortmenu/sortmenu').then(({default: SortMenu}) => { import('../sortmenu/sortmenu').then(({ default: SortMenu }) => {
const sortMenu = new SortMenu(); const sortMenu = new SortMenu();
sortMenu.show({ sortMenu.show({
settings: viewQuerySettings, settings: viewQuerySettings,
@@ -14,7 +14,7 @@ type IProps = {
children?: React.ReactNode 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 ( return (
<div className={containerClassName}> <div className={containerClassName}>
<h2>{globalize.translate(headerTitle)}</h2> <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)); 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 ( return (
<div <div
className='liSchedule listItem' className='liSchedule listItem'
@@ -5,7 +5,7 @@ type IProps = {
tag?: string; tag?: string;
} }
const BlockedTagList: FunctionComponent<IProps> = ({tag}: IProps) => { const BlockedTagList: FunctionComponent<IProps> = ({ tag }: IProps) => {
return ( return (
<div className='paperList'> <div className='paperList'>
<div className='listItem'> <div className='listItem'>
@@ -36,7 +36,7 @@ const createLinkElement = (activeTab: string) => ({
</a>` </a>`
}); });
const SectionTabs: FunctionComponent<IProps> = ({activeTab}: IProps) => { const SectionTabs: FunctionComponent<IProps> = ({ activeTab }: IProps) => {
return ( return (
<div <div
data-role='controlgroup' data-role='controlgroup'
@@ -74,7 +74,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
</div> </div>
<div className='cardFooter visualCardBox-cardFooter'> <div className='cardFooter visualCardBox-cardFooter'>
<div <div
style={{textAlign: 'right', float: 'right', paddingTop: '5px'}} style={{ textAlign: 'right', float: 'right', paddingTop: '5px' }}
> >
<IconButtonElement <IconButtonElement
is='paper-icon-button-light' is='paper-icon-button-light'
@@ -14,7 +14,7 @@ type IProps = {
userId: string; userId: string;
} }
const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => { const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
const loadUser = useCallback(() => { const loadUser = useCallback(() => {
@@ -76,7 +76,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false; chkEnableLocalEasyPassword.checked = user.Configuration.EnableLocalPassword || false;
import('../../autoFocuser').then(({default: autoFocuser}) => { import('../../autoFocuser').then(({ default: autoFocuser }) => {
autoFocuser.autoFocus(page); autoFocuser.autoFocus(page);
}); });
}); });
@@ -214,7 +214,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
<div ref={element}> <div ref={element}>
<form <form
className='updatePasswordForm passwordSection hide' className='updatePasswordForm passwordSection hide'
style={{margin: '0 auto 2em'}} style={{ margin: '0 auto 2em' }}
> >
<div className='detailSection'> <div className='detailSection'>
<div id='fldCurrentPassword' className='inputContainer hide'> <div id='fldCurrentPassword' className='inputContainer hide'>
@@ -260,7 +260,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({userId}: IProps) => {
<br /> <br />
<form <form
className='localAccessForm localAccessSection' className='localAccessForm localAccessSection'
style={{margin: '0 auto'}} style={{ margin: '0 auto' }}
> >
<div className='detailSection'> <div className='detailSection'>
<div className='detailSectionHeader'> <div className='detailSectionHeader'>
+102 -105
View File
@@ -13,129 +13,126 @@ import '../formdialog.scss';
import '../../styles/flexstyles.scss'; import '../../styles/flexstyles.scss';
import template from './dialog.template.html'; import template from './dialog.template.html';
/* eslint-disable indent */ function showDialog(options = { dialogOptions: {}, buttons: [] }) {
const dialogOptions = {
removeOnClose: true,
scrollY: false,
...options.dialogOptions
};
function showDialog(options = { dialogOptions: {}, buttons: [] }) { const enableTvLayout = layoutManager.tv;
const dialogOptions = {
removeOnClose: true,
scrollY: false,
...options.dialogOptions
};
const enableTvLayout = layoutManager.tv; if (enableTvLayout) {
dialogOptions.size = 'fullscreen';
}
if (enableTvLayout) { const dlg = dialogHelper.createDialog(dialogOptions);
dialogOptions.size = 'fullscreen';
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-content-center');
const formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.classList.add('no-grow');
if (enableTvLayout) {
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = `${Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)}px`;
dlg.classList.add('dialog-fullscreen-lowres');
}
if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || '';
} else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
}
const displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText);
if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide');
}
let i;
let length;
let html = '';
let hasDescriptions = false;
for (i = 0, length = options.buttons.length; i < length; i++) {
const item = options.buttons[i];
const autoFocus = i === 0 ? ' autofocus' : '';
let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ` button-${item.type}`;
} }
const dlg = dialogHelper.createDialog(dialogOptions); if (item.description) {
hasDescriptions = true;
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
dlg.classList.add('align-items-center');
dlg.classList.add('justify-content-center');
const formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.classList.add('no-grow');
if (enableTvLayout) {
formDialogContent.style['max-width'] = '50%';
formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false);
} else {
formDialogContent.style.maxWidth = `${Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)}px`;
dlg.classList.add('dialog-fullscreen-lowres');
} }
if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || '';
} else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
}
const displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText);
if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide');
}
let i;
let length;
let html = '';
let hasDescriptions = false;
for (i = 0, length = options.buttons.length; i < length; i++) {
const item = options.buttons[i];
const autoFocus = i === 0 ? ' autofocus' : '';
let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) {
buttonClass += ` button-${item.type}`;
}
if (item.description) {
hasDescriptions = true;
}
if (hasDescriptions) {
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
}
html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${escapeHtml(item.name)}</button>`;
if (item.description) {
html += `<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">${item.description}</div>`;
}
}
dlg.querySelector('.formDialogFooter').innerHTML = html;
if (hasDescriptions) { if (hasDescriptions) {
dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
} }
let dialogResult; html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${escapeHtml(item.name)}</button>`;
function onButtonClick() {
dialogResult = this.getAttribute('data-id'); if (item.description) {
dialogHelper.close(dlg); html += `<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">${item.description}</div>`;
} }
const buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
return dialogHelper.open(dlg).then(() => {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
if (dialogResult) {
return dialogResult;
} else {
return Promise.reject();
}
});
} }
export function show(text, title) { dlg.querySelector('.formDialogFooter').innerHTML = html;
let options;
if (typeof text === 'string') { if (hasDescriptions) {
options = { dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical');
title: title, }
text: text
}; let dialogResult;
function onButtonClick() {
dialogResult = this.getAttribute('data-id');
dialogHelper.close(dlg);
}
const buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick);
}
return dialogHelper.open(dlg).then(() => {
if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
if (dialogResult) {
return dialogResult;
} else { } else {
options = text; return Promise.reject();
} }
});
}
return showDialog(options); export function show(text, title) {
let options;
if (typeof text === 'string') {
options = {
title: title,
text: text
};
} else {
options = text;
} }
/* eslint-enable indent */ return showDialog(options);
}
export default { export default {
show: show show: show
}; };
+436 -441
View File
@@ -9,506 +9,501 @@ import dom from '../../scripts/dom';
import './dialoghelper.scss'; import './dialoghelper.scss';
import '../../styles/scrollstyles.scss'; import '../../styles/scrollstyles.scss';
/* eslint-disable indent */ let globalOnOpenCallback;
let globalOnOpenCallback; function enableAnimation() {
// too slow
function enableAnimation() { if (browser.tv) {
// too slow return false;
if (browser.tv) {
return false;
}
return browser.supportsCssAnimation();
} }
function removeCenterFocus(dlg) { return browser.supportsCssAnimation();
if (layoutManager.tv) { }
if (dlg.classList.contains('scrollX')) {
centerFocus(dlg, true, false); function removeCenterFocus(dlg) {
} else if (dlg.classList.contains('smoothScrollY')) { if (layoutManager.tv) {
centerFocus(dlg, false, false); if (dlg.classList.contains('scrollX')) {
} centerFocus(dlg, true, false);
} else if (dlg.classList.contains('smoothScrollY')) {
centerFocus(dlg, false, false);
}
}
}
function tryRemoveElement(elem) {
const parentNode = elem.parentNode;
if (parentNode) {
// Seeing crashes in edge webview
try {
parentNode.removeChild(elem);
} catch (err) {
console.error('[dialogHelper] error removing dialog element: ' + err);
}
}
}
function DialogHashHandler(dlg, hash, resolve) {
const self = this;
self.originalUrl = window.location.href;
const activeElement = document.activeElement;
let removeScrollLockOnClose = false;
let unlisten;
function onHashChange({ location }) {
const dialogs = location.state?.dialogs || [];
const shouldClose = !dialogs.includes(hash);
if ((shouldClose || !isOpened(dlg)) && unlisten) {
unlisten();
unlisten = null;
}
if (shouldClose) {
close(dlg);
} }
} }
function tryRemoveElement(elem) { function finishClose() {
const parentNode = elem.parentNode; if (unlisten) {
if (parentNode) { unlisten();
// Seeing crashes in edge webview unlisten = null;
try {
parentNode.removeChild(elem);
} catch (err) {
console.error('[dialogHelper] error removing dialog element: ' + err);
}
}
}
function DialogHashHandler(dlg, hash, resolve) {
const self = this;
self.originalUrl = window.location.href;
const activeElement = document.activeElement;
let removeScrollLockOnClose = false;
let unlisten;
function onHashChange({ location }) {
const dialogs = location.state?.dialogs || [];
const shouldClose = !dialogs.includes(hash);
if ((shouldClose || !isOpened(dlg)) && unlisten) {
unlisten();
unlisten = null;
}
if (shouldClose) {
close(dlg);
}
} }
function finishClose() { dlg.dispatchEvent(new CustomEvent('close', {
if (unlisten) {
unlisten();
unlisten = null;
}
dlg.dispatchEvent(new CustomEvent('close', {
bubbles: false,
cancelable: false
}));
resolve({
element: dlg
});
}
function onBackCommand(e) {
if (e.detail.command === 'back') {
e.preventDefault();
e.stopPropagation();
close(dlg);
}
}
function onDialogClosed() {
if (!isHistoryEnabled(dlg)) {
inputManager.off(dlg, onBackCommand);
}
if (unlisten) {
unlisten();
unlisten = null;
}
removeBackdrop(dlg);
dlg.classList.remove('opened');
if (removeScrollLockOnClose) {
document.body.classList.remove('noScroll');
}
if (isHistoryEnabled(dlg)) {
const state = history.location.state || {};
if (state.dialogs?.length > 0) {
if (state.dialogs[state.dialogs.length - 1] === hash) {
unlisten = history.listen(finishClose);
history.back();
} else if (state.dialogs.includes(hash)) {
console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash);
unlisten = history.listen(finishClose);
// Remove the closed dialog hash from the history state
history.replace(
`${history.location.pathname}${history.location.search}`,
{
...state,
dialogs: state.dialogs.filter(dialog => dialog !== hash)
}
);
}
}
}
if (layoutManager.tv) {
focusManager.focus(activeElement);
}
if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) {
removeCenterFocus(dlg);
const dialogContainer = dlg.dialogContainer;
if (dialogContainer) {
tryRemoveElement(dialogContainer);
dlg.dialogContainer = null;
} else {
tryRemoveElement(dlg);
}
}
if (!unlisten) {
finishClose();
}
}
dlg.addEventListener('_close', onDialogClosed);
const center = !dlg.classList.contains('dialog-fixedSize');
if (center) {
dlg.classList.add('centeredDialog');
}
dlg.classList.remove('hide');
addBackdropOverlay(dlg);
dlg.classList.add('opened');
dlg.dispatchEvent(new CustomEvent('open', {
bubbles: false, bubbles: false,
cancelable: false cancelable: false
})); }));
if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) { resolve({
document.body.classList.add('noScroll'); element: dlg
removeScrollLockOnClose = true; });
}
function onBackCommand(e) {
if (e.detail.command === 'back') {
e.preventDefault();
e.stopPropagation();
close(dlg);
}
}
function onDialogClosed() {
if (!isHistoryEnabled(dlg)) {
inputManager.off(dlg, onBackCommand);
} }
animateDialogOpen(dlg); if (unlisten) {
unlisten();
unlisten = null;
}
removeBackdrop(dlg);
dlg.classList.remove('opened');
if (removeScrollLockOnClose) {
document.body.classList.remove('noScroll');
}
if (isHistoryEnabled(dlg)) { if (isHistoryEnabled(dlg)) {
const state = history.location.state || {}; const state = history.location.state || {};
const dialogs = state.dialogs || []; if (state.dialogs?.length > 0) {
// Add new dialog to the list of open dialogs if (state.dialogs[state.dialogs.length - 1] === hash) {
dialogs.push(hash); unlisten = history.listen(finishClose);
history.back();
} else if (state.dialogs.includes(hash)) {
console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash);
history.push( unlisten = history.listen(finishClose);
`${history.location.pathname}${history.location.search}`,
{ // Remove the closed dialog hash from the history state
...state, history.replace(
dialogs `${history.location.pathname}${history.location.search}`,
{
...state,
dialogs: state.dialogs.filter(dialog => dialog !== hash)
}
);
} }
);
unlisten = history.listen(onHashChange);
} else {
inputManager.on(dlg, onBackCommand);
}
}
function addBackdropOverlay(dlg) {
const backdrop = document.createElement('div');
backdrop.classList.add('dialogBackdrop');
const backdropParent = dlg.dialogContainer || dlg;
backdropParent.parentNode.insertBefore(backdrop, backdropParent);
dlg.backdrop = backdrop;
// trigger reflow or the backdrop will not animate
void backdrop.offsetWidth;
backdrop.classList.add('dialogBackdropOpened');
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => {
if (e.target === dlg.dialogContainer) {
close(dlg);
} }
}, { }
passive: true
});
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => { if (layoutManager.tv) {
if (e.target === dlg.dialogContainer) { focusManager.focus(activeElement);
// Close the application dialog menu }
close(dlg);
// Prevent the default browser context menu from appearing if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) {
e.preventDefault(); removeCenterFocus(dlg);
const dialogContainer = dlg.dialogContainer;
if (dialogContainer) {
tryRemoveElement(dialogContainer);
dlg.dialogContainer = null;
} else {
tryRemoveElement(dlg);
} }
});
}
function isHistoryEnabled(dlg) {
return dlg.getAttribute('data-history') === 'true';
}
export function open(dlg) {
if (globalOnOpenCallback) {
globalOnOpenCallback(dlg);
} }
const parent = dlg.parentNode; if (!unlisten) {
if (parent) { finishClose();
parent.removeChild(dlg);
} }
const dialogContainer = document.createElement('div');
dialogContainer.classList.add('dialogContainer');
dialogContainer.appendChild(dlg);
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise((resolve) => {
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
});
} }
function isOpened(dlg) { dlg.addEventListener('_close', onDialogClosed);
//return dlg.opened;
return !dlg.classList.contains('hide'); const center = !dlg.classList.contains('dialog-fixedSize');
if (center) {
dlg.classList.add('centeredDialog');
} }
export function close(dlg) { dlg.classList.remove('hide');
if (!dlg.classList.contains('hide')) {
dlg.dispatchEvent(new CustomEvent('closing', { addBackdropOverlay(dlg);
dlg.classList.add('opened');
dlg.dispatchEvent(new CustomEvent('open', {
bubbles: false,
cancelable: false
}));
if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) {
document.body.classList.add('noScroll');
removeScrollLockOnClose = true;
}
animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) {
const state = history.location.state || {};
const dialogs = state.dialogs || [];
// Add new dialog to the list of open dialogs
dialogs.push(hash);
history.push(
`${history.location.pathname}${history.location.search}`,
{
...state,
dialogs
}
);
unlisten = history.listen(onHashChange);
} else {
inputManager.on(dlg, onBackCommand);
}
}
function addBackdropOverlay(dlg) {
const backdrop = document.createElement('div');
backdrop.classList.add('dialogBackdrop');
const backdropParent = dlg.dialogContainer || dlg;
backdropParent.parentNode.insertBefore(backdrop, backdropParent);
dlg.backdrop = backdrop;
// trigger reflow or the backdrop will not animate
void backdrop.offsetWidth;
backdrop.classList.add('dialogBackdropOpened');
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => {
if (e.target === dlg.dialogContainer) {
close(dlg);
}
}, {
passive: true
});
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => {
if (e.target === dlg.dialogContainer) {
// Close the application dialog menu
close(dlg);
// Prevent the default browser context menu from appearing
e.preventDefault();
}
});
}
function isHistoryEnabled(dlg) {
return dlg.getAttribute('data-history') === 'true';
}
export function open(dlg) {
if (globalOnOpenCallback) {
globalOnOpenCallback(dlg);
}
const parent = dlg.parentNode;
if (parent) {
parent.removeChild(dlg);
}
const dialogContainer = document.createElement('div');
dialogContainer.classList.add('dialogContainer');
dialogContainer.appendChild(dlg);
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise((resolve) => {
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
});
}
function isOpened(dlg) {
return !dlg.classList.contains('hide');
}
export function close(dlg) {
if (!dlg.classList.contains('hide')) {
dlg.dispatchEvent(new CustomEvent('closing', {
bubbles: false,
cancelable: false
}));
const onAnimationFinish = () => {
focusManager.popScope(dlg);
dlg.classList.add('hide');
dlg.dispatchEvent(new CustomEvent('_close', {
bubbles: false, bubbles: false,
cancelable: false cancelable: false
})); }));
};
const onAnimationFinish = () => { animateDialogClose(dlg, onAnimationFinish);
focusManager.popScope(dlg);
dlg.classList.add('hide');
dlg.dispatchEvent(new CustomEvent('_close', {
bubbles: false,
cancelable: false
}));
};
animateDialogClose(dlg, onAnimationFinish);
}
} }
}
const getAnimationEndHandler = (dlg, callback) => function handler() { const getAnimationEndHandler = (dlg, callback) => function handler() {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true });
callback(); callback();
};
function animateDialogOpen(dlg) {
const onAnimationFinish = () => {
focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') {
focusManager.autoFocus(dlg);
}
if (document.activeElement && !dlg.contains(document.activeElement)) {
// Blur foreign element to prevent triggering of an action from the previous scope
document.activeElement.blur();
}
}; };
function animateDialogOpen(dlg) { if (enableAnimation()) {
const onAnimationFinish = () => { dom.addEventListener(
focusManager.pushScope(dlg); dlg,
dom.whichAnimationEvent(),
getAnimationEndHandler(dlg, onAnimationFinish),
{ once: true });
if (dlg.getAttribute('data-autofocus') === 'true') { return;
focusManager.autoFocus(dlg); }
}
if (document.activeElement && !dlg.contains(document.activeElement)) { onAnimationFinish();
// Blur foreign element to prevent triggering of an action from the previous scope }
document.activeElement.blur();
}
};
if (enableAnimation()) { function animateDialogClose(dlg, onAnimationFinish) {
dom.addEventListener( if (enableAnimation()) {
dlg, let animated = true;
dom.whichAnimationEvent(),
getAnimationEndHandler(dlg, onAnimationFinish),
{ once: true });
switch (dlg.animationConfig.exit.name) {
case 'fadeout':
dlg.style.animation = `fadeout ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
case 'scaledown':
dlg.style.animation = `scaledown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
case 'slidedown':
dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
default:
animated = false;
break;
}
dom.addEventListener(
dlg,
dom.whichAnimationEvent(),
getAnimationEndHandler(dlg, onAnimationFinish),
{ once: true });
if (animated) {
return; return;
} }
onAnimationFinish();
} }
function animateDialogClose(dlg, onAnimationFinish) { onAnimationFinish();
if (enableAnimation()) { }
let animated = true;
switch (dlg.animationConfig.exit.name) { const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
case 'fadeout':
dlg.style.animation = `fadeout ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`; function shouldLockDocumentScroll(options) {
break; if (options.lockScroll != null) {
case 'scaledown': return options.lockScroll;
dlg.style.animation = `scaledown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`; }
break;
case 'slidedown': if (options.size === 'fullscreen') {
dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`; return true;
break; }
default:
animated = false; if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
break; return false;
}
if (options.size) {
return true;
}
return browser.touch;
}
function removeBackdrop(dlg) {
const backdrop = dlg.backdrop;
if (!backdrop) {
return;
}
dlg.backdrop = null;
const onAnimationFinish = () => {
tryRemoveElement(backdrop);
};
if (enableAnimation()) {
backdrop.classList.remove('dialogBackdropOpened');
// this is not firing animationend
setTimeout(onAnimationFinish, 300);
return;
}
onAnimationFinish();
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export function createDialog(options = {}) {
// If there's no native dialog support, use a plain div
// Also not working well in samsung tizen browser, content inside not clickable
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
const dlg = document.createElement('div');
// Add an id so we can access the dialog element
if (options.id) {
dlg.id = options.id;
}
dlg.classList.add('focuscontainer');
dlg.classList.add('hide');
if (shouldLockDocumentScroll(options)) {
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory !== false) {
dlg.setAttribute('data-history', 'true');
}
// without this safari will scroll the background instead of the dialog contents
// but not needed here since this is already on top of an existing dialog
// but skip it in IE because it's causing the entire browser to hang
// Also have to disable for firefox because it's causing select elements to not be clickable
if (options.modal !== false) {
dlg.setAttribute('modal', 'modal');
}
if (options.autoFocus !== false) {
dlg.setAttribute('data-autofocus', 'true');
}
const defaultEntryAnimation = 'scaleup';
const defaultExitAnimation = 'scaledown';
const entryAnimation = options.entryAnimation || defaultEntryAnimation;
const exitAnimation = options.exitAnimation || defaultExitAnimation;
// If it's not fullscreen then lower the default animation speed to make it open really fast
const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
dlg.animationConfig = {
// scale up
'entry': {
name: entryAnimation,
timing: {
duration: entryAnimationDuration,
easing: 'ease-out'
} }
},
dom.addEventListener( // fade out
dlg, 'exit': {
dom.whichAnimationEvent(), name: exitAnimation,
getAnimationEndHandler(dlg, onAnimationFinish), timing: {
{ once: true }); duration: exitAnimationDuration,
easing: 'ease-out',
if (animated) { fill: 'both'
return;
} }
} }
};
onAnimationFinish(); dlg.classList.add('dialog');
if (options.scrollX) {
dlg.classList.add('scrollX');
dlg.classList.add('smoothScrollX');
if (layoutManager.tv) {
centerFocus(dlg, true, true);
}
} else if (options.scrollY !== false) {
dlg.classList.add('smoothScrollY');
if (layoutManager.tv) {
centerFocus(dlg, false, true);
}
} }
const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style; if (options.removeOnClose) {
dlg.setAttribute('data-removeonclose', 'true');
function shouldLockDocumentScroll(options) {
if (options.lockScroll != null) {
return options.lockScroll;
}
if (options.size === 'fullscreen') {
return true;
}
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
return false;
}
if (options.size) {
return true;
}
return browser.touch;
} }
function removeBackdrop(dlg) { if (options.size) {
const backdrop = dlg.backdrop; dlg.classList.add('dialog-fixedSize');
dlg.classList.add(`dialog-${options.size}`);
if (!backdrop) {
return;
}
dlg.backdrop = null;
const onAnimationFinish = () => {
tryRemoveElement(backdrop);
};
if (enableAnimation()) {
backdrop.classList.remove('dialogBackdropOpened');
// this is not firing animationend
setTimeout(onAnimationFinish, 300);
return;
}
onAnimationFinish();
} }
function centerFocus(elem, horiz, on) { if (enableAnimation()) {
import('../../scripts/scrollHelper').then((scrollHelper) => { switch (dlg.animationConfig.entry.name) {
const fn = on ? 'on' : 'off'; case 'fadein':
scrollHelper.centerFocus[fn](elem, horiz); dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`;
}); break;
case 'scaleup':
dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`;
break;
case 'slideup':
dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`;
break;
case 'slidedown':
dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`;
break;
default:
break;
}
} }
export function createDialog(options = {}) { return dlg;
// If there's no native dialog support, use a plain div }
// Also not working well in samsung tizen browser, content inside not clickable
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
const dlg = document.createElement('div');
// Add an id so we can access the dialog element export function setOnOpen(val) {
if (options.id) { globalOnOpenCallback = val;
dlg.id = options.id; }
}
dlg.classList.add('focuscontainer');
dlg.classList.add('hide');
if (shouldLockDocumentScroll(options)) {
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory !== false) {
dlg.setAttribute('data-history', 'true');
}
// without this safari will scroll the background instead of the dialog contents
// but not needed here since this is already on top of an existing dialog
// but skip it in IE because it's causing the entire browser to hang
// Also have to disable for firefox because it's causing select elements to not be clickable
if (options.modal !== false) {
dlg.setAttribute('modal', 'modal');
}
if (options.autoFocus !== false) {
dlg.setAttribute('data-autofocus', 'true');
}
const defaultEntryAnimation = 'scaleup';
const defaultExitAnimation = 'scaledown';
const entryAnimation = options.entryAnimation || defaultEntryAnimation;
const exitAnimation = options.exitAnimation || defaultExitAnimation;
// If it's not fullscreen then lower the default animation speed to make it open really fast
const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
dlg.animationConfig = {
// scale up
'entry': {
name: entryAnimation,
timing: {
duration: entryAnimationDuration,
easing: 'ease-out'
}
},
// fade out
'exit': {
name: exitAnimation,
timing: {
duration: exitAnimationDuration,
easing: 'ease-out',
fill: 'both'
}
}
};
dlg.classList.add('dialog');
if (options.scrollX) {
dlg.classList.add('scrollX');
dlg.classList.add('smoothScrollX');
if (layoutManager.tv) {
centerFocus(dlg, true, true);
}
} else if (options.scrollY !== false) {
dlg.classList.add('smoothScrollY');
if (layoutManager.tv) {
centerFocus(dlg, false, true);
}
}
if (options.removeOnClose) {
dlg.setAttribute('data-removeonclose', 'true');
}
if (options.size) {
dlg.classList.add('dialog-fixedSize');
dlg.classList.add(`dialog-${options.size}`);
}
if (enableAnimation()) {
switch (dlg.animationConfig.entry.name) {
case 'fadein':
dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`;
break;
case 'scaleup':
dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`;
break;
case 'slideup':
dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`;
break;
case 'slidedown':
dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`;
break;
default:
break;
}
}
return dlg;
}
export function setOnOpen(val) {
globalOnOpenCallback = val;
}
/* eslint-enable indent */
export default { export default {
open: open, open: open,
+211 -214
View File
@@ -18,238 +18,235 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './displaySettings.template.html'; import template from './displaySettings.template.html';
/* eslint-disable indent */ function fillThemes(select, selectedTheme) {
skinManager.getThemes().then(themes => {
function fillThemes(select, selectedTheme) { select.innerHTML = themes.map(t => {
skinManager.getThemes().then(themes => { return `<option value="${t.id}">${escapeHtml(t.name)}</option>`;
select.innerHTML = themes.map(t => {
return `<option value="${t.id}">${escapeHtml(t.name)}</option>`;
}).join('');
// get default theme
const defaultTheme = themes.find(theme => theme.default);
// set the current theme
select.value = selectedTheme || defaultTheme.id;
});
}
function loadScreensavers(context, userSettings) {
const selectScreensaver = context.querySelector('.selectScreensaver');
const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => {
return {
name: plugin.name,
value: plugin.id
};
});
options.unshift({
name: globalize.translate('None'),
value: 'none'
});
selectScreensaver.innerHTML = options.map(o => {
return `<option value="${o.value}">${escapeHtml(o.name)}</option>`;
}).join(''); }).join('');
selectScreensaver.value = userSettings.screensaver(); // get default theme
const defaultTheme = themes.find(theme => theme.default);
if (!selectScreensaver.value) { // set the current theme
// TODO: set the default instead of none select.value = selectedTheme || defaultTheme.id;
selectScreensaver.value = 'none'; });
} }
function loadScreensavers(context, userSettings) {
const selectScreensaver = context.querySelector('.selectScreensaver');
const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => {
return {
name: plugin.name,
value: plugin.id
};
});
options.unshift({
name: globalize.translate('None'),
value: 'none'
});
selectScreensaver.innerHTML = options.map(o => {
return `<option value="${o.value}">${escapeHtml(o.name)}</option>`;
}).join('');
selectScreensaver.value = userSettings.screensaver();
if (!selectScreensaver.value) {
// TODO: set the default instead of none
selectScreensaver.value = 'none';
}
}
function showOrHideMissingEpisodesField(context) {
if (browser.tizen || browser.web0s) {
context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide');
return;
} }
function showOrHideMissingEpisodesField(context) { context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide');
if (browser.tizen || browser.web0s) { }
context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide');
return;
}
context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); function loadForm(context, user, userSettings) {
if (appHost.supports('displaylanguage')) {
context.querySelector('.languageSection').classList.remove('hide');
} else {
context.querySelector('.languageSection').classList.add('hide');
} }
function loadForm(context, user, userSettings) { if (appHost.supports('displaymode')) {
if (appHost.supports('displaylanguage')) { context.querySelector('.fldDisplayMode').classList.remove('hide');
context.querySelector('.languageSection').classList.remove('hide'); } else {
} else { context.querySelector('.fldDisplayMode').classList.add('hide');
context.querySelector('.languageSection').classList.add('hide');
}
if (appHost.supports('displaymode')) {
context.querySelector('.fldDisplayMode').classList.remove('hide');
} else {
context.querySelector('.fldDisplayMode').classList.add('hide');
}
if (appHost.supports('externallinks')) {
context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
} else {
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
}
context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator);
if (appHost.supports('screensaver')) {
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
} else {
context.querySelector('.selectScreensaverContainer').classList.add('hide');
}
if (datetime.supportsLocalization()) {
context.querySelector('.fldDateTimeLocale').classList.remove('hide');
} else {
context.querySelector('.fldDateTimeLocale').classList.add('hide');
}
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
loadScreensavers(context, userSettings);
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss();
context.querySelector('#txtLocalCustomCss').value = userSettings.customCss();
context.querySelector('#selectLanguage').value = userSettings.language() || '';
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp();
context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp();
context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume();
context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || '';
showOrHideMissingEpisodesField(context);
loading.hide();
} }
function saveUser(context, user, userSettingsInstance, apiClient) { if (appHost.supports('externallinks')) {
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; context.querySelector('.learnHowToContributeContainer').classList.remove('hide');
} else {
if (appHost.supports('displaylanguage')) { context.querySelector('.learnHowToContributeContainer').classList.add('hide');
userSettingsInstance.language(context.querySelector('#selectLanguage').value);
}
userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value);
userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked);
userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked);
userSettingsInstance.theme(context.querySelector('#selectTheme').value);
userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value);
userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value);
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value);
userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked);
userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked);
userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value);
if (user.Id === apiClient.getCurrentUserId()) {
skinManager.setTheme(userSettingsInstance.theme());
}
layoutManager.setLayout(context.querySelector('.selectLayout').value);
return apiClient.updateUserConfiguration(user.Id, user.Configuration);
} }
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator);
if (appHost.supports('screensaver')) {
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
} else {
context.querySelector('.selectScreensaverContainer').classList.add('hide');
}
if (datetime.supportsLocalization()) {
context.querySelector('.fldDateTimeLocale').classList.remove('hide');
} else {
context.querySelector('.fldDateTimeLocale').classList.add('hide');
}
fillThemes(context.querySelector('#selectTheme'), userSettings.theme());
fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme());
loadScreensavers(context, userSettings);
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss();
context.querySelector('#txtLocalCustomCss').value = userSettings.customCss();
context.querySelector('#selectLanguage').value = userSettings.language() || '';
context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || '';
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp();
context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp();
context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume();
context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || '';
showOrHideMissingEpisodesField(context);
loading.hide();
}
function saveUser(context, user, userSettingsInstance, apiClient) {
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
if (appHost.supports('displaylanguage')) {
userSettingsInstance.language(context.querySelector('#selectLanguage').value);
}
userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value);
userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked);
userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked);
userSettingsInstance.theme(context.querySelector('#selectTheme').value);
userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value);
userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value);
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value);
userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked);
userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked);
userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value);
if (user.Id === apiClient.getCurrentUserId()) {
skinManager.setTheme(userSettingsInstance.theme());
}
layoutManager.setLayout(context.querySelector('.selectLayout').value);
return apiClient.updateUserConfiguration(user.Id, user.Configuration);
}
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show();
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
toast(globalize.translate('SettingsSaved'));
}
Events.trigger(instance, 'saved');
}, () => {
loading.hide();
});
});
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId;
const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => {
const enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
// Disable default form submission
if (e) {
e.preventDefault();
}
return false;
}
function embed(options, self) {
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
self.loadData(options.autoFocus);
}
class DisplaySettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData(autoFocus) {
const self = this;
const context = self.options.element;
loading.show(); loading.show();
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
toast(globalize.translate('SettingsSaved'));
}
Events.trigger(instance, 'saved');
}, () => {
loading.hide();
});
});
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId; const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings; const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => { return apiClient.getUser(userId).then(user => {
const enableSaveConfirmation = self.options.enableSaveConfirmation; return userSettings.setUserInfo(userId, apiClient).then(() => {
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); self.dataLoaded = true;
}); loadForm(context, user, userSettings);
if (autoFocus) {
// Disable default form submission focusManager.autoFocus(context);
if (e) { }
e.preventDefault();
}
return false;
}
function embed(options, self) {
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
self.loadData(options.autoFocus);
}
class DisplaySettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData(autoFocus) {
const self = this;
const context = self.options.element;
loading.show();
const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings;
return apiClient.getUser(userId).then(user => {
return userSettings.setUserInfo(userId, apiClient).then(() => {
self.dataLoaded = true;
loadForm(context, user, userSettings);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
}); });
} });
submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
} }
/* eslint-enable indent */ submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
}
export default DisplaySettings; export default DisplaySettings;
+203 -207
View File
@@ -9,236 +9,232 @@ import { getParameterByName } from '../utils/url.ts';
import '../styles/scrollstyles.scss'; import '../styles/scrollstyles.scss';
import '../elements/emby-itemscontainer/emby-itemscontainer'; import '../elements/emby-itemscontainer/emby-itemscontainer';
/* eslint-disable indent */ function enableScrollX() {
return !layoutManager.desktop;
}
function enableScrollX() { function getThumbShape() {
return !layoutManager.desktop; return enableScrollX() ? 'overflowBackdrop' : 'backdrop';
}
function getPosterShape() {
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
function getSquareShape() {
return enableScrollX() ? 'overflowSquare' : 'square';
}
function getSections() {
return [{
name: 'Movies',
types: 'Movie',
id: 'favoriteMovies',
shape: getPosterShape(),
showTitle: false,
overlayPlayButton: true
}, {
name: 'Shows',
types: 'Series',
id: 'favoriteShows',
shape: getPosterShape(),
showTitle: false,
overlayPlayButton: true
}, {
name: 'Episodes',
types: 'Episode',
id: 'favoriteEpisode',
shape: getThumbShape(),
preferThumb: false,
showTitle: true,
showParentTitle: true,
overlayPlayButton: true,
overlayText: false,
centerText: true
}, {
name: 'Videos',
types: 'Video,MusicVideo',
id: 'favoriteVideos',
shape: getThumbShape(),
preferThumb: true,
showTitle: true,
overlayPlayButton: true,
overlayText: false,
centerText: true
}, {
name: 'Artists',
types: 'MusicArtist',
id: 'favoriteArtists',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: false,
centerText: true,
overlayPlayButton: true,
coverImage: true
}, {
name: 'Albums',
types: 'MusicAlbum',
id: 'favoriteAlbums',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: true,
centerText: true,
overlayPlayButton: true,
coverImage: true
}, {
name: 'Songs',
types: 'Audio',
id: 'favoriteSongs',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: true,
centerText: true,
overlayMoreButton: true,
action: 'instantmix',
coverImage: true
}];
}
function loadSection(elem, userId, topParentId, section, isSingleSection) {
const screenWidth = dom.getWindowSize().innerWidth;
const options = {
SortBy: 'SortName',
SortOrder: 'Ascending',
Filters: 'IsFavorite',
Recursive: true,
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
CollapseBoxSetItems: false,
ExcludeLocationTypes: 'Virtual',
EnableTotalRecordCount: false
};
if (topParentId) {
options.ParentId = topParentId;
} }
function getThumbShape() { if (!isSingleSection) {
return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; options.Limit = 6;
}
function getPosterShape() { if (enableScrollX()) {
return enableScrollX() ? 'overflowPortrait' : 'portrait'; options.Limit = 20;
} } else if (screenWidth >= 1920) {
options.Limit = 10;
function getSquareShape() { } else if (screenWidth >= 1440) {
return enableScrollX() ? 'overflowSquare' : 'square'; options.Limit = 8;
}
function getSections() {
return [{
name: 'Movies',
types: 'Movie',
id: 'favoriteMovies',
shape: getPosterShape(),
showTitle: false,
overlayPlayButton: true
}, {
name: 'Shows',
types: 'Series',
id: 'favoriteShows',
shape: getPosterShape(),
showTitle: false,
overlayPlayButton: true
}, {
name: 'Episodes',
types: 'Episode',
id: 'favoriteEpisode',
shape: getThumbShape(),
preferThumb: false,
showTitle: true,
showParentTitle: true,
overlayPlayButton: true,
overlayText: false,
centerText: true
}, {
name: 'Videos',
types: 'Video,MusicVideo',
id: 'favoriteVideos',
shape: getThumbShape(),
preferThumb: true,
showTitle: true,
overlayPlayButton: true,
overlayText: false,
centerText: true
}, {
name: 'Artists',
types: 'MusicArtist',
id: 'favoriteArtists',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: false,
centerText: true,
overlayPlayButton: true,
coverImage: true
}, {
name: 'Albums',
types: 'MusicAlbum',
id: 'favoriteAlbums',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: true,
centerText: true,
overlayPlayButton: true,
coverImage: true
}, {
name: 'Songs',
types: 'Audio',
id: 'favoriteSongs',
shape: getSquareShape(),
preferThumb: false,
showTitle: true,
overlayText: false,
showParentTitle: true,
centerText: true,
overlayMoreButton: true,
action: 'instantmix',
coverImage: true
}];
}
function loadSection(elem, userId, topParentId, section, isSingleSection) {
const screenWidth = dom.getWindowSize().innerWidth;
const options = {
SortBy: 'SortName',
SortOrder: 'Ascending',
Filters: 'IsFavorite',
Recursive: true,
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
CollapseBoxSetItems: false,
ExcludeLocationTypes: 'Virtual',
EnableTotalRecordCount: false
};
if (topParentId) {
options.ParentId = topParentId;
} }
}
if (!isSingleSection) { let promise;
options.Limit = 6;
if (section.types === 'MusicArtist') {
promise = ApiClient.getArtists(userId, options);
} else {
options.IncludeItemTypes = section.types;
promise = ApiClient.getItems(userId, options);
}
return promise.then(function (result) {
let html = '';
if (result.Items.length) {
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) {
html += '<a is="emby-linkbutton" href="' + ('#/list.html?serverId=' + ApiClient.serverId() + '&type=' + section.types + '&IsFavorite=true') + '" class="more button-flat button-flat-mini sectionTitleTextButton">';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += globalize.translate(section.name);
html += '</h2>';
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
html += '</a>';
} else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';
}
html += '</div>';
if (enableScrollX()) { if (enableScrollX()) {
options.Limit = 20; let scrollXClass = 'scrollX hiddenScrollX';
} else if (screenWidth >= 1920) { if (layoutManager.tv) {
options.Limit = 10; scrollXClass += ' smoothScrollX';
} else if (screenWidth >= 1440) {
options.Limit = 8;
}
}
let promise;
if (section.types === 'MusicArtist') {
promise = ApiClient.getArtists(userId, options);
} else {
options.IncludeItemTypes = section.types;
promise = ApiClient.getItems(userId, options);
}
return promise.then(function (result) {
let html = '';
if (result.Items.length) {
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) {
html += '<a is="emby-linkbutton" href="' + ('#/list.html?serverId=' + ApiClient.serverId() + '&type=' + section.types + '&IsFavorite=true') + '" class="more button-flat button-flat-mini sectionTitleTextButton">';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += globalize.translate(section.name);
html += '</h2>';
html += '<span class="material-icons chevron_right" aria-hidden="true"></span>';
html += '</a>';
} else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate(section.name) + '</h2>';
} }
html += '</div>'; html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' padded-left padded-right">';
if (enableScrollX()) { } else {
let scrollXClass = 'scrollX hiddenScrollX'; html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
if (layoutManager.tv) {
scrollXClass += ' smoothScrollX';
}
html += '<div is="emby-itemscontainer" class="itemsContainer ' + scrollXClass + ' padded-left padded-right">';
} else {
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
}
let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
cardLayout = false;
html += cardBuilder.getCardsHtml(result.Items, {
preferThumb: section.preferThumb,
shape: section.shape,
centerText: section.centerText && !cardLayout,
overlayText: section.overlayText !== false,
showTitle: section.showTitle,
showParentTitle: section.showParentTitle,
scalable: true,
coverImage: section.coverImage,
overlayPlayButton: section.overlayPlayButton,
overlayMoreButton: section.overlayMoreButton && !cardLayout,
action: section.action,
allowBottomPadding: !enableScrollX(),
cardLayout: cardLayout
});
html += '</div>';
} }
elem.innerHTML = html; let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
imageLoader.lazyChildren(elem); cardLayout = false;
html += cardBuilder.getCardsHtml(result.Items, {
preferThumb: section.preferThumb,
shape: section.shape,
centerText: section.centerText && !cardLayout,
overlayText: section.overlayText !== false,
showTitle: section.showTitle,
showParentTitle: section.showParentTitle,
scalable: true,
coverImage: section.coverImage,
overlayPlayButton: section.overlayPlayButton,
overlayMoreButton: section.overlayMoreButton && !cardLayout,
action: section.action,
allowBottomPadding: !enableScrollX(),
cardLayout: cardLayout
});
html += '</div>';
}
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
});
}
export function loadSections(page, userId, topParentId, types) {
loading.show();
let sections = getSections();
const sectionid = getParameterByName('sectionid');
if (sectionid) {
sections = sections.filter(function (s) {
return s.id === sectionid;
}); });
} }
export function loadSections(page, userId, topParentId, types) { if (types) {
loading.show(); sections = sections.filter(function (s) {
let sections = getSections(); return types.indexOf(s.id) !== -1;
const sectionid = getParameterByName('sectionid'); });
}
if (sectionid) { let elem = page.querySelector('.favoriteSections');
sections = sections.filter(function (s) {
return s.id === sectionid;
});
}
if (types) { if (!elem.innerHTML) {
sections = sections.filter(function (s) { let html = '';
return types.indexOf(s.id) !== -1;
});
}
let elem = page.querySelector('.favoriteSections');
if (!elem.innerHTML) {
let html = '';
for (let i = 0, length = sections.length; i < length; i++) {
html += '<div class="verticalSection section' + sections[i].id + '"></div>';
}
elem.innerHTML = html;
}
const promises = [];
for (let i = 0, length = sections.length; i < length; i++) { for (let i = 0, length = sections.length; i < length; i++) {
const section = sections[i]; html += '<div class="verticalSection section' + sections[i].id + '"></div>';
elem = page.querySelector('.section' + section.id);
promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1));
} }
Promise.all(promises).then(function () { elem.innerHTML = html;
loading.hide();
});
} }
const promises = [];
for (let i = 0, length = sections.length; i < length; i++) {
const section = sections[i];
elem = page.querySelector('.section' + section.id);
promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1));
}
Promise.all(promises).then(function () {
loading.hide();
});
}
export default { export default {
render: loadSections render: loadSections
}; };
/* eslint-enable indent */
+87 -89
View File
@@ -1,110 +1,108 @@
/* eslint-disable indent */ export function getFetchPromise(request) {
export function getFetchPromise(request) { const headers = request.headers || {};
const headers = request.headers || {};
if (request.dataType === 'json') { if (request.dataType === 'json') {
headers.accept = 'application/json'; headers.accept = 'application/json';
}
const fetchRequest = {
headers: headers,
method: request.type,
credentials: 'same-origin'
};
let contentType = request.contentType;
if (request.data) {
if (typeof request.data === 'string') {
fetchRequest.body = request.data;
} else {
fetchRequest.body = paramsToString(request.data);
contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
}
}
if (contentType) {
headers['Content-Type'] = contentType;
}
let url = request.url;
if (request.query) {
const paramString = paramsToString(request.query);
if (paramString) {
url += `?${paramString}`;
}
}
if (!request.timeout) {
return fetch(url, fetchRequest);
}
return fetchWithTimeout(url, fetchRequest, request.timeout);
} }
function fetchWithTimeout(url, options, timeoutMs) { const fetchRequest = {
console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); headers: headers,
method: request.type,
credentials: 'same-origin'
};
return new Promise(function (resolve, reject) { let contentType = request.contentType;
const timeout = setTimeout(reject, timeoutMs);
options = options || {}; if (request.data) {
options.credentials = 'same-origin'; if (typeof request.data === 'string') {
fetchRequest.body = request.data;
} else {
fetchRequest.body = paramsToString(request.data);
fetch(url, options).then(function (response) { contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
clearTimeout(timeout); }
}
console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); if (contentType) {
headers['Content-Type'] = contentType;
}
resolve(response); let url = request.url;
}, function (error) {
clearTimeout(timeout);
console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); if (request.query) {
const paramString = paramsToString(request.query);
if (paramString) {
url += `?${paramString}`;
}
}
reject(error); if (!request.timeout) {
}); return fetch(url, fetchRequest);
}
return fetchWithTimeout(url, fetchRequest, request.timeout);
}
function fetchWithTimeout(url, options, timeoutMs) {
console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);
return new Promise(function (resolve, reject) {
const timeout = setTimeout(reject, timeoutMs);
options = options || {};
options.credentials = 'same-origin';
fetch(url, options).then(function (response) {
clearTimeout(timeout);
console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`);
resolve(response);
}, function (error) {
clearTimeout(timeout);
console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`);
reject(error);
}); });
} });
}
/** /**
* @param params {Record<string, string | number | boolean>} * @param params {Record<string, string | number | boolean>}
* @returns {string} Query string * @returns {string} Query string
*/ */
function paramsToString(params) { function paramsToString(params) {
return Object.entries(params) return Object.entries(params)
.filter(([, v]) => v !== null && v !== undefined && v !== '') .filter(([, v]) => v !== null && v !== undefined && v !== '')
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&'); .join('&');
}
export function ajax(request) {
if (!request) {
throw new Error('Request cannot be null');
} }
export function ajax(request) { request.headers = request.headers || {};
if (!request) {
throw new Error('Request cannot be null');
}
request.headers = request.headers || {}; console.debug(`requesting url: ${request.url}`);
console.debug(`requesting url: ${request.url}`); return getFetchPromise(request).then(function (response) {
console.debug(`response status: ${response.status}, url: ${request.url}`);
return getFetchPromise(request).then(function (response) { if (response.status < 400) {
console.debug(`response status: ${response.status}, url: ${request.url}`); if (request.dataType === 'json' || request.headers.accept === 'application/json') {
if (response.status < 400) { return response.json();
if (request.dataType === 'json' || request.headers.accept === 'application/json') { } else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().startsWith('text/')) {
return response.json(); return response.text();
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().startsWith('text/')) {
return response.text();
} else {
return response;
}
} else { } else {
return Promise.reject(response); return response;
} }
}, function (err) { } else {
console.error(`request failed to url: ${request.url}`); return Promise.reject(response);
throw err; }
}); }, function (err) {
} console.error(`request failed to url: ${request.url}`);
/* eslint-enable indent */ throw err;
});
}
+382 -385
View File
@@ -8,417 +8,414 @@ import './style.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import template from './filterdialog.template.html'; import template from './filterdialog.template.html';
/* eslint-disable indent */ function renderOptions(context, selector, cssClass, items, isCheckedFn) {
function renderOptions(context, selector, cssClass, items, isCheckedFn) { const elem = context.querySelector(selector);
const elem = context.querySelector(selector); if (items.length) {
if (items.length) { elem.classList.remove('hide');
elem.classList.remove('hide'); } else {
} else { elem.classList.add('hide');
elem.classList.add('hide');
}
let html = '';
html += '<div class="checkboxList">';
html += items.map(function (filter) {
let itemHtml = '';
const checkedHtml = isCheckedFn(filter) ? 'checked' : '';
itemHtml += '<label>';
itemHtml += `<input is="emby-checkbox" type="checkbox" ${checkedHtml} data-filter="${filter}" class="${cssClass}"/>`;
itemHtml += `<span>${filter}</span>`;
itemHtml += '</label>';
return itemHtml;
}).join('');
html += '</div>';
elem.querySelector('.filterOptions').innerHTML = html;
} }
let html = '';
html += '<div class="checkboxList">';
html += items.map(function (filter) {
let itemHtml = '';
const checkedHtml = isCheckedFn(filter) ? 'checked' : '';
itemHtml += '<label>';
itemHtml += `<input is="emby-checkbox" type="checkbox" ${checkedHtml} data-filter="${filter}" class="${cssClass}"/>`;
itemHtml += `<span>${filter}</span>`;
itemHtml += '</label>';
return itemHtml;
}).join('');
html += '</div>';
elem.querySelector('.filterOptions').innerHTML = html;
}
function renderFilters(context, result, query) { function renderFilters(context, result, query) {
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
const delimeter = '|'; const delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter); return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter);
}); });
renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) { renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) {
const delimeter = '|'; const delimeter = '|';
return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter); return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter);
}); });
renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) { renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) {
const delimeter = '|'; const delimeter = '|';
return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter); return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter);
}); });
renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) { renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) {
const delimeter = ','; const delimeter = ',';
return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter); return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter);
}); });
} }
function loadDynamicFilters(context, apiClient, userId, itemQuery) { function loadDynamicFilters(context, apiClient, userId, itemQuery) {
return apiClient.getJSON(apiClient.getUrl('Items/Filters', { return apiClient.getJSON(apiClient.getUrl('Items/Filters', {
UserId: userId, UserId: userId,
ParentId: itemQuery.ParentId, ParentId: itemQuery.ParentId,
IncludeItemTypes: itemQuery.IncludeItemTypes IncludeItemTypes: itemQuery.IncludeItemTypes
})).then(function (result) { })).then(function (result) {
renderFilters(context, result, itemQuery); renderFilters(context, result, itemQuery);
}); });
} }
/** /**
* @param context {HTMLDivElement} Dialog * @param context {HTMLDivElement} Dialog
* @param options {any} Options * @param options {any} Options
*/ */
function updateFilterControls(context, options) { function updateFilterControls(context, options) {
const query = options.query; const query = options.query;
if (options.mode === 'livetvchannels') { if (options.mode === 'livetvchannels') {
context.querySelector('.chkFavorite').checked = query.IsFavorite === true; context.querySelector('.chkFavorite').checked = query.IsFavorite === true;
} else {
for (const elem of context.querySelectorAll('.chkStandardFilter')) {
const filters = `,${query.Filters || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
const filters = `,${query.VideoTypes || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === false;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true;
for (const elem of context.querySelectorAll('.chkStatus')) {
const filters = `,${query.SeriesStatus || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
/**
* @param instance {FilterDialog} An instance of FilterDialog
*/
function triggerChange(instance) {
Events.trigger(instance, 'filterchange');
}
function setVisibility(context, options) {
if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') {
hideByClass(context, 'videoStandard');
}
if (enableDynamicFilters(options.mode)) {
context.querySelector('.genreFilters').classList.remove('hide');
context.querySelector('.officialRatingFilters').classList.remove('hide');
context.querySelector('.tagFilters').classList.remove('hide');
context.querySelector('.yearFilters').classList.remove('hide');
}
if (options.mode === 'movies' || options.mode === 'episodes') {
context.querySelector('.videoTypeFilters').classList.remove('hide');
}
if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') {
context.querySelector('.features').classList.remove('hide');
}
if (options.mode === 'series') {
context.querySelector('.seriesStatus').classList.remove('hide');
}
if (options.mode === 'episodes') {
showByClass(context, 'episodeFilter');
}
}
function showByClass(context, className) {
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.remove('hide');
}
}
function hideByClass(context, className) {
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.add('hide');
}
}
function enableDynamicFilters(mode) {
return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes';
}
class FilterDialog {
constructor(options) {
/**
* @private
*/
this.options = options;
}
/**
* @private
*/
onFavoriteChange(elem) {
const query = this.options.query;
query.StartIndex = 0;
query.IsFavorite = !!elem.checked || null;
triggerChange(this);
}
/**
* @private
*/
onStandardFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.Filters || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.Filters = filters;
triggerChange(this);
}
/**
* @private
*/
onVideoTypeFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.VideoTypes || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.VideoTypes = filters;
triggerChange(this);
}
/**
* @private
*/
onStatusChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.SeriesStatus || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.SeriesStatus = filters;
query.StartIndex = 0;
triggerChange(this);
}
/**
* @param context {HTMLDivElement} The dialog
*/
bindEvents(context) {
const query = this.options.query;
if (this.options.mode === 'livetvchannels') {
for (const elem of context.querySelectorAll('.chkFavorite')) {
elem.addEventListener('change', () => this.onFavoriteChange(elem));
}
} else { } else {
for (const elem of context.querySelectorAll('.chkStandardFilter')) { for (const elem of context.querySelectorAll('.chkStandardFilter')) {
const filters = `,${query.Filters || ''}`; elem.addEventListener('change', () => this.onStandardFilterChange(elem));
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
} }
} }
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
const filters = `,${query.VideoTypes || ''}`; elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem));
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
} }
context.querySelector('.chk3DFilter').checked = query.Is3D === true; const chk3DFilter = context.querySelector('.chk3DFilter');
context.querySelector('.chkHDFilter').checked = query.IsHD === true; chk3DFilter.addEventListener('change', () => {
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === false;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true;
for (const elem of context.querySelectorAll('.chkStatus')) {
const filters = `,${query.SeriesStatus || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
/**
* @param instance {FilterDialog} An instance of FilterDialog
*/
function triggerChange(instance) {
Events.trigger(instance, 'filterchange');
}
function setVisibility(context, options) {
if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') {
hideByClass(context, 'videoStandard');
}
if (enableDynamicFilters(options.mode)) {
context.querySelector('.genreFilters').classList.remove('hide');
context.querySelector('.officialRatingFilters').classList.remove('hide');
context.querySelector('.tagFilters').classList.remove('hide');
context.querySelector('.yearFilters').classList.remove('hide');
}
if (options.mode === 'movies' || options.mode === 'episodes') {
context.querySelector('.videoTypeFilters').classList.remove('hide');
}
if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') {
context.querySelector('.features').classList.remove('hide');
}
if (options.mode === 'series') {
context.querySelector('.seriesStatus').classList.remove('hide');
}
if (options.mode === 'episodes') {
showByClass(context, 'episodeFilter');
}
}
function showByClass(context, className) {
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.remove('hide');
}
}
function hideByClass(context, className) {
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.add('hide');
}
}
function enableDynamicFilters(mode) {
return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes';
}
class FilterDialog {
constructor(options) {
/**
* @private
*/
this.options = options;
}
/**
* @private
*/
onFavoriteChange(elem) {
const query = this.options.query;
query.StartIndex = 0; query.StartIndex = 0;
query.IsFavorite = !!elem.checked || null; query.Is3D = chk3DFilter.checked ? true : null;
triggerChange(this); triggerChange(this);
} });
const chk4KFilter = context.querySelector('.chk4KFilter');
/** chk4KFilter.addEventListener('change', () => {
* @private
*/
onStandardFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.Filters || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0; query.StartIndex = 0;
query.Filters = filters; query.Is4K = chk4KFilter.checked ? true : null;
triggerChange(this); triggerChange(this);
} });
const chkHDFilter = context.querySelector('.chkHDFilter');
/** const chkSDFilter = context.querySelector('.chkSDFilter');
* @private chkHDFilter.addEventListener('change', () => {
*/
onVideoTypeFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.VideoTypes || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0; query.StartIndex = 0;
query.VideoTypes = filters; if (chkHDFilter.checked) {
triggerChange(this); chkSDFilter.checked = false;
} query.IsHD = true;
/**
* @private
*/
onStatusChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.SeriesStatus || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.SeriesStatus = filters;
query.StartIndex = 0;
triggerChange(this);
}
/**
* @param context {HTMLDivElement} The dialog
*/
bindEvents(context) {
const query = this.options.query;
if (this.options.mode === 'livetvchannels') {
for (const elem of context.querySelectorAll('.chkFavorite')) {
elem.addEventListener('change', () => this.onFavoriteChange(elem));
}
} else { } else {
for (const elem of context.querySelectorAll('.chkStandardFilter')) { query.IsHD = null;
elem.addEventListener('change', () => this.onStandardFilterChange(elem));
}
} }
triggerChange(this);
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { });
elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
if (chkSDFilter.checked) {
chkHDFilter.checked = false;
query.IsHD = false;
} else {
query.IsHD = null;
} }
const chk3DFilter = context.querySelector('.chk3DFilter'); triggerChange(this);
chk3DFilter.addEventListener('change', () => { });
query.StartIndex = 0; for (const elem of context.querySelectorAll('.chkStatus')) {
query.Is3D = chk3DFilter.checked ? true : null; elem.addEventListener('change', () => this.onStatusChange(elem));
triggerChange(this);
});
const chk4KFilter = context.querySelector('.chk4KFilter');
chk4KFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.Is4K = chk4KFilter.checked ? true : null;
triggerChange(this);
});
const chkHDFilter = context.querySelector('.chkHDFilter');
const chkSDFilter = context.querySelector('.chkSDFilter');
chkHDFilter.addEventListener('change', () => {
query.StartIndex = 0;
if (chkHDFilter.checked) {
chkSDFilter.checked = false;
query.IsHD = true;
} else {
query.IsHD = null;
}
triggerChange(this);
});
chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
if (chkSDFilter.checked) {
chkHDFilter.checked = false;
query.IsHD = false;
} else {
query.IsHD = null;
}
triggerChange(this);
});
for (const elem of context.querySelectorAll('.chkStatus')) {
elem.addEventListener('change', () => this.onStatusChange(elem));
}
const chkTrailer = context.querySelector('#chkTrailer');
chkTrailer.addEventListener('change', () => {
query.StartIndex = 0;
query.HasTrailer = chkTrailer.checked ? true : null;
triggerChange(this);
});
const chkThemeSong = context.querySelector('#chkThemeSong');
chkThemeSong.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeSong = chkThemeSong.checked ? true : null;
triggerChange(this);
});
const chkSpecialFeature = context.querySelector('#chkSpecialFeature');
chkSpecialFeature.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSpecialFeature = chkSpecialFeature.checked ? true : null;
triggerChange(this);
});
const chkThemeVideo = context.querySelector('#chkThemeVideo');
chkThemeVideo.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeVideo = chkThemeVideo.checked ? true : null;
triggerChange(this);
});
const chkMissingEpisode = context.querySelector('#chkMissingEpisode');
chkMissingEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.IsMissing = !!chkMissingEpisode.checked;
triggerChange(this);
});
const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode');
chkSpecialEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null;
triggerChange(this);
});
const chkFutureEpisode = context.querySelector('#chkFutureEpisode');
chkFutureEpisode.addEventListener('change', () => {
query.StartIndex = 0;
if (chkFutureEpisode.checked) {
query.IsUnaired = true;
query.IsVirtualUnaired = null;
} else {
query.IsUnaired = null;
query.IsVirtualUnaired = false;
}
triggerChange(this);
});
const chkSubtitle = context.querySelector('#chkSubtitle');
chkSubtitle.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSubtitles = chkSubtitle.checked ? true : null;
triggerChange(this);
});
context.addEventListener('change', (e) => {
const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
if (chkGenreFilter) {
const filterName = chkGenreFilter.getAttribute('data-filter');
let filters = query.Genres || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkGenreFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Genres = filters;
triggerChange(this);
return;
}
const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
if (chkTagFilter) {
const filterName = chkTagFilter.getAttribute('data-filter');
let filters = query.Tags || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkTagFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Tags = filters;
triggerChange(this);
return;
}
const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
if (chkYearFilter) {
const filterName = chkYearFilter.getAttribute('data-filter');
let filters = query.Years || '';
const delimiter = ',';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkYearFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Years = filters;
triggerChange(this);
return;
}
const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
if (chkOfficialRatingFilter) {
const filterName = chkOfficialRatingFilter.getAttribute('data-filter');
let filters = query.OfficialRatings || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkOfficialRatingFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.OfficialRatings = filters;
triggerChange(this);
}
});
} }
const chkTrailer = context.querySelector('#chkTrailer');
show() { chkTrailer.addEventListener('change', () => {
return new Promise((resolve) => { query.StartIndex = 0;
const dlg = dialogHelper.createDialog({ query.HasTrailer = chkTrailer.checked ? true : null;
removeOnClose: true, triggerChange(this);
modal: false });
}); const chkThemeSong = context.querySelector('#chkThemeSong');
dlg.classList.add('ui-body-a'); chkThemeSong.addEventListener('change', () => {
dlg.classList.add('background-theme-a'); query.StartIndex = 0;
dlg.classList.add('formDialog'); query.HasThemeSong = chkThemeSong.checked ? true : null;
dlg.classList.add('filterDialog'); triggerChange(this);
dlg.innerHTML = globalize.translateHtml(template); });
setVisibility(dlg, this.options); const chkSpecialFeature = context.querySelector('#chkSpecialFeature');
dialogHelper.open(dlg); chkSpecialFeature.addEventListener('change', () => {
dlg.addEventListener('close', resolve); query.StartIndex = 0;
updateFilterControls(dlg, this.options); query.HasSpecialFeature = chkSpecialFeature.checked ? true : null;
this.bindEvents(dlg); triggerChange(this);
if (enableDynamicFilters(this.options.mode)) { });
dlg.classList.add('dynamicFilterDialog'); const chkThemeVideo = context.querySelector('#chkThemeVideo');
const apiClient = ServerConnections.getApiClient(this.options.serverId); chkThemeVideo.addEventListener('change', () => {
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); query.StartIndex = 0;
query.HasThemeVideo = chkThemeVideo.checked ? true : null;
triggerChange(this);
});
const chkMissingEpisode = context.querySelector('#chkMissingEpisode');
chkMissingEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.IsMissing = !!chkMissingEpisode.checked;
triggerChange(this);
});
const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode');
chkSpecialEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null;
triggerChange(this);
});
const chkFutureEpisode = context.querySelector('#chkFutureEpisode');
chkFutureEpisode.addEventListener('change', () => {
query.StartIndex = 0;
if (chkFutureEpisode.checked) {
query.IsUnaired = true;
query.IsVirtualUnaired = null;
} else {
query.IsUnaired = null;
query.IsVirtualUnaired = false;
}
triggerChange(this);
});
const chkSubtitle = context.querySelector('#chkSubtitle');
chkSubtitle.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSubtitles = chkSubtitle.checked ? true : null;
triggerChange(this);
});
context.addEventListener('change', (e) => {
const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
if (chkGenreFilter) {
const filterName = chkGenreFilter.getAttribute('data-filter');
let filters = query.Genres || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkGenreFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
} }
}); query.StartIndex = 0;
} query.Genres = filters;
triggerChange(this);
return;
}
const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
if (chkTagFilter) {
const filterName = chkTagFilter.getAttribute('data-filter');
let filters = query.Tags || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkTagFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Tags = filters;
triggerChange(this);
return;
}
const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
if (chkYearFilter) {
const filterName = chkYearFilter.getAttribute('data-filter');
let filters = query.Years || '';
const delimiter = ',';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkYearFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Years = filters;
triggerChange(this);
return;
}
const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
if (chkOfficialRatingFilter) {
const filterName = chkOfficialRatingFilter.getAttribute('data-filter');
let filters = query.OfficialRatings || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkOfficialRatingFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.OfficialRatings = filters;
triggerChange(this);
}
});
} }
/* eslint-enable indent */ show() {
return new Promise((resolve) => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
modal: false
});
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
dlg.classList.add('formDialog');
dlg.classList.add('filterDialog');
dlg.innerHTML = globalize.translateHtml(template);
setVisibility(dlg, this.options);
dialogHelper.open(dlg);
dlg.addEventListener('close', resolve);
updateFilterControls(dlg, this.options);
this.bindEvents(dlg);
if (enableDynamicFilters(this.options.mode)) {
dlg.classList.add('dynamicFilterDialog');
const apiClient = ServerConnections.getApiClient(this.options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query);
}
});
}
}
export default FilterDialog; export default FilterDialog;
-2
View File
@@ -297,10 +297,8 @@ class FilterMenu {
} }
if (submitted) { if (submitted) {
//if (!options.onChange) {
saveValues(dlg, options.settings, options.settingsKey, options.setfilters); saveValues(dlg, options.settings, options.settingsKey, options.setfilters);
return resolve(); return resolve();
//}
} }
return resolve(); return resolve();
}); });
+413 -417
View File
@@ -1,470 +1,466 @@
/* eslint-disable indent */
import dom from '../scripts/dom'; import dom from '../scripts/dom';
import scrollManager from './scrollManager'; import scrollManager from './scrollManager';
const scopes = []; const scopes = [];
function pushScope(elem) { function pushScope(elem) {
scopes.push(elem); scopes.push(elem);
} }
function popScope() { function popScope() {
if (scopes.length) { if (scopes.length) {
scopes.length -= 1; scopes.length -= 1;
}
}
function autoFocus(view, defaultToFirst, findAutoFocusElement) {
let element;
if (findAutoFocusElement !== false) {
element = view.querySelector('*[autofocus]');
if (element) {
focus(element);
return element;
} }
} }
function autoFocus(view, defaultToFirst, findAutoFocusElement) { if (defaultToFirst !== false) {
let element; element = getFocusableElements(view, 1, 'noautofocus')[0];
if (findAutoFocusElement !== false) {
element = view.querySelector('*[autofocus]');
if (element) {
focus(element);
return element;
}
}
if (defaultToFirst !== false) { if (element) {
element = getFocusableElements(view, 1, 'noautofocus')[0]; focus(element);
return element;
if (element) {
focus(element);
return element;
}
}
return null;
}
function focus(element) {
try {
element.focus({
preventScroll: scrollManager.isEnabled()
});
} catch (err) {
console.error('Error in focusManager.autoFocus: ' + err);
} }
} }
const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A']; return null;
const focusableContainerTagNames = ['BODY', 'DIALOG']; }
const focusableQuery = focusableTagNames.map(function (t) {
if (t === 'INPUT') {
t += ':not([type="range"]):not([type="file"])';
}
return t + ':not([tabindex="-1"]):not(:disabled)';
}).join(',') + ',.focusable';
function isFocusable(elem) { function focus(element) {
return focusableTagNames.indexOf(elem.tagName) !== -1 try {
element.focus({
preventScroll: scrollManager.isEnabled()
});
} catch (err) {
console.error('Error in focusManager.autoFocus: ' + err);
}
}
const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'];
const focusableContainerTagNames = ['BODY', 'DIALOG'];
const focusableQuery = focusableTagNames.map(function (t) {
if (t === 'INPUT') {
t += ':not([type="range"]):not([type="file"])';
}
return t + ':not([tabindex="-1"]):not(:disabled)';
}).join(',') + ',.focusable';
function isFocusable(elem) {
return focusableTagNames.indexOf(elem.tagName) !== -1
|| (elem.classList?.contains('focusable')); || (elem.classList?.contains('focusable'));
}
function normalizeFocusable(elem, originalElement) {
if (elem) {
const tagName = elem.tagName;
if (!tagName || tagName === 'HTML' || tagName === 'BODY') {
elem = originalElement;
}
} }
function normalizeFocusable(elem, originalElement) { return elem;
if (elem) { }
const tagName = elem.tagName;
if (!tagName || tagName === 'HTML' || tagName === 'BODY') { function focusableParent(elem) {
elem = originalElement; const originalElement = elem;
}
while (!isFocusable(elem)) {
const parent = elem.parentNode;
if (!parent) {
return normalizeFocusable(elem, originalElement);
} }
return elem; elem = parent;
} }
function focusableParent(elem) { return normalizeFocusable(elem, originalElement);
const originalElement = elem; }
while (!isFocusable(elem)) { // Determines if a focusable element can be focused at a given point in time
const parent = elem.parentNode; function isCurrentlyFocusableInternal(elem) {
// http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
if (!parent) { return elem.offsetParent !== null;
return normalizeFocusable(elem, originalElement); }
}
elem = parent;
}
return normalizeFocusable(elem, originalElement);
}
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusableInternal(elem) {
// http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
return elem.offsetParent !== null;
}
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusable(elem) {
if (elem.disabled) {
return false;
}
if (elem.getAttribute('tabindex') === '-1') {
return false;
}
if (elem.tagName === 'INPUT') {
const type = elem.type;
if (type === 'range') {
return false;
}
if (type === 'file') {
return false;
}
}
return isCurrentlyFocusableInternal(elem);
}
function getDefaultScope() {
return scopes[0] || document.body;
}
function getFocusableElements(parent, limit, excludeClass) {
const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery);
const focusableElements = [];
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[i];
if (excludeClass && elem.classList.contains(excludeClass)) {
continue;
}
if (isCurrentlyFocusableInternal(elem)) {
focusableElements.push(elem);
if (limit && focusableElements.length >= limit) {
break;
}
}
}
return focusableElements;
}
function isFocusContainer(elem, direction) {
if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) {
return true;
}
const classList = elem.classList;
if (classList.contains('focuscontainer')) {
return true;
}
if (direction === 0) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-left')) {
return true;
}
} else if (direction === 1) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-right')) {
return true;
}
} else if (direction === 2) {
if (classList.contains('focuscontainer-y')) {
return true;
}
} else if (direction === 3) {
if (classList.contains('focuscontainer-y')) {
return true;
}
if (classList.contains('focuscontainer-down')) {
return true;
}
}
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusable(elem) {
if (elem.disabled) {
return false; return false;
} }
function getFocusContainer(elem, direction) { if (elem.getAttribute('tabindex') === '-1') {
while (!isFocusContainer(elem, direction)) { return false;
elem = elem.parentNode; }
if (!elem) { if (elem.tagName === 'INPUT') {
return getDefaultScope(); const type = elem.type;
if (type === 'range') {
return false;
}
if (type === 'file') {
return false;
}
}
return isCurrentlyFocusableInternal(elem);
}
function getDefaultScope() {
return scopes[0] || document.body;
}
function getFocusableElements(parent, limit, excludeClass) {
const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery);
const focusableElements = [];
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[i];
if (excludeClass && elem.classList.contains(excludeClass)) {
continue;
}
if (isCurrentlyFocusableInternal(elem)) {
focusableElements.push(elem);
if (limit && focusableElements.length >= limit) {
break;
} }
} }
return elem;
} }
function getOffset(elem) { return focusableElements;
let box; }
// Support: BlackBerry 5, iOS 3 (original iPhone) function isFocusContainer(elem, direction) {
// If we don't have gBCR, just use 0,0 rather than error if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) {
if (elem.getBoundingClientRect) { return true;
box = elem.getBoundingClientRect();
} else {
box = {
top: 0,
left: 0,
width: 0,
height: 0
};
}
if (box.right === null) {
// Create a new object because some browsers will throw an error when trying to set data onto the Rect object
const newBox = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
};
box = newBox;
box.right = box.left + box.width;
box.bottom = box.top + box.height;
}
return box;
} }
function nav(activeElement, direction, container, focusableElements) { const classList = elem.classList;
activeElement = activeElement || document.activeElement;
if (classList.contains('focuscontainer')) {
return true;
}
if (direction === 0) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-left')) {
return true;
}
} else if (direction === 1) {
if (classList.contains('focuscontainer-x')) {
return true;
}
if (classList.contains('focuscontainer-right')) {
return true;
}
} else if (direction === 2) {
if (classList.contains('focuscontainer-y')) {
return true;
}
} else if (direction === 3) {
if (classList.contains('focuscontainer-y')) {
return true;
}
if (classList.contains('focuscontainer-down')) {
return true;
}
}
return false;
}
function getFocusContainer(elem, direction) {
while (!isFocusContainer(elem, direction)) {
elem = elem.parentNode;
if (!elem) {
return getDefaultScope();
}
}
return elem;
}
function getOffset(elem) {
let box;
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (elem.getBoundingClientRect) {
box = elem.getBoundingClientRect();
} else {
box = {
top: 0,
left: 0,
width: 0,
height: 0
};
}
if (box.right === null) {
// Create a new object because some browsers will throw an error when trying to set data onto the Rect object
const newBox = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
};
box = newBox;
box.right = box.left + box.width;
box.bottom = box.top + box.height;
}
return box;
}
function nav(activeElement, direction, container, focusableElements) {
activeElement = activeElement || document.activeElement;
if (activeElement) {
activeElement = focusableParent(activeElement);
}
container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope());
if (!activeElement) {
autoFocus(container, true, false);
return;
}
const focusableContainer = dom.parentWithClass(activeElement, 'focusable');
const rect = getOffset(activeElement);
// Get elements and work out x/y points
const point1x = parseFloat(rect.left) || 0;
const point1y = parseFloat(rect.top) || 0;
const point2x = parseFloat(point1x + rect.width - 1) || point1x;
const point2y = parseFloat(point1y + rect.height - 1) || point1y;
const sourceMidX = rect.left + (rect.width / 2);
const sourceMidY = rect.top + (rect.height / 2);
const focusable = focusableElements || container.querySelectorAll(focusableQuery);
const maxDistance = Infinity;
let minDistance = maxDistance;
let nearestElement;
for (let i = 0, length = focusable.length; i < length; i++) {
const curr = focusable[i];
if (curr === activeElement) {
continue;
}
// Don't refocus into the same container
if (curr === focusableContainer) {
continue;
}
const elementRect = getOffset(curr);
// not currently visible
if (!elementRect.width && !elementRect.height) {
continue;
}
switch (direction) {
case 0:
// left
if (elementRect.left >= rect.left) {
continue;
}
if (elementRect.right === rect.right) {
continue;
}
break;
case 1:
// right
if (elementRect.right <= rect.right) {
continue;
}
if (elementRect.left === rect.left) {
continue;
}
break;
case 2:
// up
if (elementRect.top >= rect.top) {
continue;
}
if (elementRect.bottom >= rect.bottom) {
continue;
}
break;
case 3:
// down
if (elementRect.bottom <= rect.bottom) {
continue;
}
if (elementRect.top <= rect.top) {
continue;
}
break;
default:
break;
}
const x = elementRect.left;
const y = elementRect.top;
const x2 = x + elementRect.width - 1;
const y2 = y + elementRect.height - 1;
const intersectX = intersects(point1x, point2x, x, x2);
const intersectY = intersects(point1y, point2y, y, y2);
const midX = elementRect.left + (elementRect.width / 2);
const midY = elementRect.top + (elementRect.height / 2);
let distX;
let distY;
switch (direction) {
case 0:
// left
distX = Math.abs(point1x - Math.min(point1x, x2));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 1:
// right
distX = Math.abs(point2x - Math.max(point2x, x));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 2:
// up
distY = Math.abs(point1y - Math.min(point1y, y2));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
case 3:
// down
distY = Math.abs(point2y - Math.max(point2y, y));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
default:
break;
}
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < minDistance) {
nearestElement = curr;
minDistance = dist;
}
}
if (nearestElement) {
// See if there's a focusable container, and if so, send the focus command to that
if (activeElement) { if (activeElement) {
activeElement = focusableParent(activeElement); const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable');
} if (nearestElementFocusableParent
container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope());
if (!activeElement) {
autoFocus(container, true, false);
return;
}
const focusableContainer = dom.parentWithClass(activeElement, 'focusable');
const rect = getOffset(activeElement);
// Get elements and work out x/y points
const point1x = parseFloat(rect.left) || 0;
const point1y = parseFloat(rect.top) || 0;
const point2x = parseFloat(point1x + rect.width - 1) || point1x;
const point2y = parseFloat(point1y + rect.height - 1) || point1y;
const sourceMidX = rect.left + (rect.width / 2);
const sourceMidY = rect.top + (rect.height / 2);
const focusable = focusableElements || container.querySelectorAll(focusableQuery);
const maxDistance = Infinity;
let minDistance = maxDistance;
let nearestElement;
for (let i = 0, length = focusable.length; i < length; i++) {
const curr = focusable[i];
if (curr === activeElement) {
continue;
}
// Don't refocus into the same container
if (curr === focusableContainer) {
continue;
}
const elementRect = getOffset(curr);
// not currently visible
if (!elementRect.width && !elementRect.height) {
continue;
}
switch (direction) {
case 0:
// left
if (elementRect.left >= rect.left) {
continue;
}
if (elementRect.right === rect.right) {
continue;
}
break;
case 1:
// right
if (elementRect.right <= rect.right) {
continue;
}
if (elementRect.left === rect.left) {
continue;
}
break;
case 2:
// up
if (elementRect.top >= rect.top) {
continue;
}
if (elementRect.bottom >= rect.bottom) {
continue;
}
break;
case 3:
// down
if (elementRect.bottom <= rect.bottom) {
continue;
}
if (elementRect.top <= rect.top) {
continue;
}
break;
default:
break;
}
const x = elementRect.left;
const y = elementRect.top;
const x2 = x + elementRect.width - 1;
const y2 = y + elementRect.height - 1;
const intersectX = intersects(point1x, point2x, x, x2);
const intersectY = intersects(point1y, point2y, y, y2);
const midX = elementRect.left + (elementRect.width / 2);
const midY = elementRect.top + (elementRect.height / 2);
let distX;
let distY;
switch (direction) {
case 0:
// left
distX = Math.abs(point1x - Math.min(point1x, x2));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 1:
// right
distX = Math.abs(point2x - Math.max(point2x, x));
distY = intersectY ? 0 : Math.abs(sourceMidY - midY);
break;
case 2:
// up
distY = Math.abs(point1y - Math.min(point1y, y2));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
case 3:
// down
distY = Math.abs(point2y - Math.max(point2y, y));
distX = intersectX ? 0 : Math.abs(sourceMidX - midX);
break;
default:
break;
}
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < minDistance) {
nearestElement = curr;
minDistance = dist;
}
}
if (nearestElement) {
// See if there's a focusable container, and if so, send the focus command to that
if (activeElement) {
const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable');
if (nearestElementFocusableParent
&& nearestElementFocusableParent !== nearestElement && nearestElementFocusableParent !== nearestElement
&& focusableContainer !== nearestElementFocusableParent && focusableContainer !== nearestElementFocusableParent
) { ) {
nearestElement = nearestElementFocusableParent; nearestElement = nearestElementFocusableParent;
}
}
focus(nearestElement);
}
}
function intersectsInternal(a1, a2, b1, b2) {
return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2);
}
function intersects(a1, a2, b1, b2) {
return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2);
}
function sendText(text) {
const elem = document.activeElement;
elem.value = text;
}
function focusFirst(container, focusableSelector) {
const elems = container.querySelectorAll(focusableSelector);
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break;
} }
} }
focus(nearestElement);
} }
}
function focusLast(container, focusableSelector) { function intersectsInternal(a1, a2, b1, b2) {
const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(); return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2);
}
for (let i = 0, length = elems.length; i < length; i++) { function intersects(a1, a2, b1, b2) {
const elem = elems[i]; return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2);
}
if (isCurrentlyFocusableInternal(elem)) { function sendText(text) {
focus(elem); const elem = document.activeElement;
break;
} elem.value = text;
}
function focusFirst(container, focusableSelector) {
const elems = container.querySelectorAll(focusableSelector);
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break;
}
}
}
function focusLast(container, focusableSelector) {
const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse();
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
focus(elem);
break;
}
}
}
function moveFocus(sourceElement, container, focusableSelector, offset) {
const elems = container.querySelectorAll(focusableSelector);
const list = [];
let i;
let length;
let elem;
for (i = 0, length = elems.length; i < length; i++) {
elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
list.push(elem);
} }
} }
function moveFocus(sourceElement, container, focusableSelector, offset) { let currentIndex = -1;
const elems = container.querySelectorAll(focusableSelector);
const list = [];
let i;
let length;
let elem;
for (i = 0, length = elems.length; i < length; i++) { for (i = 0, length = list.length; i < length; i++) {
elem = elems[i]; elem = list[i];
if (isCurrentlyFocusableInternal(elem)) { if (sourceElement === elem || elem.contains(sourceElement)) {
list.push(elem); currentIndex = i;
} break;
}
let currentIndex = -1;
for (i = 0, length = list.length; i < length; i++) {
elem = list[i];
if (sourceElement === elem || elem.contains(sourceElement)) {
currentIndex = i;
break;
}
}
if (currentIndex === -1) {
return;
}
let newIndex = currentIndex + offset;
newIndex = Math.max(0, newIndex);
newIndex = Math.min(newIndex, list.length - 1);
const newElem = list[newIndex];
if (newElem) {
focus(newElem);
} }
} }
/* eslint-enable indent */ if (currentIndex === -1) {
return;
}
let newIndex = currentIndex + offset;
newIndex = Math.max(0, newIndex);
newIndex = Math.min(newIndex, list.length - 1);
const newElem = list[newIndex];
if (newElem) {
focus(newElem);
}
}
export default { export default {
autoFocus: autoFocus, autoFocus: autoFocus,
+32 -36
View File
@@ -1,47 +1,43 @@
/* eslint-disable indent */
import dom from '../scripts/dom'; import dom from '../scripts/dom';
import { appRouter } from './appRouter'; import { appRouter } from './appRouter';
import Dashboard from '../utils/dashboard'; import Dashboard from '../utils/dashboard';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
function onGroupedCardClick(e, card) { function onGroupedCardClick(e, card) {
const itemId = card.getAttribute('data-id'); const itemId = card.getAttribute('data-id');
const serverId = card.getAttribute('data-serverid'); const serverId = card.getAttribute('data-serverid');
const apiClient = ServerConnections.getApiClient(serverId); const apiClient = ServerConnections.getApiClient(serverId);
const userId = apiClient.getCurrentUserId(); const userId = apiClient.getCurrentUserId();
const playedIndicator = card.querySelector('.playedIndicator'); const playedIndicator = card.querySelector('.playedIndicator');
const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null;
const options = { const options = {
Limit: parseInt(playedIndicatorHtml || '10', 10), Limit: parseInt(playedIndicatorHtml || '10', 10),
Fields: 'PrimaryImageAspectRatio,DateCreated', Fields: 'PrimaryImageAspectRatio,DateCreated',
ParentId: itemId, ParentId: itemId,
GroupItems: false GroupItems: false
}; };
const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']); const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']);
if (!actionableParent || actionableParent.classList.contains('cardContent')) { if (!actionableParent || actionableParent.classList.contains('cardContent')) {
apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) {
if (items.length === 1) { if (items.length === 1) {
appRouter.showItem(items[0]); appRouter.showItem(items[0]);
return; return;
} }
const url = 'details?id=' + itemId + '&serverId=' + serverId; const url = 'details?id=' + itemId + '&serverId=' + serverId;
Dashboard.navigate(url); Dashboard.navigate(url);
}); });
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
return false; return false;
}
} }
}
export default function onItemsContainerClick(e) { export default function onItemsContainerClick(e) {
const groupedCard = dom.parentWithClass(e.target, 'groupedCard'); const groupedCard = dom.parentWithClass(e.target, 'groupedCard');
if (groupedCard) { if (groupedCard) {
onGroupedCardClick(e, groupedCard); onGroupedCardClick(e, groupedCard);
}
} }
}
/* eslint-enable indent */
+1 -1
View File
@@ -29,7 +29,7 @@ import ServerConnections from '../ServerConnections';
import template from './tvguide.template.html'; import template from './tvguide.template.html';
function showViewSettings(instance) { function showViewSettings(instance) {
import('./guide-settings').then(({default: guideSettingsDialog}) => { import('./guide-settings').then(({ default: guideSettingsDialog }) => {
guideSettingsDialog.show(instance.categoryOptions).then(function () { guideSettingsDialog.show(instance.categoryOptions).then(function () {
instance.refresh(); instance.refresh();
}); });
@@ -14,498 +14,494 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './homeScreenSettings.template.html'; import template from './homeScreenSettings.template.html';
/* eslint-disable indent */ const numConfigurableSections = 7;
const numConfigurableSections = 7; function renderViews(page, user, result) {
let folderHtml = '';
function renderViews(page, user, result) { folderHtml += '<div class="checkboxList">';
let folderHtml = ''; folderHtml += result.map(i => {
let currentHtml = '';
folderHtml += '<div class="checkboxList">'; const id = `chkGroupFolder${i.Id}`;
folderHtml += result.map(i => {
let currentHtml = '';
const id = `chkGroupFolder${i.Id}`; const isChecked = user.Configuration.GroupedFolders.includes(i.Id);
const isChecked = user.Configuration.GroupedFolders.includes(i.Id); const checkedHtml = isChecked ? ' checked="checked"' : '';
const checkedHtml = isChecked ? ' checked="checked"' : ''; currentHtml += '<label>';
currentHtml += `<input type="checkbox" is="emby-checkbox" class="chkGroupFolder" data-folderid="${i.Id}" id="${id}"${checkedHtml}/>`;
currentHtml += `<span>${escapeHtml(i.Name)}</span>`;
currentHtml += '</label>';
currentHtml += '<label>'; return currentHtml;
currentHtml += `<input type="checkbox" is="emby-checkbox" class="chkGroupFolder" data-folderid="${i.Id}" id="${id}"${checkedHtml}/>`; }).join('');
currentHtml += `<span>${escapeHtml(i.Name)}</span>`;
currentHtml += '</label>';
return currentHtml; folderHtml += '</div>';
}).join('');
folderHtml += '</div>'; page.querySelector('.folderGroupList').innerHTML = folderHtml;
}
page.querySelector('.folderGroupList').innerHTML = folderHtml; function getLandingScreenOptions(type) {
const list = [];
if (type === 'movies') {
list.push({
name: globalize.translate('Movies'),
value: 'movies',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('Trailers'),
value: 'trailers'
});
list.push({
name: globalize.translate('Favorites'),
value: 'favorites'
});
list.push({
name: globalize.translate('Collections'),
value: 'collections'
});
list.push({
name: globalize.translate('Genres'),
value: 'genres'
});
} else if (type === 'tvshows') {
list.push({
name: globalize.translate('Shows'),
value: 'shows',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('TabUpcoming'),
value: 'upcoming'
});
list.push({
name: globalize.translate('Genres'),
value: 'genres'
});
list.push({
name: globalize.translate('TabNetworks'),
value: 'networks'
});
list.push({
name: globalize.translate('Episodes'),
value: 'episodes'
});
} else if (type === 'music') {
list.push({
name: globalize.translate('Albums'),
value: 'albums',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('HeaderAlbumArtists'),
value: 'albumartists'
});
list.push({
name: globalize.translate('Artists'),
value: 'artists'
});
list.push({
name: globalize.translate('Playlists'),
value: 'playlists'
});
list.push({
name: globalize.translate('Songs'),
value: 'songs'
});
list.push({
name: globalize.translate('Genres'),
value: 'genres'
});
} else if (type === 'livetv') {
list.push({
name: globalize.translate('Programs'),
value: 'programs',
isDefault: true
});
list.push({
name: globalize.translate('Guide'),
value: 'guide'
});
list.push({
name: globalize.translate('Channels'),
value: 'channels'
});
list.push({
name: globalize.translate('Recordings'),
value: 'recordings'
});
list.push({
name: globalize.translate('Schedule'),
value: 'schedule'
});
list.push({
name: globalize.translate('Series'),
value: 'series'
});
} }
function getLandingScreenOptions(type) { return list;
const list = []; }
if (type === 'movies') { function getLandingScreenOptionsHtml(type, userValue) {
list.push({ return getLandingScreenOptions(type).map(o => {
name: globalize.translate('Movies'), const selected = userValue === o.value || (o.isDefault && !userValue);
value: 'movies', const selectedHtml = selected ? ' selected' : '';
isDefault: true const optionValue = o.isDefault ? '' : o.value;
});
list.push({ return `<option value="${optionValue}"${selectedHtml}>${escapeHtml(o.name)}</option>`;
name: globalize.translate('Suggestions'), }).join('');
value: 'suggestions' }
});
list.push({ function renderViewOrder(context, user, result) {
name: globalize.translate('Trailers'), let html = '';
value: 'trailers'
}); html += result.Items.map((view) => {
list.push({ let currentHtml = '';
name: globalize.translate('Favorites'),
value: 'favorites' currentHtml += `<div class="listItem viewItem" data-viewid="${view.Id}">`;
});
list.push({ currentHtml += '<span class="material-icons listItemIcon folder_open" aria-hidden="true"></span>';
name: globalize.translate('Collections'),
value: 'collections' currentHtml += '<div class="listItemBody">';
});
list.push({ currentHtml += '<div>';
name: globalize.translate('Genres'), currentHtml += escapeHtml(view.Name);
value: 'genres' currentHtml += '</div>';
});
} else if (type === 'tvshows') { currentHtml += '</div>';
list.push({
name: globalize.translate('Shows'), currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="${globalize.translate('Up')}"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>`;
value: 'shows', currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="${globalize.translate('Down')}"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>`;
isDefault: true
}); currentHtml += '</div>';
list.push({
name: globalize.translate('Suggestions'), return currentHtml;
value: 'suggestions' }).join('');
});
list.push({ context.querySelector('.viewOrderList').innerHTML = html;
name: globalize.translate('TabUpcoming'), }
value: 'upcoming'
}); function updateHomeSectionValues(context, userSettings) {
list.push({ for (let i = 1; i <= 7; i++) {
name: globalize.translate('Genres'), const select = context.querySelector(`#selectHomeSection${i}`);
value: 'genres' const defaultValue = homeSections.getDefaultSection(i - 1);
});
list.push({ const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]');
name: globalize.translate('TabNetworks'),
value: 'networks' const userValue = userSettings.get(`homesection${i - 1}`);
});
list.push({ option.value = '';
name: globalize.translate('Episodes'),
value: 'episodes' if (userValue === defaultValue || !userValue) {
}); select.value = '';
} else if (type === 'music') { } else {
list.push({ select.value = userValue;
name: globalize.translate('Albums'),
value: 'albums',
isDefault: true
});
list.push({
name: globalize.translate('Suggestions'),
value: 'suggestions'
});
list.push({
name: globalize.translate('HeaderAlbumArtists'),
value: 'albumartists'
});
list.push({
name: globalize.translate('Artists'),
value: 'artists'
});
list.push({
name: globalize.translate('Playlists'),
value: 'playlists'
});
list.push({
name: globalize.translate('Songs'),
value: 'songs'
});
list.push({
name: globalize.translate('Genres'),
value: 'genres'
});
} else if (type === 'livetv') {
list.push({
name: globalize.translate('Programs'),
value: 'programs',
isDefault: true
});
list.push({
name: globalize.translate('Guide'),
value: 'guide'
});
list.push({
name: globalize.translate('Channels'),
value: 'channels'
});
list.push({
name: globalize.translate('Recordings'),
value: 'recordings'
});
list.push({
name: globalize.translate('Schedule'),
value: 'schedule'
});
list.push({
name: globalize.translate('Series'),
value: 'series'
});
} }
return list;
} }
function getLandingScreenOptionsHtml(type, userValue) { context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || '';
return getLandingScreenOptions(type).map(o => { }
const selected = userValue === o.value || (o.isDefault && !userValue);
const selectedHtml = selected ? ' selected' : '';
const optionValue = o.isDefault ? '' : o.value;
return `<option value="${optionValue}"${selectedHtml}>${escapeHtml(o.name)}</option>`; function getPerLibrarySettingsHtml(item, user, userSettings) {
}).join(''); let html = '';
let isChecked;
if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') {
isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id);
html += '<div>';
html += '<label>';
html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInMyMedia')}</span>`;
html += '</label>';
html += '</div>';
} }
function renderViewOrder(context, user, result) { const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels'];
let html = ''; if (!excludeFromLatest.includes(item.CollectionType || '')) {
isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id);
html += result.Items.map((view) => { html += '<label class="fldIncludeInLatest">';
let currentHtml = ''; html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInOtherHomeScreenSections')}</span>`;
currentHtml += `<div class="listItem viewItem" data-viewid="${view.Id}">`; html += '</label>';
currentHtml += '<span class="material-icons listItemIcon folder_open" aria-hidden="true"></span>';
currentHtml += '<div class="listItemBody">';
currentHtml += '<div>';
currentHtml += escapeHtml(view.Name);
currentHtml += '</div>';
currentHtml += '</div>';
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="${globalize.translate('Up')}"><span class="material-icons keyboard_arrow_up" aria-hidden="true"></span></button>`;
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="${globalize.translate('Down')}"><span class="material-icons keyboard_arrow_down" aria-hidden="true"></span></button>`;
currentHtml += '</div>';
return currentHtml;
}).join('');
context.querySelector('.viewOrderList').innerHTML = html;
} }
function updateHomeSectionValues(context, userSettings) { if (html) {
for (let i = 1; i <= 7; i++) { html = `<div class="checkboxListContainer">${html}</div>`;
const select = context.querySelector(`#selectHomeSection${i}`); }
const defaultValue = homeSections.getDefaultSection(i - 1);
const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]'); if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') {
const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id;
html += '<div class="selectContainer">';
html += `<select is="emby-select" class="selectLanding" data-folderid="${idForLanding}" label="${globalize.translate('LabelDefaultScreen')}">`;
const userValue = userSettings.get(`homesection${i - 1}`); const userValue = userSettings.get(`landing-${idForLanding}`);
option.value = ''; html += getLandingScreenOptionsHtml(item.CollectionType, userValue);
if (userValue === defaultValue || !userValue) { html += '</select>';
select.value = ''; html += '</div>';
}
if (html) {
let prefix = '';
prefix += '<div class="verticalSection">';
prefix += '<h2 class="sectionTitle">';
prefix += escapeHtml(item.Name);
prefix += '</h2>';
html = prefix + html;
html += '</div>';
}
return html;
}
function renderPerLibrarySettings(context, user, userViews, userSettings) {
const elem = context.querySelector('.perLibrarySettings');
let html = '';
for (let i = 0, length = userViews.length; i < length; i++) {
html += getPerLibrarySettingsHtml(userViews[i], user, userSettings);
}
elem.innerHTML = html;
}
function loadForm(context, user, userSettings, apiClient) {
context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false;
updateHomeSectionValues(context, userSettings);
const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id);
const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`));
Promise.all([promise1, promise2]).then(responses => {
renderViewOrder(context, user, responses[0]);
renderPerLibrarySettings(context, user, responses[0].Items, userSettings);
renderViews(context, user, responses[1]);
loading.hide();
});
}
function onSectionOrderListClick(e) {
const target = dom.parentWithClass(e.target, 'btnViewItemMove');
if (target) {
const viewItem = dom.parentWithClass(target, 'viewItem');
if (viewItem) {
if (target.classList.contains('btnViewItemDown')) {
const next = viewItem.nextSibling;
if (next) {
viewItem.parentNode.removeChild(viewItem);
next.parentNode.insertBefore(viewItem, next.nextSibling);
focusManager.focus(e.target);
}
} else { } else {
select.value = userValue; const prev = viewItem.previousSibling;
if (prev) {
viewItem.parentNode.removeChild(viewItem);
prev.parentNode.insertBefore(viewItem, prev);
focusManager.focus(e.target);
}
} }
} }
}
}
context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || ''; function getCheckboxItems(selector, context, isChecked) {
const inputs = context.querySelectorAll(selector);
const list = [];
for (let i = 0, length = inputs.length; i < length; i++) {
if (inputs[i].checked === isChecked) {
list.push(inputs[i]);
}
} }
function getPerLibrarySettingsHtml(item, user, userSettings) { return list;
let html = ''; }
let isChecked; function saveUser(context, user, userSettingsInstance, apiClient) {
user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked;
if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') { user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => {
isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id); return i.getAttribute('data-folderid');
html += '<div>'; });
html += '<label>';
html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInMyMedia')}</span>`;
html += '</label>';
html += '</div>';
}
const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels']; user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => {
if (!excludeFromLatest.includes(item.CollectionType || '')) { return i.getAttribute('data-folderid');
isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id); });
html += '<label class="fldIncludeInLatest">';
html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInOtherHomeScreenSections')}</span>`;
html += '</label>';
}
if (html) { user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => {
html = `<div class="checkboxListContainer">${html}</div>`; return i.getAttribute('data-folderid');
} });
if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') { const viewItems = context.querySelectorAll('.viewItem');
const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id; const orderedViews = [];
html += '<div class="selectContainer">'; let i;
html += `<select is="emby-select" class="selectLanding" data-folderid="${idForLanding}" label="${globalize.translate('LabelDefaultScreen')}">`; let length;
for (i = 0, length = viewItems.length; i < length; i++) {
const userValue = userSettings.get(`landing-${idForLanding}`); orderedViews.push(viewItems[i].getAttribute('data-viewid'));
html += getLandingScreenOptionsHtml(item.CollectionType, userValue);
html += '</select>';
html += '</div>';
}
if (html) {
let prefix = '';
prefix += '<div class="verticalSection">';
prefix += '<h2 class="sectionTitle">';
prefix += escapeHtml(item.Name);
prefix += '</h2>';
html = prefix + html;
html += '</div>';
}
return html;
} }
function renderPerLibrarySettings(context, user, userViews, userSettings) { user.Configuration.OrderedViews = orderedViews;
const elem = context.querySelector('.perLibrarySettings');
let html = '';
for (let i = 0, length = userViews.length; i < length; i++) { userSettingsInstance.set('tvhome', context.querySelector('.selectTVHomeScreen').value);
html += getPerLibrarySettingsHtml(userViews[i], user, userSettings);
}
elem.innerHTML = html; userSettingsInstance.set('homesection0', context.querySelector('#selectHomeSection1').value);
userSettingsInstance.set('homesection1', context.querySelector('#selectHomeSection2').value);
userSettingsInstance.set('homesection2', context.querySelector('#selectHomeSection3').value);
userSettingsInstance.set('homesection3', context.querySelector('#selectHomeSection4').value);
userSettingsInstance.set('homesection4', context.querySelector('#selectHomeSection5').value);
userSettingsInstance.set('homesection5', context.querySelector('#selectHomeSection6').value);
userSettingsInstance.set('homesection6', context.querySelector('#selectHomeSection7').value);
const selectLandings = context.querySelectorAll('.selectLanding');
for (i = 0, length = selectLandings.length; i < length; i++) {
const selectLanding = selectLandings[i];
userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value);
} }
function loadForm(context, user, userSettings, apiClient) { return apiClient.updateUserConfiguration(user.Id, user.Configuration);
context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false; }
updateHomeSectionValues(context, userSettings); function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show();
const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); apiClient.getUser(userId).then(user => {
const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`)); saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
Promise.all([promise1, promise2]).then(responses => { if (enableSaveConfirmation) {
renderViewOrder(context, user, responses[0]); toast(globalize.translate('SettingsSaved'));
}
renderPerLibrarySettings(context, user, responses[0].Items, userSettings);
renderViews(context, user, responses[1]);
Events.trigger(instance, 'saved');
}, () => {
loading.hide(); loading.hide();
}); });
});
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId;
const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => {
const enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
// Disable default form submission
if (e) {
e.preventDefault();
}
return false;
}
function onChange(e) {
const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia');
if (!chkIncludeInMyMedia) {
return;
} }
function onSectionOrderListClick(e) { const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection');
const target = dom.parentWithClass(e.target, 'btnViewItemMove'); const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest');
if (fldIncludeInLatest) {
if (target) { if (chkIncludeInMyMedia.checked) {
const viewItem = dom.parentWithClass(target, 'viewItem'); fldIncludeInLatest.classList.remove('hide');
} else {
if (viewItem) { fldIncludeInLatest.classList.add('hide');
if (target.classList.contains('btnViewItemDown')) {
const next = viewItem.nextSibling;
if (next) {
viewItem.parentNode.removeChild(viewItem);
next.parentNode.insertBefore(viewItem, next.nextSibling);
focusManager.focus(e.target);
}
} else {
const prev = viewItem.previousSibling;
if (prev) {
viewItem.parentNode.removeChild(viewItem);
prev.parentNode.insertBefore(viewItem, prev);
focusManager.focus(e.target);
}
}
}
} }
} }
}
function getCheckboxItems(selector, context, isChecked) { function embed(options, self) {
const inputs = context.querySelectorAll(selector); let workingTemplate = template;
const list = []; for (let i = 1; i <= numConfigurableSections; i++) {
workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i));
for (let i = 0, length = inputs.length; i < length; i++) {
if (inputs[i].checked === isChecked) {
list.push(inputs[i]);
}
}
return list;
} }
function saveUser(context, user, userSettingsInstance, apiClient) { options.element.innerHTML = globalize.translateHtml(workingTemplate, 'core');
user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked;
user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => { options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick);
return i.getAttribute('data-folderid'); options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
}); options.element.addEventListener('change', onChange);
user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => { if (options.enableSaveButton) {
return i.getAttribute('data-folderid'); options.element.querySelector('.btnSave').classList.remove('hide');
});
user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => {
return i.getAttribute('data-folderid');
});
const viewItems = context.querySelectorAll('.viewItem');
const orderedViews = [];
let i;
let length;
for (i = 0, length = viewItems.length; i < length; i++) {
orderedViews.push(viewItems[i].getAttribute('data-viewid'));
}
user.Configuration.OrderedViews = orderedViews;
userSettingsInstance.set('tvhome', context.querySelector('.selectTVHomeScreen').value);
userSettingsInstance.set('homesection0', context.querySelector('#selectHomeSection1').value);
userSettingsInstance.set('homesection1', context.querySelector('#selectHomeSection2').value);
userSettingsInstance.set('homesection2', context.querySelector('#selectHomeSection3').value);
userSettingsInstance.set('homesection3', context.querySelector('#selectHomeSection4').value);
userSettingsInstance.set('homesection4', context.querySelector('#selectHomeSection5').value);
userSettingsInstance.set('homesection5', context.querySelector('#selectHomeSection6').value);
userSettingsInstance.set('homesection6', context.querySelector('#selectHomeSection7').value);
const selectLandings = context.querySelectorAll('.selectLanding');
for (i = 0, length = selectLandings.length; i < length; i++) {
const selectLanding = selectLandings[i];
userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value);
}
return apiClient.updateUserConfiguration(user.Id, user.Configuration);
} }
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { if (layoutManager.tv) {
options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide');
} else {
options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide');
}
self.loadData(options.autoFocus);
}
class HomeScreenSettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData(autoFocus) {
const self = this;
const context = self.options.element;
loading.show(); loading.show();
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
toast(globalize.translate('SettingsSaved'));
}
Events.trigger(instance, 'saved');
}, () => {
loading.hide();
});
});
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId; const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings; const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => { apiClient.getUser(userId).then(user => {
const enableSaveConfirmation = self.options.enableSaveConfirmation; userSettings.setUserInfo(userId, apiClient).then(() => {
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); self.dataLoaded = true;
});
// Disable default form submission loadForm(context, user, userSettings, apiClient);
if (e) {
e.preventDefault();
}
return false;
}
function onChange(e) { if (autoFocus) {
const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia'); focusManager.autoFocus(context);
if (!chkIncludeInMyMedia) { }
return;
}
const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection');
const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest');
if (fldIncludeInLatest) {
if (chkIncludeInMyMedia.checked) {
fldIncludeInLatest.classList.remove('hide');
} else {
fldIncludeInLatest.classList.add('hide');
}
}
}
function embed(options, self) {
let workingTemplate = template;
for (let i = 1; i <= numConfigurableSections; i++) {
workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i));
}
options.element.innerHTML = globalize.translateHtml(workingTemplate, 'core');
options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick);
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
options.element.addEventListener('change', onChange);
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
if (layoutManager.tv) {
options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide');
} else {
options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide');
}
self.loadData(options.autoFocus);
}
class HomeScreenSettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData(autoFocus) {
const self = this;
const context = self.options.element;
loading.show();
const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings;
apiClient.getUser(userId).then(user => {
userSettings.setUserInfo(userId, apiClient).then(() => {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
}); });
} });
submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
} }
/* eslint-enable indent */ submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
}
export default HomeScreenSettings; export default HomeScreenSettings;
File diff suppressed because it is too large Load Diff
+312 -325
View File
@@ -1,395 +1,382 @@
/* eslint-disable indent */
import appSettings from '../scripts/settings/appSettings' ; import appSettings from '../scripts/settings/appSettings' ;
import browser from '../scripts/browser'; import browser from '../scripts/browser';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
export function getSavedVolume() { export function getSavedVolume() {
return appSettings.get('volume') || 1; return appSettings.get('volume') || 1;
}
export function saveVolume(value) {
if (value) {
appSettings.set('volume', value);
}
}
export function getCrossOriginValue(mediaSource) {
if (mediaSource.IsRemote) {
return null;
} }
export function saveVolume(value) { return 'anonymous';
if (value) { }
appSettings.set('volume', value);
} function canPlayNativeHls() {
const media = document.createElement('video');
return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '')
|| media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, ''));
}
export function enableHlsJsPlayer(runTimeTicks, mediaType) {
if (window.MediaSource == null) {
return false;
} }
export function getCrossOriginValue(mediaSource) { // hls.js is only in beta. needs more testing.
if (mediaSource.IsRemote) { if (browser.iOS) {
return null; return false;
}
// The native players on these devices support seeking live streams, no need to use hls.js here
if (browser.tizen || browser.web0s) {
return false;
}
if (canPlayNativeHls()) {
// Having trouble with chrome's native support and transcoded music
if (browser.android && mediaType === 'Audio') {
return true;
} }
return 'anonymous'; // simple playback should use the native support
} if (runTimeTicks) {
function canPlayNativeHls() {
const media = document.createElement('video');
return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') ||
media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, ''));
}
export function enableHlsJsPlayer(runTimeTicks, mediaType) {
if (window.MediaSource == null) {
return false; return false;
} }
// hls.js is only in beta. needs more testing.
if (browser.iOS) {
return false;
}
// The native players on these devices support seeking live streams, no need to use hls.js here
if (browser.tizen || browser.web0s) {
return false;
}
if (canPlayNativeHls()) {
// Having trouble with chrome's native support and transcoded music
if (browser.android && mediaType === 'Audio') {
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;
} }
let recoverDecodingErrorDate; return true;
let recoverSwapAudioCodecDate; }
export function handleHlsJsMediaError(instance, reject) {
const hlsPlayer = instance._hlsPlayer;
if (!hlsPlayer) { let recoverDecodingErrorDate;
return; let recoverSwapAudioCodecDate;
} export function handleHlsJsMediaError(instance, reject) {
const hlsPlayer = instance._hlsPlayer;
let now = Date.now(); if (!hlsPlayer) {
return;
}
if (window.performance && window.performance.now) { let now = Date.now();
now = performance.now(); // eslint-disable-line compat/compat
}
if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { if (window.performance && window.performance.now) {
recoverDecodingErrorDate = now; now = performance.now(); // eslint-disable-line compat/compat
console.debug('try to recover media Error ...'); }
if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) {
recoverDecodingErrorDate = now;
console.debug('try to recover media Error ...');
hlsPlayer.recoverMediaError();
} else {
if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) {
recoverSwapAudioCodecDate = now;
console.debug('try to swap Audio Codec and recover media Error ...');
hlsPlayer.swapAudioCodec();
hlsPlayer.recoverMediaError(); hlsPlayer.recoverMediaError();
} else { } else {
if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { console.error('cannot recover, last media error recovery failed ...');
recoverSwapAudioCodecDate = now;
console.debug('try to swap Audio Codec and recover media Error ...'); if (reject) {
hlsPlayer.swapAudioCodec(); reject();
hlsPlayer.recoverMediaError();
} else { } else {
console.error('cannot recover, last media error recovery failed ...'); onErrorInternal(instance, 'mediadecodeerror');
if (reject) {
reject();
} else {
onErrorInternal(instance, 'mediadecodeerror');
}
} }
} }
} }
}
export function onErrorInternal(instance, type) { export function onErrorInternal(instance, type) {
// Needed for video // Needed for video
if (instance.destroyCustomTrack) { if (instance.destroyCustomTrack) {
instance.destroyCustomTrack(instance._mediaElement); instance.destroyCustomTrack(instance._mediaElement);
}
Events.trigger(instance, 'error', [
{
type: type
}
]);
} }
export function isValidDuration(duration) { Events.trigger(instance, 'error', [
return duration {
type: type
}
]);
}
export function isValidDuration(duration) {
return duration
&& !isNaN(duration) && !isNaN(duration)
&& duration !== Number.POSITIVE_INFINITY && duration !== Number.POSITIVE_INFINITY
&& duration !== Number.NEGATIVE_INFINITY; && duration !== Number.NEGATIVE_INFINITY;
} }
function setCurrentTimeIfNeeded(element, seconds) { function setCurrentTimeIfNeeded(element, seconds) {
// If it's worth skipping (1 sec or less of a difference) // If it's worth skipping (1 sec or less of a difference)
if (Math.abs((element.currentTime || 0) - seconds) >= 1) { if (Math.abs((element.currentTime || 0) - seconds) >= 1) {
element.currentTime = seconds; element.currentTime = seconds;
}
}
export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) {
const seconds = (ticks || 0) / 10000000;
if (seconds) {
// Appending #t=xxx to the query string doesn't seem to work with HLS
// For plain video files, not all browsers support it either
if (element.duration >= seconds) {
// media is ready, seek immediately
setCurrentTimeIfNeeded(element, seconds);
if (onMediaReady) onMediaReady();
} else {
// update video player position when media is ready to be sought
const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata'];
const onMediaChange = function(e) {
if (element.currentTime === 0 && element.duration >= seconds) {
// seek only when video position is exactly zero,
// as this is true only if video hasn't started yet or
// user rewound to the very beginning
// (but rewinding cannot happen as the first event with media of non-empty duration)
console.debug(`seeking to ${seconds} on ${e.type} event`);
setCurrentTimeIfNeeded(element, seconds);
events.forEach(name => {
element.removeEventListener(name, onMediaChange);
});
if (onMediaReady) onMediaReady();
}
};
events.forEach(name => {
element.addEventListener(name, onMediaChange);
});
} }
} }
}
export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { export function applySrc(elem, src, options) {
const seconds = (ticks || 0) / 10000000; if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) {
return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) {
const playlist = new Windows.Media.Playback.MediaPlaybackList();
if (seconds) { const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file);
// Appending #t=xxx to the query string doesn't seem to work with HLS const startTime = (options.playerStartPositionTicks || 0) / 10000;
// For plain video files, not all browsers support it either playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime));
elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true });
if (element.duration >= seconds) { return Promise.resolve();
// media is ready, seek immediately });
setCurrentTimeIfNeeded(element, seconds); } else {
if (onMediaReady) onMediaReady(); elem.src = src;
} else {
// update video player position when media is ready to be sought
const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata'];
const onMediaChange = function(e) {
if (element.currentTime === 0 && element.duration >= seconds) {
// seek only when video position is exactly zero,
// as this is true only if video hasn't started yet or
// user rewound to the very beginning
// (but rewinding cannot happen as the first event with media of non-empty duration)
console.debug(`seeking to ${seconds} on ${e.type} event`);
setCurrentTimeIfNeeded(element, seconds);
events.forEach(name => {
element.removeEventListener(name, onMediaChange);
});
if (onMediaReady) onMediaReady();
}
};
events.forEach(name => {
element.addEventListener(name, onMediaChange);
});
}
}
} }
export function applySrc(elem, src, options) { return Promise.resolve();
if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) { }
return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) {
const playlist = new Windows.Media.Playback.MediaPlaybackList();
const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file); export function resetSrc(elem) {
const startTime = (options.playerStartPositionTicks || 0) / 10000; elem.src = '';
playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime)); elem.innerHTML = '';
elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true }); elem.removeAttribute('src');
}
function onSuccessfulPlay(elem, onErrorFn) {
elem.addEventListener('error', onErrorFn);
}
export function playWithPromise(elem, onErrorFn) {
try {
return elem.play()
.catch((e) => {
const errorName = (e.name || '').toLowerCase();
// safari uses 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();
}
return Promise.reject();
})
.then(() => {
onSuccessfulPlay(elem, onErrorFn);
return Promise.resolve(); return Promise.resolve();
}); });
} else { } catch (err) {
elem.src = src; console.error('error calling video.play: ' + err);
} return Promise.reject();
return Promise.resolve();
} }
}
export function resetSrc(elem) { export function destroyCastPlayer(instance) {
elem.src = ''; const player = instance._castPlayer;
elem.innerHTML = ''; if (player) {
elem.removeAttribute('src');
}
function onSuccessfulPlay(elem, onErrorFn) {
elem.addEventListener('error', onErrorFn);
}
export function playWithPromise(elem, onErrorFn) {
try { try {
return elem.play() player.unload();
.catch((e) => {
const errorName = (e.name || '').toLowerCase();
// safari uses 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();
}
return Promise.reject();
})
.then(() => {
onSuccessfulPlay(elem, onErrorFn);
return Promise.resolve();
});
} catch (err) { } catch (err) {
console.error('error calling video.play: ' + err); console.error(err);
return Promise.reject();
} }
}
export function destroyCastPlayer(instance) { instance._castPlayer = null;
const player = instance._castPlayer; }
if (player) { }
try {
player.unload(); export function destroyHlsPlayer(instance) {
} catch (err) { const player = instance._hlsPlayer;
console.error(err); if (player) {
try {
player.destroy();
} catch (err) {
console.error(err);
}
instance._hlsPlayer = null;
}
}
export function destroyFlvPlayer(instance) {
const player = instance._flvPlayer;
if (player) {
try {
player.unload();
player.detachMediaElement();
player.destroy();
} catch (err) {
console.error(err);
}
instance._flvPlayer = null;
}
}
export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
hls.on(Hls.Events.MANIFEST_PARSED, function () {
playWithPromise(elem, onErrorFn).then(resolve, function () {
if (reject) {
reject();
reject = null;
} }
instance._castPlayer = null;
}
}
export function destroyHlsPlayer(instance) {
const player = instance._hlsPlayer;
if (player) {
try {
player.destroy();
} catch (err) {
console.error(err);
}
instance._hlsPlayer = null;
}
}
export function destroyFlvPlayer(instance) {
const player = instance._flvPlayer;
if (player) {
try {
player.unload();
player.detachMediaElement();
player.destroy();
} catch (err) {
console.error(err);
}
instance._flvPlayer = null;
}
}
export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
hls.on(Hls.Events.MANIFEST_PARSED, function () {
playWithPromise(elem, onErrorFn).then(resolve, function () {
if (reject) {
reject();
reject = null;
}
});
}); });
});
hls.on(Hls.Events.ERROR, function (event, data) { hls.on(Hls.Events.ERROR, function (event, data) {
console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false));
// try to recover network error // try to recover network error
if (data.type === Hls.ErrorTypes.NETWORK_ERROR if (data.type === Hls.ErrorTypes.NETWORK_ERROR
&& data.response?.code && data.response.code >= 400 && data.response?.code && data.response.code >= 400
) { ) {
console.debug('hls.js response error code: ' + data.response.code); console.debug('hls.js response error code: ' + data.response.code);
// Trigger failure differently depending on whether this is prior to start of playback, or after // Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy(); hls.destroy();
if (reject) { if (reject) {
reject('servererror'); reject('servererror');
reject = null; reject = null;
} else { } else {
onErrorInternal(instance, 'servererror'); onErrorInternal(instance, 'servererror');
}
return;
} }
if (data.fatal) { return;
switch (data.type) { }
case Hls.ErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code === 0) { if (data.fatal) {
// This could be a CORS error related to access control response headers switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.debug('hls.js response error code: ' + data.response.code); if (data.response && data.response.code === 0) {
// This could be a CORS error related to access control response headers
// Trigger failure differently depending on whether this is prior to start of playback, or after console.debug('hls.js response error code: ' + data.response.code);
hls.destroy();
if (reject) {
reject('network');
reject = null;
} else {
onErrorInternal(instance, 'network');
}
} else {
console.debug('fatal network error encountered, try to recover');
hls.startLoad();
}
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.debug('fatal media error encountered, try to recover');
handleHlsJsMediaError(instance, reject);
reject = null;
break;
default:
console.debug('Cannot recover from hls error - destroy and trigger error');
// cannot recover
// Trigger failure differently depending on whether this is prior to start of playback, or after // Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy(); hls.destroy();
if (reject) { if (reject) {
reject(); reject('network');
reject = null; reject = null;
} else { } else {
onErrorInternal(instance, 'mediadecodeerror'); onErrorInternal(instance, 'network');
} }
break; } else {
} console.debug('fatal network error encountered, try to recover');
hls.startLoad();
}
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.debug('fatal media error encountered, try to recover');
handleHlsJsMediaError(instance, reject);
reject = null;
break;
default:
console.debug('Cannot recover from hls error - destroy and trigger error');
// cannot recover
// Trigger failure differently depending on whether this is prior to start of playback, or after
hls.destroy();
if (reject) {
reject();
reject = null;
} else {
onErrorInternal(instance, 'mediadecodeerror');
}
break;
} }
}
});
}
export function onEndedInternal(instance, elem, onErrorFn) {
elem.removeEventListener('error', onErrorFn);
resetSrc(elem);
destroyHlsPlayer(instance);
destroyFlvPlayer(instance);
destroyCastPlayer(instance);
const stopInfo = {
src: instance._currentSrc
};
Events.trigger(instance, 'stopped', [stopInfo]);
instance._currentTime = null;
instance._currentSrc = null;
instance._currentPlayOptions = null;
}
export function getBufferedRanges(instance, elem) {
const ranges = [];
const seekable = elem.buffered || [];
let offset;
const currentPlayOptions = instance._currentPlayOptions;
if (currentPlayOptions) {
offset = currentPlayOptions.transcodingOffsetTicks;
}
offset = offset || 0;
for (let i = 0, length = seekable.length; i < length; i++) {
let start = seekable.start(i);
let end = seekable.end(i);
if (!isValidDuration(start)) {
start = 0;
}
if (!isValidDuration(end)) {
end = 0;
continue;
}
ranges.push({
start: (start * 10000000) + offset,
end: (end * 10000000) + offset
}); });
} }
export function onEndedInternal(instance, elem, onErrorFn) { return ranges;
elem.removeEventListener('error', onErrorFn); }
resetSrc(elem);
destroyHlsPlayer(instance);
destroyFlvPlayer(instance);
destroyCastPlayer(instance);
const stopInfo = {
src: instance._currentSrc
};
Events.trigger(instance, 'stopped', [stopInfo]);
instance._currentTime = null;
instance._currentSrc = null;
instance._currentPlayOptions = null;
}
export function getBufferedRanges(instance, elem) {
const ranges = [];
const seekable = elem.buffered || [];
let offset;
const currentPlayOptions = instance._currentPlayOptions;
if (currentPlayOptions) {
offset = currentPlayOptions.transcodingOffsetTicks;
}
offset = offset || 0;
for (let i = 0, length = seekable.length; i < length; i++) {
let start = seekable.start(i);
let end = seekable.end(i);
if (!isValidDuration(start)) {
start = 0;
}
if (!isValidDuration(end)) {
end = 0;
continue;
}
ranges.push({
start: (start * 10000000) + offset,
end: (end * 10000000) + offset
});
}
return ranges;
}
/* eslint-enable indent */
+338 -341
View File
@@ -15,371 +15,369 @@ import '../cardbuilder/card.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import template from './imageDownloader.template.html'; import template from './imageDownloader.template.html';
/* eslint-disable indent */ const enableFocusTransform = !browser.slow && !browser.edge;
const enableFocusTransform = !browser.slow && !browser.edge; let currentItemId;
let currentItemType;
let currentResolve;
let currentReject;
let hasChanges = false;
let currentItemId; // These images can be large and we're seeing memory problems in safari
let currentItemType; const browsableImagePageSize = browser.slow ? 6 : 30;
let currentResolve;
let currentReject;
let hasChanges = false;
// These images can be large and we're seeing memory problems in safari let browsableImageStartIndex = 0;
const browsableImagePageSize = browser.slow ? 6 : 30; let browsableImageType = 'Primary';
let selectedProvider;
let browsableParentId;
let browsableImageStartIndex = 0; function getBaseRemoteOptions(page, forceCurrentItemId = false) {
let browsableImageType = 'Primary'; const options = {};
let selectedProvider;
let browsableParentId;
function getBaseRemoteOptions(page, forceCurrentItemId = false) { if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) {
const options = {}; options.itemId = browsableParentId;
} else {
if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) { options.itemId = currentItemId;
options.itemId = browsableParentId;
} else {
options.itemId = currentItemId;
}
return options;
} }
function reloadBrowsableImages(page, apiClient) { return options;
loading.show(); }
const options = getBaseRemoteOptions(page); function reloadBrowsableImages(page, apiClient) {
loading.show();
options.type = browsableImageType; const options = getBaseRemoteOptions(page);
options.startIndex = browsableImageStartIndex;
options.limit = browsableImagePageSize;
options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked;
const provider = selectedProvider || ''; options.type = browsableImageType;
options.startIndex = browsableImageStartIndex;
options.limit = browsableImagePageSize;
options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked;
if (provider) { const provider = selectedProvider || '';
options.ProviderName = provider;
}
apiClient.getAvailableRemoteImages(options).then(function (result) { if (provider) {
renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit);
page.querySelector('#selectBrowsableImageType').value = browsableImageType;
const providersHtml = result.Providers.map(function (p) {
return '<option value="' + p + '">' + p + '</option>';
});
const selectImageProvider = page.querySelector('#selectImageProvider');
selectImageProvider.innerHTML = '<option value="">' + globalize.translate('All') + '</option>' + providersHtml;
selectImageProvider.value = provider;
loading.hide();
});
}
function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) {
page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount);
let html = '';
for (let i = 0, length = imagesResult.Images.length; i < length; i++) {
html += getRemoteImageHtml(imagesResult.Images[i], imageType);
}
const availableImagesList = page.querySelector('.availableImagesList');
availableImagesList.innerHTML = html;
imageLoader.lazyChildren(availableImagesList);
const btnNextPage = page.querySelector('.btnNextPage');
const btnPreviousPage = page.querySelector('.btnPreviousPage');
if (btnNextPage) {
btnNextPage.addEventListener('click', function () {
browsableImageStartIndex += browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
if (btnPreviousPage) {
btnPreviousPage.addEventListener('click', function () {
browsableImageStartIndex -= browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
}
function getPagingHtml(startIndex, limit, totalRecordCount) {
let html = '';
const recordsEnd = Math.min(startIndex + limit, totalRecordCount);
// 20 is the minimum page size
const showControls = totalRecordCount > limit;
html += '<div class="listPaging">';
html += '<span style="margin-right: 10px;">';
const startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>';
if (showControls) {
html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">';
html += `<button is="paper-icon-button-light" title="${globalize.translate('Previous')}" class="btnPreviousPage autoSize" ${(startIndex ? '' : 'disabled')}><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += `<button is="paper-icon-button-light" title="${globalize.translate('Next')}" class="btnNextPage autoSize" ${(startIndex + limit >= totalRecordCount ? 'disabled' : '')}><span class="material-icons arrow_forward" aria-hidden="true"></span></button>`;
html += '</div>';
}
html += '</div>';
return html;
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
const options = getBaseRemoteOptions(page, true);
options.Type = type;
options.ImageUrl = url;
options.ProviderName = provider; options.ProviderName = provider;
loading.show();
apiClient.downloadRemoteImage(options).then(function () {
hasChanges = true;
const dlg = dom.parentWithClass(page, 'dialog');
dialogHelper.close(dlg);
});
} }
function getRemoteImageHtml(image, imageType) { apiClient.getAvailableRemoteImages(options).then(function (result) {
const tagName = layoutManager.tv ? 'button' : 'div'; renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit);
const enableFooterButtons = !layoutManager.tv;
// TODO move card creation code to Card component page.querySelector('#selectBrowsableImageType').value = browsableImageType;
let html = ''; const providersHtml = result.Providers.map(function (p) {
return '<option value="' + p + '">' + p + '</option>';
let cssClass = 'card scalableCard imageEditorCard';
const cardBoxCssClass = 'cardBox visualCardBox';
let shape;
if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') {
shape = 'backdrop';
} else if (imageType === 'Banner') {
shape = 'banner';
} else if (imageType === 'Disc') {
shape = 'square';
} else {
if (currentItemType === 'Episode') {
shape = 'backdrop';
} else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') {
shape = 'square';
} else {
shape = 'portrait';
}
}
cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable';
if (tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
}
html += '<button type="button" class="' + cssClass + '"';
} else {
html += '<div class="' + cssClass + '"';
}
html += ' data-imageprovider="' + image.ProviderName + '" data-imageurl="' + image.Url + '" data-imagetype="' + image.Type + '"';
html += '>';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable" style="background-color:transparent;">';
html += '<div class="cardPadder-' + shape + '"></div>';
html += '<div class="cardContent">';
if (layoutManager.tv || !appHost.supports('externallinks')) {
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
} else {
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
}
html += '</div>';
html += '</div>';
// begin footer
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<div class="cardText cardTextCentered">' + image.ProviderName + '</div>';
if (image.Width || image.Height || image.Language) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' x ' + image.Height;
if (image.Language) {
html += ' • ' + image.Language;
}
} else {
if (image.Language) {
html += image.Language;
}
}
html += '</div>';
}
if (image.CommunityRating != null) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.RatingType === 'Likes') {
html += image.CommunityRating + (image.CommunityRating === 1 ? ' like' : ' likes');
} else {
if (image.CommunityRating) {
html += image.CommunityRating.toFixed(1);
if (image.VoteCount) {
html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? ' vote' : ' votes');
}
} else {
html += 'Unrated';
}
}
html += '</div>';
}
if (enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
html += `<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="${globalize.translate('Download')}"><span class="material-icons cloud_download" aria-hidden="true"></span></button>`;
html += '</div>';
}
html += '</div>';
// end footer
html += '</div>';
html += '</' + tagName + '>';
return html;
}
function reloadBrowsableImagesFirstPage(page, apiClient) {
browsableImageStartIndex = 0;
reloadBrowsableImages(page, apiClient);
}
function initEditor(page, apiClient) {
page.querySelector('#selectBrowsableImageType').addEventListener('change', function () {
browsableImageType = this.value;
selectedProvider = null;
reloadBrowsableImagesFirstPage(page, apiClient);
}); });
page.querySelector('#selectImageProvider').addEventListener('change', function () { const selectImageProvider = page.querySelector('#selectImageProvider');
selectedProvider = this.value; selectImageProvider.innerHTML = '<option value="">' + globalize.translate('All') + '</option>' + providersHtml;
selectImageProvider.value = provider;
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.querySelector('#chkAllLanguages').addEventListener('change', function () {
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.querySelector('#chkShowParentImages').addEventListener('change', function () {
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.addEventListener('click', function (e) {
const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
if (btnDownloadRemoteImage) {
const card = dom.parentWithClass(btnDownloadRemoteImage, 'card');
downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
return;
}
const btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
if (btnImageCard) {
downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider'));
}
});
}
function showEditor(itemId, serverId, itemType) {
loading.show();
const apiClient = ServerConnections.getApiClient(serverId);
currentItemId = itemId;
currentItemType = itemType;
const dialogOptions = {
removeOnClose: true
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
if (browsableParentId) {
dlg.querySelector('#lblShowParentImages').classList.remove('hide');
}
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
const editorContent = dlg.querySelector('.formDialogContent');
initEditor(editorContent, apiClient);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
reloadBrowsableImages(editorContent, apiClient);
}
function onDialogClosed() {
const dlg = this;
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
loading.hide(); loading.hide();
if (hasChanges) { });
currentResolve(); }
function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) {
page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount);
let html = '';
for (let i = 0, length = imagesResult.Images.length; i < length; i++) {
html += getRemoteImageHtml(imagesResult.Images[i], imageType);
}
const availableImagesList = page.querySelector('.availableImagesList');
availableImagesList.innerHTML = html;
imageLoader.lazyChildren(availableImagesList);
const btnNextPage = page.querySelector('.btnNextPage');
const btnPreviousPage = page.querySelector('.btnPreviousPage');
if (btnNextPage) {
btnNextPage.addEventListener('click', function () {
browsableImageStartIndex += browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
if (btnPreviousPage) {
btnPreviousPage.addEventListener('click', function () {
browsableImageStartIndex -= browsableImagePageSize;
reloadBrowsableImages(page, apiClient);
});
}
}
function getPagingHtml(startIndex, limit, totalRecordCount) {
let html = '';
const recordsEnd = Math.min(startIndex + limit, totalRecordCount);
// 20 is the minimum page size
const showControls = totalRecordCount > limit;
html += '<div class="listPaging">';
html += '<span style="margin-right: 10px;">';
const startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>';
if (showControls) {
html += '<div data-role="controlgroup" data-type="horizontal" style="display:inline-block;">';
html += `<button is="paper-icon-button-light" title="${globalize.translate('Previous')}" class="btnPreviousPage autoSize" ${(startIndex ? '' : 'disabled')}><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += `<button is="paper-icon-button-light" title="${globalize.translate('Next')}" class="btnNextPage autoSize" ${(startIndex + limit >= totalRecordCount ? 'disabled' : '')}><span class="material-icons arrow_forward" aria-hidden="true"></span></button>`;
html += '</div>';
}
html += '</div>';
return html;
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
const options = getBaseRemoteOptions(page, true);
options.Type = type;
options.ImageUrl = url;
options.ProviderName = provider;
loading.show();
apiClient.downloadRemoteImage(options).then(function () {
hasChanges = true;
const dlg = dom.parentWithClass(page, 'dialog');
dialogHelper.close(dlg);
});
}
function getRemoteImageHtml(image, imageType) {
const tagName = layoutManager.tv ? 'button' : 'div';
const enableFooterButtons = !layoutManager.tv;
// TODO move card creation code to Card component
let html = '';
let cssClass = 'card scalableCard imageEditorCard';
const cardBoxCssClass = 'cardBox visualCardBox';
let shape;
if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') {
shape = 'backdrop';
} else if (imageType === 'Banner') {
shape = 'banner';
} else if (imageType === 'Disc') {
shape = 'square';
} else {
if (currentItemType === 'Episode') {
shape = 'backdrop';
} else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') {
shape = 'square';
} else { } else {
currentReject(); shape = 'portrait';
} }
} }
cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable';
if (tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
}
html += '<button type="button" class="' + cssClass + '"';
} else {
html += '<div class="' + cssClass + '"';
}
html += ' data-imageprovider="' + image.ProviderName + '" data-imageurl="' + image.Url + '" data-imagetype="' + image.Type + '"';
html += '>';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable" style="background-color:transparent;">';
html += '<div class="cardPadder-' + shape + '"></div>';
html += '<div class="cardContent">';
if (layoutManager.tv || !appHost.supports('externallinks')) {
html += '<div class="cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain;"></div>';
} else {
html += '<a is="emby-linkbutton" target="_blank" href="' + image.Url + '" class="button-link cardImageContainer lazy" data-src="' + image.Url + '" style="background-position:center center;background-size:contain"></a>';
}
html += '</div>';
html += '</div>';
// begin footer
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<div class="cardText cardTextCentered">' + image.ProviderName + '</div>';
if (image.Width || image.Height || image.Language) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' x ' + image.Height;
if (image.Language) {
html += ' • ' + image.Language;
}
} else {
if (image.Language) {
html += image.Language;
}
}
html += '</div>';
}
if (image.CommunityRating != null) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.RatingType === 'Likes') {
html += image.CommunityRating + (image.CommunityRating === 1 ? ' like' : ' likes');
} else {
if (image.CommunityRating) {
html += image.CommunityRating.toFixed(1);
if (image.VoteCount) {
html += ' • ' + image.VoteCount + (image.VoteCount === 1 ? ' vote' : ' votes');
}
} else {
html += 'Unrated';
}
}
html += '</div>';
}
if (enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
html += `<button is="paper-icon-button-light" class="btnDownloadRemoteImage autoSize" raised" title="${globalize.translate('Download')}"><span class="material-icons cloud_download" aria-hidden="true"></span></button>`;
html += '</div>';
}
html += '</div>';
// end footer
html += '</div>';
html += '</' + tagName + '>';
return html;
}
function reloadBrowsableImagesFirstPage(page, apiClient) {
browsableImageStartIndex = 0;
reloadBrowsableImages(page, apiClient);
}
function initEditor(page, apiClient) {
page.querySelector('#selectBrowsableImageType').addEventListener('change', function () {
browsableImageType = this.value;
selectedProvider = null;
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.querySelector('#selectImageProvider').addEventListener('change', function () {
selectedProvider = this.value;
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.querySelector('#chkAllLanguages').addEventListener('change', function () {
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.querySelector('#chkShowParentImages').addEventListener('change', function () {
reloadBrowsableImagesFirstPage(page, apiClient);
});
page.addEventListener('click', function (e) {
const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
if (btnDownloadRemoteImage) {
const card = dom.parentWithClass(btnDownloadRemoteImage, 'card');
downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
return;
}
const btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
if (btnImageCard) {
downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider'));
}
});
}
function showEditor(itemId, serverId, itemType) {
loading.show();
const apiClient = ServerConnections.getApiClient(serverId);
currentItemId = itemId;
currentItemType = itemType;
const dialogOptions = {
removeOnClose: true
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
if (browsableParentId) {
dlg.querySelector('#lblShowParentImages').classList.remove('hide');
}
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
const editorContent = dlg.querySelector('.formDialogContent');
initEditor(editorContent, apiClient);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
reloadBrowsableImages(editorContent, apiClient);
}
function onDialogClosed() {
const dlg = this;
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
loading.hide();
if (hasChanges) {
currentResolve();
} else {
currentReject();
}
}
export function show(itemId, serverId, itemType, imageType, parentId) { export function show(itemId, serverId, itemType, imageType, parentId) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
currentResolve = resolve; currentResolve = resolve;
@@ -397,4 +395,3 @@ export default {
show: show show: show
}; };
/* eslint-enable indent */
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for image Options Editor. * Module for image Options Editor.
@@ -13,95 +12,95 @@ import '../../elements/emby-select/emby-select';
import '../../elements/emby-input/emby-input'; import '../../elements/emby-input/emby-input';
import template from './imageOptionsEditor.template.html'; import template from './imageOptionsEditor.template.html';
function getDefaultImageConfig(itemType, type) { function getDefaultImageConfig(itemType, type) {
return { return {
Type: type, Type: type,
MinWidth: 0, MinWidth: 0,
Limit: type === 'Primary' ? 1 : 0 Limit: type === 'Primary' ? 1 : 0
}; };
} }
function findImageOptions(imageOptions, type) { function findImageOptions(imageOptions, type) {
return imageOptions.filter(i => { return imageOptions.filter(i => {
return i.Type == type; return i.Type == type;
})[0]; })[0];
} }
function getImageConfig(options, availableOptions, imageType, itemType) { function getImageConfig(options, availableOptions, imageType, itemType) {
return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType); return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType);
} }
function setVisibilityOfBackdrops(elem, visible) { function setVisibilityOfBackdrops(elem, visible) {
if (visible) { if (visible) {
elem.classList.remove('hide'); elem.classList.remove('hide');
elem.querySelector('input').setAttribute('required', 'required'); elem.querySelector('input').setAttribute('required', 'required');
} else {
elem.classList.add('hide');
elem.querySelector('input').setAttribute('required', '');
elem.querySelector('input').removeAttribute('required');
}
}
function loadValues(context, itemType, options, availableOptions) {
const supportedImageTypes = availableOptions.SupportedImageTypes || [];
setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop'));
Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => {
const imageType = i.getAttribute('data-imagetype');
const container = dom.parentWithTag(i, 'LABEL');
if (!supportedImageTypes.includes(imageType)) {
container.classList.add('hide');
} else { } else {
elem.classList.add('hide'); container.classList.remove('hide');
elem.querySelector('input').setAttribute('required', '');
elem.querySelector('input').removeAttribute('required');
} }
}
function loadValues(context, itemType, options, availableOptions) { if (getImageConfig(options, availableOptions, imageType, itemType).Limit) {
const supportedImageTypes = availableOptions.SupportedImageTypes || []; i.checked = true;
setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop')); } else {
Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => { i.checked = false;
const imageType = i.getAttribute('data-imagetype'); }
const container = dom.parentWithTag(i, 'LABEL'); });
const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType);
context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit;
context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth;
}
if (!supportedImageTypes.includes(imageType)) { function saveValues(context, options) {
container.classList.add('hide'); options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => {
} else { return {
container.classList.remove('hide'); Type: c.getAttribute('data-imagetype'),
} Limit: c.checked ? 1 : 0,
MinWidth: 0
};
});
options.ImageOptions.push({
Type: 'Backdrop',
Limit: context.querySelector('#txtMaxBackdrops').value,
MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value
});
}
if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { function showEditor(itemType, options, availableOptions) {
i.checked = true; const dlg = dialogHelper.createDialog({
} else { size: 'small',
i.checked = false; removeOnClose: true,
} scrollY: false
}); });
const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); dlg.classList.add('formDialog');
context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; dlg.innerHTML = globalize.translateHtml(template);
context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; dlg.addEventListener('close', function () {
} saveValues(dlg, options);
});
function saveValues(context, options) { loadValues(dlg, itemType, options, availableOptions);
options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => { dialogHelper.open(dlg).then(() => {
return { return;
Type: c.getAttribute('data-imagetype'), }).catch(() => {
Limit: c.checked ? 1 : 0, return;
MinWidth: 0 });
}; dlg.querySelector('.btnCancel').addEventListener('click', function () {
}); dialogHelper.close(dlg);
options.ImageOptions.push({ });
Type: 'Backdrop', }
Limit: context.querySelector('#txtMaxBackdrops').value,
MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value
});
}
function showEditor(itemType, options, availableOptions) {
const dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template);
dlg.addEventListener('close', function () {
saveValues(dlg, options);
});
loadValues(dlg, itemType, options, availableOptions);
dialogHelper.open(dlg).then(() => {
return;
}).catch(() => {
return;
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
export class editor { export class editor {
constructor() { constructor() {
@@ -109,5 +108,4 @@ export class editor {
} }
} }
/* eslint-enable indent */
export default editor; export default editor;
+136 -138
View File
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for imageUploader. * Module for imageUploader.
@@ -19,169 +18,168 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './imageUploader.template.html'; import template from './imageUploader.template.html';
let currentItemId; let currentItemId;
let currentServerId; let currentServerId;
let currentFile; let currentFile;
let hasChanges = false; let hasChanges = false;
function onFileReaderError(evt) { function onFileReaderError(evt) {
loading.hide();
switch (evt.target.error.code) {
case evt.target.error.NOT_FOUND_ERR:
toast(globalize.translate('MessageFileReadError'));
break;
case evt.target.error.ABORT_ERR:
break; // noop
default:
toast(globalize.translate('MessageFileReadError'));
break;
}
}
function setFiles(page, files) {
const file = files[0];
if (!file || !file.type.match('image.*')) {
page.querySelector('#imageOutput').innerHTML = '';
page.querySelector('#fldUpload').classList.add('hide');
currentFile = null;
return;
}
currentFile = file;
const reader = new FileReader();
reader.onerror = onFileReaderError;
reader.onloadstart = () => {
page.querySelector('#fldUpload').classList.add('hide');
};
reader.onabort = () => {
loading.hide(); loading.hide();
console.debug('File read cancelled');
};
switch (evt.target.error.code) { // Closure to capture the file information.
case evt.target.error.NOT_FOUND_ERR: reader.onload = (theFile => {
toast(globalize.translate('MessageFileReadError')); return e => {
break; // Render thumbnail.
case evt.target.error.ABORT_ERR: const html = ['<img style="max-width:100%;max-height:100%;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
break; // noop
default: page.querySelector('#imageOutput').innerHTML = html;
toast(globalize.translate('MessageFileReadError')); page.querySelector('#dropImageText').classList.add('hide');
break; page.querySelector('#fldUpload').classList.remove('hide');
} };
})(file);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
function onSubmit(e) {
const file = currentFile;
if (!file) {
return false;
} }
function setFiles(page, files) { if (!file.type.startsWith('image/')) {
const file = files[0]; toast(globalize.translate('MessageImageFileTypeAllowed'));
if (!file || !file.type.match('image.*')) {
page.querySelector('#imageOutput').innerHTML = '';
page.querySelector('#fldUpload').classList.add('hide');
currentFile = null;
return;
}
currentFile = file;
const reader = new FileReader();
reader.onerror = onFileReaderError;
reader.onloadstart = () => {
page.querySelector('#fldUpload').classList.add('hide');
};
reader.onabort = () => {
loading.hide();
console.debug('File read cancelled');
};
// Closure to capture the file information.
reader.onload = (theFile => {
return e => {
// Render thumbnail.
const html = ['<img style="max-width:100%;max-height:100%;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
page.querySelector('#imageOutput').innerHTML = html;
page.querySelector('#dropImageText').classList.add('hide');
page.querySelector('#fldUpload').classList.remove('hide');
};
})(file);
// Read in the image file as a data URL.
reader.readAsDataURL(file);
}
function onSubmit(e) {
const file = currentFile;
if (!file) {
return false;
}
if (!file.type.startsWith('image/')) {
toast(globalize.translate('MessageImageFileTypeAllowed'));
e.preventDefault();
return false;
}
loading.show();
const dlg = dom.parentWithClass(this, 'dialog');
const imageType = dlg.querySelector('#selectImageType').value;
if (imageType === 'None') {
toast(globalize.translate('MessageImageTypeNotSelected'));
e.preventDefault();
return false;
}
ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => {
dlg.querySelector('#uploadImage').value = '';
loading.hide();
hasChanges = true;
dialogHelper.close(dlg);
});
e.preventDefault(); e.preventDefault();
return false; return false;
} }
function initEditor(page) { loading.show();
page.querySelector('form').addEventListener('submit', onSubmit);
page.querySelector('#uploadImage').addEventListener('change', function () { const dlg = dom.parentWithClass(this, 'dialog');
setFiles(page, this.files);
});
page.querySelector('.btnBrowse').addEventListener('click', () => { const imageType = dlg.querySelector('#selectImageType').value;
page.querySelector('#uploadImage').click(); if (imageType === 'None') {
}); toast(globalize.translate('MessageImageTypeNotSelected'));
e.preventDefault();
return false;
} }
function showEditor(options, resolve) { ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => {
options = options || {}; dlg.querySelector('#uploadImage').value = '';
currentItemId = options.itemId; loading.hide();
currentServerId = options.serverId; hasChanges = true;
dialogHelper.close(dlg);
});
const dialogOptions = { e.preventDefault();
removeOnClose: true return false;
}; }
function initEditor(page) {
page.querySelector('form').addEventListener('submit', onSubmit);
page.querySelector('#uploadImage').addEventListener('change', function () {
setFiles(page, this.files);
});
page.querySelector('.btnBrowse').addEventListener('click', () => {
page.querySelector('#uploadImage').click();
});
}
function showEditor(options, resolve) {
options = options || {};
currentItemId = options.itemId;
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', () => {
if (layoutManager.tv) { if (layoutManager.tv) {
dialogOptions.size = 'fullscreen'; scrollHelper.centerFocus.off(dlg, false);
} else {
dialogOptions.size = 'small';
} }
const dlg = dialogHelper.createDialog(dialogOptions); loading.hide();
resolve(hasChanges);
});
dlg.classList.add('formDialog'); dialogHelper.open(dlg);
dlg.innerHTML = globalize.translateHtml(template, 'core'); initEditor(dlg);
if (layoutManager.tv) { dlg.querySelector('#selectImageType').value = options.imageType || 'Primary';
scrollHelper.centerFocus.on(dlg, false);
}
// Has to be assigned a z-index after the call to .open() dlg.querySelector('.btnCancel').addEventListener('click', () => {
dlg.addEventListener('close', () => { dialogHelper.close(dlg);
if (layoutManager.tv) { });
scrollHelper.centerFocus.off(dlg, false); }
}
loading.hide(); export function show(options) {
resolve(hasChanges); return new Promise(resolve => {
}); hasChanges = false;
dialogHelper.open(dlg); showEditor(options, resolve);
});
}
initEditor(dlg);
dlg.querySelector('#selectImageType').value = options.imageType || 'Primary';
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
}
export function show(options) {
return new Promise(resolve => {
hasChanges = false;
showEditor(options, resolve);
});
}
/* eslint-enable indent */
export default { export default {
show: show show: show
}; };
+394 -397
View File
@@ -18,441 +18,439 @@ import alert from '../alert';
import confirm from '../confirm/confirm'; import confirm from '../confirm/confirm';
import template from './imageeditor.template.html'; import template from './imageeditor.template.html';
/* eslint-disable indent */ const enableFocusTransform = !browser.slow && !browser.edge;
const enableFocusTransform = !browser.slow && !browser.edge; let currentItem;
let hasChanges = false;
let currentItem; function getBaseRemoteOptions() {
let hasChanges = false; return { itemId: currentItem.Id };
}
function getBaseRemoteOptions() { function reload(page, item, focusContext) {
return { itemId: currentItem.Id }; loading.show();
let apiClient;
if (item) {
apiClient = ServerConnections.getApiClient(item.ServerId);
reloadItem(page, item, apiClient, focusContext);
} else {
apiClient = ServerConnections.getApiClient(currentItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) {
reloadItem(page, itemToReload, apiClient, focusContext);
});
} }
}
function reload(page, item, focusContext) { function addListeners(container, className, eventName, fn) {
loading.show(); container.addEventListener(eventName, function (e) {
const elem = dom.parentWithClass(e.target, className);
let apiClient; if (elem) {
fn.call(elem, e);
if (item) {
apiClient = ServerConnections.getApiClient(item.ServerId);
reloadItem(page, item, apiClient, focusContext);
} else {
apiClient = ServerConnections.getApiClient(currentItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) {
reloadItem(page, itemToReload, apiClient, focusContext);
});
} }
} });
}
function addListeners(container, className, eventName, fn) { function reloadItem(page, item, apiClient, focusContext) {
container.addEventListener(eventName, function (e) { currentItem = item;
const elem = dom.parentWithClass(e.target, className);
if (elem) { apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) {
fn.call(elem, e); const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages');
for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) {
if (providers.length) {
btnBrowseAllImages[i].classList.remove('hide');
} else {
btnBrowseAllImages[i].classList.add('hide');
}
}
apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) {
renderStandardImages(page, apiClient, item, imageInfos, providers);
renderBackdrops(page, apiClient, item, imageInfos, providers);
loading.hide();
if (layoutManager.tv) {
focusManager.autoFocus((focusContext || page));
} }
}); });
});
}
function getImageUrl(item, apiClient, type, index, options) {
options = options || {};
options.type = type;
options.index = index;
if (type === 'Backdrop') {
options.tag = item.BackdropImageTags[index];
} else if (type === 'Primary') {
options.tag = item.PrimaryImageTag || item.ImageTags[type];
} else {
options.tag = item.ImageTags[type];
} }
function reloadItem(page, item, apiClient, focusContext) { // For search hints
currentItem = item; return apiClient.getScaledImageUrl(item.Id || item.ItemId, options);
}
apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) { function getCardHtml(image, apiClient, options) {
const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages'); // TODO move card creation code to Card component
for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) {
if (providers.length) { let html = '';
btnBrowseAllImages[i].classList.remove('hide');
} else { let cssClass = 'card scalableCard imageEditorCard';
btnBrowseAllImages[i].classList.add('hide'); const cardBoxCssClass = 'cardBox visualCardBox';
}
cssClass += ' backdropCard backdropCard-scalable';
if (options.tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
}
html += '<button type="button" class="' + cssClass + '"';
} else {
html += '<div class="' + cssClass + '"';
}
html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + options.index + '" data-numimages="' + options.numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + options.imageProviders.length + '"';
html += '>';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable" style="background-color:transparent;">';
html += '<div class="cardPadder-backdrop"></div>';
html += '<div class="cardContent">';
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: options.imageSize });
html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>';
html += '</div>';
html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<h3 class="cardText cardTextCentered" style="margin:0;">' + globalize.translate('' + image.ImageType) + '</h3>';
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' X ' + image.Height;
} else {
html += '&nbsp;';
}
html += '</div>';
if (options.enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
if (image.ImageType === 'Backdrop') {
if (options.index > 0) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
} else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>';
} }
apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) { if (options.index < options.numImages - 1) {
renderStandardImages(page, apiClient, item, imageInfos, providers); html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
renderBackdrops(page, apiClient, item, imageInfos, providers); } else {
loading.hide(); html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
}
} else {
if (options.imageProviders.length) {
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>';
}
}
if (layoutManager.tv) { html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" data-index="' + (image.ImageIndex != null ? image.ImageIndex : 'null') + '" class="btnDeleteImage autoSize" title="' + globalize.translate('Delete') + '"><span class="material-icons delete" aria-hidden="true"></span></button>';
focusManager.autoFocus((focusContext || page)); html += '</div>';
}
});
});
} }
function getImageUrl(item, apiClient, type, index, options) { html += '</div>';
options = options || {}; html += '</div>';
options.type = type; html += '</' + options.tagName + '>';
options.index = index;
return html;
}
function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) {
const afterConfirm = function () {
apiClient.deleteItemImage(itemId, type, index).then(function () {
hasChanges = true;
reload(context);
});
};
if (!enableConfirmation) {
afterConfirm();
return;
}
confirm({
text: globalize.translate('ConfirmDeleteImage'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(afterConfirm);
}
function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) {
apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () {
hasChanges = true;
reload(context, null, focusContext);
}, function () {
alert(globalize.translate('ErrorDefault'));
});
}
function renderImages(page, item, apiClient, images, imageProviders, elem) {
let html = '';
let imageSize = 300;
const windowSize = dom.getWindowSize();
if (windowSize.innerWidth >= 1280) {
imageSize = Math.round(windowSize.innerWidth / 4);
}
const tagName = layoutManager.tv ? 'button' : 'div';
const enableFooterButtons = !layoutManager.tv;
for (let i = 0, length = images.length; i < length; i++) {
const image = images[i];
const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons };
html += getCardHtml(image, apiClient, options);
}
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
}
function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) {
const images = imageInfos.filter(function (i) {
return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter';
});
renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images'));
}
function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) {
const images = imageInfos.filter(function (i) {
return i.ImageType === 'Backdrop';
}).sort(function (a, b) {
return a.ImageIndex - b.ImageIndex;
});
if (images.length) {
page.querySelector('#backdropsContainer', page).classList.remove('hide');
renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops'));
} else {
page.querySelector('#backdropsContainer', page).classList.add('hide');
}
}
function showImageDownloader(page, imageType) {
import('../imageDownloader/imageDownloader').then((ImageDownloader) => {
ImageDownloader.show(
currentItem.Id,
currentItem.ServerId,
currentItem.Type,
imageType,
currentItem.Type == 'Season' ? currentItem.ParentId : null
).then(function () {
hasChanges = true;
reload(page);
});
});
}
function showActionSheet(context, imageCard) {
const itemId = imageCard.getAttribute('data-id');
const serverId = imageCard.getAttribute('data-serverid');
const apiClient = ServerConnections.getApiClient(serverId);
const type = imageCard.getAttribute('data-imagetype');
const index = parseInt(imageCard.getAttribute('data-index'), 10);
const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10);
const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10);
import('../actionSheet/actionSheet').then(({ default: actionSheet }) => {
const commands = [];
commands.push({
name: globalize.translate('Delete'),
id: 'delete'
});
if (type === 'Backdrop') { if (type === 'Backdrop') {
options.tag = item.BackdropImageTags[index]; if (index > 0) {
} else if (type === 'Primary') { commands.push({
options.tag = item.PrimaryImageTag || item.ImageTags[type]; name: globalize.translate('MoveLeft'),
} else { id: 'moveleft'
options.tag = item.ImageTags[type]; });
}
// For search hints
return apiClient.getScaledImageUrl(item.Id || item.ItemId, options);
}
function getCardHtml(image, apiClient, options) {
// TODO move card creation code to Card component
let html = '';
let cssClass = 'card scalableCard imageEditorCard';
const cardBoxCssClass = 'cardBox visualCardBox';
cssClass += ' backdropCard backdropCard-scalable';
if (options.tagName === 'button') {
cssClass += ' btnImageCard';
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
} }
html += '<button type="button" class="' + cssClass + '"'; if (index < numImages - 1) {
} else { commands.push({
html += '<div class="' + cssClass + '"'; name: globalize.translate('MoveRight'),
} id: 'moveright'
});
html += ' data-id="' + currentItem.Id + '" data-serverid="' + apiClient.serverId() + '" data-index="' + options.index + '" data-numimages="' + options.numImages + '" data-imagetype="' + image.ImageType + '" data-providers="' + options.imageProviders.length + '"';
html += '>';
html += '<div class="' + cardBoxCssClass + '">';
html += '<div class="cardScalable visualCardBox-cardScalable" style="background-color:transparent;">';
html += '<div class="cardPadder-backdrop"></div>';
html += '<div class="cardContent">';
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: options.imageSize });
html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>';
html += '</div>';
html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">';
html += '<h3 class="cardText cardTextCentered" style="margin:0;">' + globalize.translate('' + image.ImageType) + '</h3>';
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' X ' + image.Height;
} else {
html += '&nbsp;';
}
html += '</div>';
if (options.enableFooterButtons) {
html += '<div class="cardText cardTextCentered">';
if (image.ImageType === 'Backdrop') {
if (options.index > 0) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
} else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left" aria-hidden="true"></span></button>';
}
if (options.index < options.numImages - 1) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex + 1) + '" title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
} else {
html += '<button type="button" is="paper-icon-button-light" class="autoSize" disabled title="' + globalize.translate('MoveRight') + '"><span class="material-icons chevron_right" aria-hidden="true"></span></button>';
}
} else {
if (options.imageProviders.length) {
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" class="btnSearchImages autoSize" title="' + globalize.translate('Search') + '"><span class="material-icons search" aria-hidden="true"></span></button>';
}
} }
html += '<button type="button" is="paper-icon-button-light" data-imagetype="' + image.ImageType + '" data-index="' + (image.ImageIndex != null ? image.ImageIndex : 'null') + '" class="btnDeleteImage autoSize" title="' + globalize.translate('Delete') + '"><span class="material-icons delete" aria-hidden="true"></span></button>';
html += '</div>';
} }
html += '</div>'; if (providerCount) {
html += '</div>'; commands.push({
html += '</' + options.tagName + '>'; name: globalize.translate('Search'),
id: 'search'
return html;
}
function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) {
const afterConfirm = function () {
apiClient.deleteItemImage(itemId, type, index).then(function () {
hasChanges = true;
reload(context);
}); });
}
actionSheet.show({
items: commands,
positionTo: imageCard
}).then(function (id) {
switch (id) {
case 'delete':
deleteImage(context, itemId, type, index, apiClient, false);
break;
case 'search':
showImageDownloader(context, type);
break;
case 'moveleft':
moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer'));
break;
case 'moveright':
moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer'));
break;
default:
break;
}
});
});
}
function initEditor(context, options) {
const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
const isFileInputSupported = appHost.supports('fileinput');
for (let i = 0, length = uploadButtons.length; i < length; i++) {
if (isFileInputSupported) {
uploadButtons[i].classList.remove('hide');
} else {
uploadButtons[i].classList.add('hide');
}
}
addListeners(context, 'btnOpenUploadMenu', 'click', function () {
const imageType = this.getAttribute('data-imagetype');
import('../imageUploader/imageUploader').then(({ default: imageUploader }) => {
imageUploader.show({
theme: options.theme,
imageType: imageType,
itemId: currentItem.Id,
serverId: currentItem.ServerId
}).then(function (hasChanged) {
if (hasChanged) {
hasChanges = true;
reload(context);
}
});
});
});
addListeners(context, 'btnSearchImages', 'click', function () {
showImageDownloader(context, this.getAttribute('data-imagetype'));
});
addListeners(context, 'btnBrowseAllImages', 'click', function () {
showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary');
});
addListeners(context, 'btnImageCard', 'click', function () {
showActionSheet(context, this);
});
addListeners(context, 'btnDeleteImage', 'click', function () {
const type = this.getAttribute('data-imagetype');
let index = this.getAttribute('data-index');
index = index === 'null' ? null : parseInt(index, 10);
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
deleteImage(context, currentItem.Id, type, index, apiClient, true);
});
addListeners(context, 'btnMoveImage', 'click', function () {
const type = this.getAttribute('data-imagetype');
const index = this.getAttribute('data-index');
const newIndex = this.getAttribute('data-newindex');
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer'));
});
}
function showEditor(options, resolve, reject) {
const itemId = options.itemId;
const serverId = options.serverId;
loading.show();
const apiClient = ServerConnections.getApiClient(serverId);
apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
const dialogOptions = {
removeOnClose: true
}; };
if (!enableConfirmation) { if (layoutManager.tv) {
afterConfirm(); dialogOptions.size = 'fullscreen';
return;
}
confirm({
text: globalize.translate('ConfirmDeleteImage'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(afterConfirm);
}
function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) {
apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () {
hasChanges = true;
reload(context, null, focusContext);
}, function () {
alert(globalize.translate('ErrorDefault'));
});
}
function renderImages(page, item, apiClient, images, imageProviders, elem) {
let html = '';
let imageSize = 300;
const windowSize = dom.getWindowSize();
if (windowSize.innerWidth >= 1280) {
imageSize = Math.round(windowSize.innerWidth / 4);
}
const tagName = layoutManager.tv ? 'button' : 'div';
const enableFooterButtons = !layoutManager.tv;
for (let i = 0, length = images.length; i < length; i++) {
const image = images[i];
const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons };
html += getCardHtml(image, apiClient, options);
}
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
}
function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) {
const images = imageInfos.filter(function (i) {
return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter';
});
renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images'));
}
function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) {
const images = imageInfos.filter(function (i) {
return i.ImageType === 'Backdrop';
}).sort(function (a, b) {
return a.ImageIndex - b.ImageIndex;
});
if (images.length) {
page.querySelector('#backdropsContainer', page).classList.remove('hide');
renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops'));
} else { } else {
page.querySelector('#backdropsContainer', page).classList.add('hide'); dialogOptions.size = 'small';
}
}
function showImageDownloader(page, imageType) {
import('../imageDownloader/imageDownloader').then((ImageDownloader) => {
ImageDownloader.show(
currentItem.Id,
currentItem.ServerId,
currentItem.Type,
imageType,
currentItem.Type == 'Season' ? currentItem.ParentId : null
).then(function () {
hasChanges = true;
reload(page);
});
});
}
function showActionSheet(context, imageCard) {
const itemId = imageCard.getAttribute('data-id');
const serverId = imageCard.getAttribute('data-serverid');
const apiClient = ServerConnections.getApiClient(serverId);
const type = imageCard.getAttribute('data-imagetype');
const index = parseInt(imageCard.getAttribute('data-index'), 10);
const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10);
const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10);
import('../actionSheet/actionSheet').then(({default: actionSheet}) => {
const commands = [];
commands.push({
name: globalize.translate('Delete'),
id: 'delete'
});
if (type === 'Backdrop') {
if (index > 0) {
commands.push({
name: globalize.translate('MoveLeft'),
id: 'moveleft'
});
}
if (index < numImages - 1) {
commands.push({
name: globalize.translate('MoveRight'),
id: 'moveright'
});
}
}
if (providerCount) {
commands.push({
name: globalize.translate('Search'),
id: 'search'
});
}
actionSheet.show({
items: commands,
positionTo: imageCard
}).then(function (id) {
switch (id) {
case 'delete':
deleteImage(context, itemId, type, index, apiClient, false);
break;
case 'search':
showImageDownloader(context, type);
break;
case 'moveleft':
moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer'));
break;
case 'moveright':
moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer'));
break;
default:
break;
}
});
});
}
function initEditor(context, options) {
const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
const isFileInputSupported = appHost.supports('fileinput');
for (let i = 0, length = uploadButtons.length; i < length; i++) {
if (isFileInputSupported) {
uploadButtons[i].classList.remove('hide');
} else {
uploadButtons[i].classList.add('hide');
}
} }
addListeners(context, 'btnOpenUploadMenu', 'click', function () { const dlg = dialogHelper.createDialog(dialogOptions);
const imageType = this.getAttribute('data-imagetype');
import('../imageUploader/imageUploader').then(({default: imageUploader}) => { dlg.classList.add('formDialog');
imageUploader.show({
theme: options.theme, dlg.innerHTML = globalize.translateHtml(template, 'core');
imageType: imageType,
itemId: currentItem.Id,
serverId: currentItem.ServerId
}).then(function (hasChanged) { if (layoutManager.tv) {
if (hasChanged) { scrollHelper.centerFocus.on(dlg, false);
hasChanges = true; }
reload(context);
}
});
});
});
addListeners(context, 'btnSearchImages', 'click', function () { initEditor(dlg, options);
showImageDownloader(context, this.getAttribute('data-imagetype'));
});
addListeners(context, 'btnBrowseAllImages', 'click', function () {
showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary');
});
addListeners(context, 'btnImageCard', 'click', function () {
showActionSheet(context, this);
});
addListeners(context, 'btnDeleteImage', 'click', function () {
const type = this.getAttribute('data-imagetype');
let index = this.getAttribute('data-index');
index = index === 'null' ? null : parseInt(index, 10);
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
deleteImage(context, currentItem.Id, type, index, apiClient, true);
});
addListeners(context, 'btnMoveImage', 'click', function () {
const type = this.getAttribute('data-imagetype');
const index = this.getAttribute('data-index');
const newIndex = this.getAttribute('data-newindex');
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer'));
});
}
function showEditor(options, resolve, reject) {
const itemId = options.itemId;
const serverId = options.serverId;
loading.show();
const apiClient = ServerConnections.getApiClient(serverId);
apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
const dialogOptions = {
removeOnClose: true
};
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', function () {
if (layoutManager.tv) { if (layoutManager.tv) {
dialogOptions.size = 'fullscreen'; scrollHelper.centerFocus.off(dlg, false);
}
loading.hide();
if (hasChanges) {
resolve();
} else { } else {
dialogOptions.size = 'small'; reject();
} }
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
initEditor(dlg, options);
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
loading.hide();
if (hasChanges) {
resolve();
} else {
reject();
}
});
dialogHelper.open(dlg);
reload(dlg, item);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
}); });
}
dialogHelper.open(dlg);
reload(dlg, item);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
});
}
export function show (options) { export function show (options) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
@@ -465,4 +463,3 @@ export default {
show show
}; };
/* eslint-enable indent */
+199 -201
View File
@@ -17,237 +17,235 @@ worker.addEventListener(
} }
} }
); );
/* eslint-disable indent */
export function lazyImage(elem, source = elem.getAttribute('data-src')) { export function lazyImage(elem, source = elem.getAttribute('data-src')) {
if (!source) { if (!source) {
return; return;
}
fillImageElement(elem, source);
} }
function drawBlurhash(target, pixels, width, height) { fillImageElement(elem, source);
const canvas = document.createElement('canvas'); }
canvas.setAttribute('aria-hidden', 'true');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const imgData = ctx.createImageData(width, height);
imgData.data.set(pixels); function drawBlurhash(target, pixels, width, height) {
ctx.putImageData(imgData, 0, 0); const canvas = document.createElement('canvas');
canvas.setAttribute('aria-hidden', 'true');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const imgData = ctx.createImageData(width, height);
requestAnimationFrame(() => { imgData.data.set(pixels);
// This class is just an utility class, so users can customize the canvas using their own CSS. ctx.putImageData(imgData, 0, 0);
canvas.classList.add('blurhash-canvas');
target.parentNode.insertBefore(canvas, target); requestAnimationFrame(() => {
target.classList.add('blurhashed'); // This class is just an utility class, so users can customize the canvas using their own CSS.
target.removeAttribute('data-blurhash'); canvas.classList.add('blurhash-canvas');
target.parentNode.insertBefore(canvas, target);
target.classList.add('blurhashed');
target.removeAttribute('data-blurhash');
});
}
function itemBlurhashing(target, hash) {
try {
// Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us,
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
// Lower values had more visible pixelation
const width = 20;
const height = 20;
targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target);
targetDic[hash].push(target);
worker.postMessage({
hash,
width,
height
}); });
} catch (err) {
console.error(err);
target.classList.add('non-blurhashable');
return;
}
}
export function fillImage(entry) {
if (!entry) {
throw new Error('entry cannot be null');
}
const target = entry.target;
let source = undefined;
if (target) {
source = target.getAttribute('data-src');
} else {
source = entry;
} }
function itemBlurhashing(target, hash) { if (entry.isIntersecting) {
try { if (source) {
// Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us, fillImageElement(target, source);
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
// Lower values had more visible pixelation
const width = 20;
const height = 20;
targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target);
targetDic[hash].push(target);
worker.postMessage({
hash,
width,
height
});
} catch (err) {
console.error(err);
target.classList.add('non-blurhashable');
return;
} }
} else if (!source) {
emptyImageElement(target);
} }
}
export function fillImage(entry) { function onAnimationEnd(event) {
if (!entry) { const elem = event.target;
throw new Error('entry cannot be null'); requestAnimationFrame(() => {
}
const target = entry.target;
let source = undefined;
if (target) {
source = target.getAttribute('data-src');
} else {
source = entry;
}
if (entry.isIntersecting) {
if (source) {
fillImageElement(target, source);
}
} else if (!source) {
emptyImageElement(target);
}
}
function onAnimationEnd(event) {
const elem = event.target;
requestAnimationFrame(() => {
const canvas = elem.previousSibling;
if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') {
canvas.classList.add('lazy-hidden');
}
// HACK: Hide the content of the card padder
elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children');
});
elem.removeEventListener('animationend', onAnimationEnd);
}
function fillImageElement(elem, url) {
if (url === undefined) {
throw new TypeError('url cannot be undefined');
}
const preloaderImg = new Image();
preloaderImg.src = url;
elem.classList.add('lazy-hidden');
elem.addEventListener('animationend', onAnimationEnd);
preloaderImg.addEventListener('load', () => {
requestAnimationFrame(() => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
}
elem.removeAttribute('data-src');
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
elem.classList.remove('lazy-hidden');
});
});
}
function emptyImageElement(elem) {
elem.removeEventListener('animationend', onAnimationEnd);
const canvas = elem.previousSibling; const canvas = elem.previousSibling;
if (canvas?.tagName === 'CANVAS') { if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') {
canvas.classList.remove('lazy-hidden'); canvas.classList.add('lazy-hidden');
} }
// HACK: Unhide the content of the card padder // HACK: Hide the content of the card padder
elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children'); elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children');
});
elem.removeEventListener('animationend', onAnimationEnd);
}
let url; function fillImageElement(elem, url) {
if (url === undefined) {
if (elem.tagName !== 'IMG') { throw new TypeError('url cannot be undefined');
url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, '');
elem.style.backgroundImage = 'none';
} else {
url = elem.getAttribute('src');
elem.setAttribute('src', '');
}
elem.setAttribute('data-src', url);
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
} }
export function lazyChildren(elem) { const preloaderImg = new Image();
if (userSettings.enableBlurhash()) { preloaderImg.src = url;
for (const lazyElem of elem.querySelectorAll('.lazy')) {
const blurhashstr = lazyElem.getAttribute('data-blurhash'); elem.classList.add('lazy-hidden');
if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { elem.addEventListener('animationend', onAnimationEnd);
itemBlurhashing(lazyElem, blurhashstr);
} else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { preloaderImg.addEventListener('load', () => {
lazyElem.classList.add('non-blurhashable'); requestAnimationFrame(() => {
} if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
} }
} elem.removeAttribute('data-src');
lazyLoader.lazyChildren(elem, fillImage); if (userSettings.enableFastFadein()) {
} elem.classList.add('lazy-image-fadein-fast');
} else {
export function getPrimaryImageAspectRatio(items) { elem.classList.add('lazy-image-fadein');
const values = [];
for (let i = 0, length = items.length; i < length; i++) {
const ratio = items[i].PrimaryImageAspectRatio || 0;
if (!ratio) {
continue;
} }
elem.classList.remove('lazy-hidden');
values[values.length] = ratio;
}
if (!values.length) {
return null;
}
// Use the median
values.sort(function (a, b) {
return a - b;
}); });
});
}
const half = Math.floor(values.length / 2); function emptyImageElement(elem) {
elem.removeEventListener('animationend', onAnimationEnd);
let result; const canvas = elem.previousSibling;
if (canvas?.tagName === 'CANVAS') {
if (values.length % 2) { canvas.classList.remove('lazy-hidden');
result = values[half];
} else {
result = (values[half - 1] + values[half]) / 2.0;
}
// If really close to 2:3 (poster image), just return 2:3
const aspect2x3 = 2 / 3;
if (Math.abs(aspect2x3 - result) <= 0.15) {
return aspect2x3;
}
// If really close to 16:9 (episode image), just return 16:9
const aspect16x9 = 16 / 9;
if (Math.abs(aspect16x9 - result) <= 0.2) {
return aspect16x9;
}
// If really close to 1 (square image), just return 1
if (Math.abs(1 - result) <= 0.15) {
return 1;
}
// If really close to 4:3 (poster image), just return 2:3
const aspect4x3 = 4 / 3;
if (Math.abs(aspect4x3 - result) <= 0.15) {
return aspect4x3;
}
return result;
} }
export function fillImages(elems) { // HACK: Unhide the content of the card padder
for (let i = 0, length = elems.length; i < length; i++) { elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children');
const elem = elems[0];
fillImage(elem); let url;
if (elem.tagName !== 'IMG') {
url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, '');
elem.style.backgroundImage = 'none';
} else {
url = elem.getAttribute('src');
elem.setAttribute('src', '');
}
elem.setAttribute('data-src', url);
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
}
export function lazyChildren(elem) {
if (userSettings.enableBlurhash()) {
for (const lazyElem of elem.querySelectorAll('.lazy')) {
const blurhashstr = lazyElem.getAttribute('data-blurhash');
if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) {
itemBlurhashing(lazyElem, blurhashstr);
} else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) {
lazyElem.classList.add('non-blurhashable');
}
} }
} }
export function setLazyImage(element, url) { lazyLoader.lazyChildren(elem, fillImage);
element.classList.add('lazy'); }
element.setAttribute('data-src', url);
lazyImage(element); export function getPrimaryImageAspectRatio(items) {
const values = [];
for (let i = 0, length = items.length; i < length; i++) {
const ratio = items[i].PrimaryImageAspectRatio || 0;
if (!ratio) {
continue;
}
values[values.length] = ratio;
} }
/* eslint-enable indent */ if (!values.length) {
return null;
}
// Use the median
values.sort(function (a, b) {
return a - b;
});
const half = Math.floor(values.length / 2);
let result;
if (values.length % 2) {
result = values[half];
} else {
result = (values[half - 1] + values[half]) / 2.0;
}
// If really close to 2:3 (poster image), just return 2:3
const aspect2x3 = 2 / 3;
if (Math.abs(aspect2x3 - result) <= 0.15) {
return aspect2x3;
}
// If really close to 16:9 (episode image), just return 16:9
const aspect16x9 = 16 / 9;
if (Math.abs(aspect16x9 - result) <= 0.2) {
return aspect16x9;
}
// If really close to 1 (square image), just return 1
if (Math.abs(1 - result) <= 0.15) {
return 1;
}
// If really close to 4:3 (poster image), just return 2:3
const aspect4x3 = 4 / 3;
if (Math.abs(aspect4x3 - result) <= 0.15) {
return aspect4x3;
}
return result;
}
export function fillImages(elems) {
for (let i = 0, length = elems.length; i < length; i++) {
const elem = elems[0];
fillImage(elem);
}
}
export function setLazyImage(element, url) {
element.classList.add('lazy');
element.setAttribute('data-src', url);
lazyImage(element);
}
export default { export default {
setLazyImage: setLazyImage, setLazyImage: setLazyImage,
fillImages: fillImages, fillImages: fillImages,
File diff suppressed because it is too large Load Diff
+213 -215
View File
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for display media info. * Module for display media info.
@@ -30,229 +29,228 @@ const copyButtonHtml = layoutManager.tv ? '' :
><span class="material-icons content_copy" aria-hidden="true"></span></button>`; ><span class="material-icons content_copy" aria-hidden="true"></span></button>`;
const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </span>'; const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </span>';
function setMediaInfo(user, page, item) { function setMediaInfo(user, page, item) {
let html = item.MediaSources.map(version => { let html = item.MediaSources.map(version => {
return getMediaSourceHtml(user, item, version); return getMediaSourceHtml(user, item, version);
}).join('<div style="border-top:1px solid #444;margin: 1em 0;"></div>'); }).join('<div style="border-top:1px solid #444;margin: 1em 0;"></div>');
if (item.MediaSources.length > 1) { if (item.MediaSources.length > 1) {
html = `<br/>${html}`; html = `<br/>${html}`;
}
const mediaInfoContent = page.querySelector('#mediaInfoContent');
mediaInfoContent.innerHTML = html;
for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) {
btn.addEventListener('click', () => {
const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent;
copy(infoBlock.textContent).then(() => {
toast(globalize.translate('Copied'));
}).catch(() => {
console.error('Could not copy text');
toast(globalize.translate('CopyFailed'));
});
});
}
} }
const mediaInfoContent = page.querySelector('#mediaInfoContent');
mediaInfoContent.innerHTML = html;
function getMediaSourceHtml(user, item, version) { for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) {
let html = '<div class="mediaInfoSource">'; btn.addEventListener('click', () => {
if (version.Name) { const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent;
html += `<div><h2 class="mediaInfoStreamType">${escapeHtml(version.Name)}${copyButtonHtml}</h2></div>\n`;
}
if (version.Container) {
html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}<br/>`;
}
if (version.Formats && version.Formats.length) {
html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}<br/>`;
}
if (version.Path && user && user.Policy.IsAdministrator) {
html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}<br/>`;
}
if (version.Size) {
const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`;
html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}<br/>`;
}
version.MediaStreams.sort(itemHelper.sortTracks);
for (const stream of version.MediaStreams) {
if (stream.Type === 'Data') {
continue;
}
html += '<div class="mediaInfoStream">'; copy(infoBlock.textContent).then(() => {
let translateString; toast(globalize.translate('Copied'));
switch (stream.Type) { }).catch(() => {
case 'Audio': console.error('Could not copy text');
case 'Data': toast(globalize.translate('CopyFailed'));
case 'Subtitle':
case 'Video':
translateString = stream.Type;
break;
case 'EmbeddedImage':
translateString = 'Image';
break;
}
const displayType = globalize.translate(translateString);
html += `\n<h2 class="mediaInfoStreamType">${displayType}${copyButtonHtml}</h2>\n`;
const attributes = [];
if (stream.DisplayTitle) {
attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle));
}
if (stream.Language && stream.Type !== 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language));
}
if (stream.Codec) {
attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase()));
}
if (stream.CodecTag) {
attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag));
}
if (stream.IsAVC != null) {
attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No')));
}
if (stream.Profile) {
attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile));
}
if (stream.Level > 0) {
attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level));
}
if (stream.Width || stream.Height) {
attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`));
}
if (stream.AspectRatio && stream.Codec !== 'mjpeg') {
attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio));
}
if (stream.Type === 'Video') {
if (stream.IsAnamorphic != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No')));
}
attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No')));
}
if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate)));
}
if (stream.ChannelLayout) {
attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout));
}
if (stream.Channels) {
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
}
if (stream.BitRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`));
}
if (stream.SampleRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`));
}
if (stream.BitDepth) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`));
}
if (stream.VideoRange) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange));
}
if (stream.VideoRangeType) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType));
}
if (stream.VideoDoViTitle) {
attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle));
if (stream.DvVersionMajor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor));
}
if (stream.DvVersionMinor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor));
}
if (stream.DvProfile != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile));
}
if (stream.DvLevel != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel));
}
if (stream.RpuPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag));
}
if (stream.ElPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag));
}
if (stream.BlPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag));
}
if (stream.DvBlSignalCompatibilityId != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId));
}
}
if (stream.ColorSpace) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace));
}
if (stream.ColorTransfer) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer));
}
if (stream.ColorPrimaries) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries));
}
if (stream.PixelFormat) {
attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat));
}
if (stream.RefFrames) {
attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames));
}
if (stream.NalLengthSize) {
attributes.push(createAttribute('NAL', stream.NalLengthSize));
}
if (stream.Type === 'Subtitle' || stream.Type === 'Audio') {
attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No')));
attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No')));
attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No')));
}
if (stream.Type === 'Video' && version.Timestamp) {
attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp));
}
html += attributes.join('<br/>');
html += '</div>';
}
html += '</div>';
return html;
}
// File Paths should be always ltr. The isLtr parameter allows this.
function createAttribute(label, value, isLtr) {
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute" ${isLtr && 'dir="ltr"'}>${escapeHtml(value)}</span>\n`;
}
function loadMediaInfo(itemId, serverId) {
const apiClient = ServerConnections.getApiClient(serverId);
return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => {
const dialogOptions = {
size: 'small',
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
if (layoutManager.tv) {
dlg.querySelector('.formDialogContent');
}
dialogHelper.open(dlg);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
}); });
apiClient.getCurrentUser().then(user => {
setMediaInfo(user, dlg, item);
});
loading.hide();
}); });
} }
}
export function show(itemId, serverId) { function getMediaSourceHtml(user, item, version) {
loading.show(); let html = '<div class="mediaInfoSource">';
return loadMediaInfo(itemId, serverId); if (version.Name) {
html += `<div><h2 class="mediaInfoStreamType">${escapeHtml(version.Name)}${copyButtonHtml}</h2></div>\n`;
} }
if (version.Container) {
html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}<br/>`;
}
if (version.Formats && version.Formats.length) {
html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}<br/>`;
}
if (version.Path && user && user.Policy.IsAdministrator) {
html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}<br/>`;
}
if (version.Size) {
const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`;
html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}<br/>`;
}
version.MediaStreams.sort(itemHelper.sortTracks);
for (const stream of version.MediaStreams) {
if (stream.Type === 'Data') {
continue;
}
html += '<div class="mediaInfoStream">';
let translateString;
switch (stream.Type) {
case 'Audio':
case 'Data':
case 'Subtitle':
case 'Video':
translateString = stream.Type;
break;
case 'EmbeddedImage':
translateString = 'Image';
break;
}
const displayType = globalize.translate(translateString);
html += `\n<h2 class="mediaInfoStreamType">${displayType}${copyButtonHtml}</h2>\n`;
const attributes = [];
if (stream.DisplayTitle) {
attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle));
}
if (stream.Language && stream.Type !== 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language));
}
if (stream.Codec) {
attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase()));
}
if (stream.CodecTag) {
attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag));
}
if (stream.IsAVC != null) {
attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No')));
}
if (stream.Profile) {
attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile));
}
if (stream.Level > 0) {
attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level));
}
if (stream.Width || stream.Height) {
attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`));
}
if (stream.AspectRatio && stream.Codec !== 'mjpeg') {
attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio));
}
if (stream.Type === 'Video') {
if (stream.IsAnamorphic != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No')));
}
attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No')));
}
if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate)));
}
if (stream.ChannelLayout) {
attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout));
}
if (stream.Channels) {
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
}
if (stream.BitRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`));
}
if (stream.SampleRate) {
attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`));
}
if (stream.BitDepth) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`));
}
if (stream.VideoRange) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange));
}
if (stream.VideoRangeType) {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType));
}
if (stream.VideoDoViTitle) {
attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle));
if (stream.DvVersionMajor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor));
}
if (stream.DvVersionMinor != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor));
}
if (stream.DvProfile != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile));
}
if (stream.DvLevel != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel));
}
if (stream.RpuPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag));
}
if (stream.ElPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag));
}
if (stream.BlPresentFlag != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag));
}
if (stream.DvBlSignalCompatibilityId != null) {
attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId));
}
}
if (stream.ColorSpace) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace));
}
if (stream.ColorTransfer) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer));
}
if (stream.ColorPrimaries) {
attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries));
}
if (stream.PixelFormat) {
attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat));
}
if (stream.RefFrames) {
attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames));
}
if (stream.NalLengthSize) {
attributes.push(createAttribute('NAL', stream.NalLengthSize));
}
if (stream.Type === 'Subtitle' || stream.Type === 'Audio') {
attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No')));
attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No')));
attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No')));
}
if (stream.Type === 'Video' && version.Timestamp) {
attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp));
}
html += attributes.join('<br/>');
html += '</div>';
}
html += '</div>';
return html;
}
// File Paths should be always ltr. The isLtr parameter allows this.
function createAttribute(label, value, isLtr) {
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute" ${isLtr && 'dir="ltr"'}>${escapeHtml(value)}</span>\n`;
}
function loadMediaInfo(itemId, serverId) {
const apiClient = ServerConnections.getApiClient(serverId);
return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => {
const dialogOptions = {
size: 'small',
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
if (layoutManager.tv) {
dlg.querySelector('.formDialogContent');
}
dialogHelper.open(dlg);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
apiClient.getCurrentUser().then(user => {
setMediaInfo(user, dlg, item);
});
loading.hide();
});
}
export function show(itemId, serverId) {
loading.show();
return loadMediaInfo(itemId, serverId);
}
/* eslint-enable indent */
export default { export default {
show: show show: show
}; };
+378 -380
View File
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for itemidentifier media item. * Module for itemidentifier media item.
@@ -24,388 +23,314 @@ import toast from '../toast/toast';
import template from './itemidentifier.template.html'; import template from './itemidentifier.template.html';
import datetime from '../../scripts/datetime'; import datetime from '../../scripts/datetime';
const enableFocusTransform = !browser.slow && !browser.edge; const enableFocusTransform = !browser.slow && !browser.edge;
let currentItem; let currentItem;
let currentItemType; let currentItemType;
let currentServerId; let currentServerId;
let currentResolve; let currentResolve;
let currentReject; let currentReject;
let hasChanges = false; let hasChanges = false;
let currentSearchResult; let currentSearchResult;
function getApiClient() { function getApiClient() {
return ServerConnections.getApiClient(currentServerId); return ServerConnections.getApiClient(currentServerId);
}
function searchForIdentificationResults(page) {
let lookupInfo = {
ProviderIds: {}
};
let i;
let length;
const identifyField = page.querySelectorAll('.identifyField');
let value;
for (i = 0, length = identifyField.length; i < length; i++) {
value = identifyField[i].value;
if (value) {
if (identifyField[i].type === 'number') {
value = parseInt(value, 10);
}
lookupInfo[identifyField[i].getAttribute('data-lookup')] = value;
}
} }
function searchForIdentificationResults(page) { let hasId = false;
let lookupInfo = {
ProviderIds: {}
};
let i; const txtLookupId = page.querySelectorAll('.txtLookupId');
let length; for (i = 0, length = txtLookupId.length; i < length; i++) {
const identifyField = page.querySelectorAll('.identifyField'); value = txtLookupId[i].value;
let value;
for (i = 0, length = identifyField.length; i < length; i++) {
value = identifyField[i].value;
if (value) { if (value) {
if (identifyField[i].type === 'number') { hasId = true;
value = parseInt(value, 10);
}
lookupInfo[identifyField[i].getAttribute('data-lookup')] = value;
}
} }
lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value;
}
let hasId = false; if (!hasId && !lookupInfo.Name) {
toast(globalize.translate('PleaseEnterNameOrId'));
return;
}
const txtLookupId = page.querySelectorAll('.txtLookupId'); lookupInfo = {
for (i = 0, length = txtLookupId.length; i < length; i++) { SearchInfo: lookupInfo
value = txtLookupId[i].value; };
if (value) { if (currentItem && currentItem.Id) {
hasId = true; lookupInfo.ItemId = currentItem.Id;
} } else {
lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; lookupInfo.IncludeDisabledProviders = true;
} }
if (!hasId && !lookupInfo.Name) { loading.show();
toast(globalize.translate('PleaseEnterNameOrId'));
return;
}
lookupInfo = { const apiClient = getApiClient();
SearchInfo: lookupInfo
};
if (currentItem && currentItem.Id) { apiClient.ajax({
lookupInfo.ItemId = currentItem.Id; type: 'POST',
url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`),
data: JSON.stringify(lookupInfo),
contentType: 'application/json',
dataType: 'json'
}).then(results => {
loading.hide();
showIdentificationSearchResults(page, results);
});
}
function showIdentificationSearchResults(page, results) {
const identificationSearchResults = page.querySelector('.identificationSearchResults');
page.querySelector('.popupIdentifyForm').classList.add('hide');
identificationSearchResults.classList.remove('hide');
page.querySelector('.identifyOptionsForm').classList.add('hide');
page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered');
let html = '';
let i;
let length;
for (i = 0, length = results.length; i < length; i++) {
const result = results[i];
html += getSearchResultHtml(result, i);
}
const elem = page.querySelector('.identificationSearchResultList');
elem.innerHTML = html;
function onSearchImageClick() {
const index = parseInt(this.getAttribute('data-index'), 10);
const currentResult = results[index];
if (currentItem != null) {
showIdentifyOptions(page, currentResult);
} else { } else {
lookupInfo.IncludeDisabledProviders = true; finishFindNewDialog(page, currentResult);
}
loading.show();
const apiClient = getApiClient();
apiClient.ajax({
type: 'POST',
url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`),
data: JSON.stringify(lookupInfo),
contentType: 'application/json',
dataType: 'json'
}).then(results => {
loading.hide();
showIdentificationSearchResults(page, results);
});
}
function showIdentificationSearchResults(page, results) {
const identificationSearchResults = page.querySelector('.identificationSearchResults');
page.querySelector('.popupIdentifyForm').classList.add('hide');
identificationSearchResults.classList.remove('hide');
page.querySelector('.identifyOptionsForm').classList.add('hide');
page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered');
let html = '';
let i;
let length;
for (i = 0, length = results.length; i < length; i++) {
const result = results[i];
html += getSearchResultHtml(result, i);
}
const elem = page.querySelector('.identificationSearchResultList');
elem.innerHTML = html;
function onSearchImageClick() {
const index = parseInt(this.getAttribute('data-index'), 10);
const currentResult = results[index];
if (currentItem != null) {
showIdentifyOptions(page, currentResult);
} else {
finishFindNewDialog(page, currentResult);
}
}
const searchImages = elem.querySelectorAll('.card');
for (i = 0, length = searchImages.length; i < length; i++) {
searchImages[i].addEventListener('click', onSearchImageClick);
}
if (layoutManager.tv) {
focusManager.autoFocus(identificationSearchResults);
} }
} }
function finishFindNewDialog(dlg, identifyResult) { const searchImages = elem.querySelectorAll('.card');
currentSearchResult = identifyResult; for (i = 0, length = searchImages.length; i < length; i++) {
searchImages[i].addEventListener('click', onSearchImageClick);
}
if (layoutManager.tv) {
focusManager.autoFocus(identificationSearchResults);
}
}
function finishFindNewDialog(dlg, identifyResult) {
currentSearchResult = identifyResult;
hasChanges = true;
loading.hide();
dialogHelper.close(dlg);
}
function showIdentifyOptions(page, identifyResult) {
const identifyOptionsForm = page.querySelector('.identifyOptionsForm');
page.querySelector('.popupIdentifyForm').classList.add('hide');
page.querySelector('.identificationSearchResults').classList.add('hide');
identifyOptionsForm.classList.remove('hide');
page.querySelector('#chkIdentifyReplaceImages').checked = true;
page.querySelector('.dialogContentInner').classList.add('dialog-content-centered');
currentSearchResult = identifyResult;
const lines = [];
lines.push(escapeHtml(identifyResult.Name));
if (identifyResult.ProductionYear) {
lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false }));
}
let resultHtml = lines.join('<br/>');
if (identifyResult.ImageUrl) {
resultHtml = `<div style="display:flex;align-items:center;"><img src="${identifyResult.ImageUrl}" style="max-height:240px;" /><div style="margin-left:1em;">${resultHtml}</div>`;
}
page.querySelector('.selectedSearchResult').innerHTML = resultHtml;
focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit'));
}
function getSearchResultHtml(result, index) {
// TODO move card creation code to Card component
let html = '';
let cssClass = 'card scalableCard';
let cardBoxCssClass = 'cardBox';
let padderClass;
if (currentItemType === 'Episode') {
cssClass += ' backdropCard backdropCard-scalable';
padderClass = 'cardPadder-backdrop';
} else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') {
cssClass += ' squareCard squareCard-scalable';
padderClass = 'cardPadder-square';
} else {
cssClass += ' portraitCard portraitCard-scalable';
padderClass = 'cardPadder-portrait';
}
if (layoutManager.tv) {
cssClass += ' show-focus';
if (enableFocusTransform) {
cssClass += ' show-animation';
}
}
cardBoxCssClass += ' cardBox-bottompadded';
html += `<button type="button" class="${cssClass}" data-index="${index}">`;
html += `<div class="${cardBoxCssClass}">`;
html += '<div class="cardScalable">';
html += `<div class="${padderClass}"></div>`;
html += '<div class="cardContent searchImage">';
if (result.ImageUrl) {
html += `<div class="cardImageContainer coveredImage" style="background-image:url('${result.ImageUrl}');"></div>`;
} else {
html += `<div class="cardImageContainer coveredImage defaultCardBackground defaultCardBackground1"><div class="cardText cardCenteredText">${escapeHtml(result.Name)}</div></div>`;
}
html += '</div>';
html += '</div>';
let numLines = 3;
if (currentItemType === 'MusicAlbum') {
numLines++;
}
const lines = [result.Name];
lines.push(result.SearchProviderName);
if (result.AlbumArtist) {
lines.push(result.AlbumArtist.Name);
}
if (result.ProductionYear) {
lines.push(result.ProductionYear);
}
for (let i = 0; i < numLines; i++) {
if (i === 0) {
html += '<div class="cardText cardText-first cardTextCentered">';
} else {
html += '<div class="cardText cardText-secondary cardTextCentered">';
}
html += escapeHtml(lines[i] || '') || '&nbsp;';
html += '</div>';
}
html += '</div>';
html += '</button>';
return html;
}
function submitIdentficationResult(page) {
loading.show();
const options = {
ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked
};
const apiClient = getApiClient();
apiClient.ajax({
type: 'POST',
url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options),
data: JSON.stringify(currentSearchResult),
contentType: 'application/json'
}).then(() => {
hasChanges = true; hasChanges = true;
loading.hide(); loading.hide();
dialogHelper.close(dlg); dialogHelper.close(page);
} }, () => {
loading.hide();
function showIdentifyOptions(page, identifyResult) { dialogHelper.close(page);
const identifyOptionsForm = page.querySelector('.identifyOptionsForm'); });
}
page.querySelector('.popupIdentifyForm').classList.add('hide'); function showIdentificationForm(page, item) {
page.querySelector('.identificationSearchResults').classList.add('hide'); const apiClient = getApiClient();
identifyOptionsForm.classList.remove('hide');
page.querySelector('#chkIdentifyReplaceImages').checked = true;
page.querySelector('.dialogContentInner').classList.add('dialog-content-centered');
currentSearchResult = identifyResult;
const lines = [];
lines.push(escapeHtml(identifyResult.Name));
if (identifyResult.ProductionYear) {
lines.push(datetime.toLocaleString(identifyResult.ProductionYear, {useGrouping: false}));
}
let resultHtml = lines.join('<br/>');
if (identifyResult.ImageUrl) {
resultHtml = `<div style="display:flex;align-items:center;"><img src="${identifyResult.ImageUrl}" style="max-height:240px;" /><div style="margin-left:1em;">${resultHtml}</div>`;
}
page.querySelector('.selectedSearchResult').innerHTML = resultHtml;
focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit'));
}
function getSearchResultHtml(result, index) {
// TODO move card creation code to Card component
apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => {
let html = ''; let html = '';
let cssClass = 'card scalableCard';
let cardBoxCssClass = 'cardBox';
let padderClass;
if (currentItemType === 'Episode') { for (let i = 0, length = idList.length; i < length; i++) {
cssClass += ' backdropCard backdropCard-scalable'; const idInfo = idList[i];
padderClass = 'cardPadder-backdrop';
} else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') {
cssClass += ' squareCard squareCard-scalable';
padderClass = 'cardPadder-square';
} else {
cssClass += ' portraitCard portraitCard-scalable';
padderClass = 'cardPadder-portrait';
}
if (layoutManager.tv) { const id = `txtLookup${idInfo.Key}`;
cssClass += ' show-focus';
if (enableFocusTransform) { html += '<div class="inputContainer">';
cssClass += ' show-animation';
let fullName = idInfo.Name;
if (idInfo.Type) {
fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`;
} }
}
cardBoxCssClass += ' cardBox-bottompadded'; const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName));
html += `<button type="button" class="${cssClass}" data-index="${index}">`; html += `<input is="emby-input" class="txtLookupId" data-providerkey="${idInfo.Key}" id="${id}" label="${idLabel}"/>`;
html += `<div class="${cardBoxCssClass}">`;
html += '<div class="cardScalable">';
html += `<div class="${padderClass}"></div>`;
html += '<div class="cardContent searchImage">';
if (result.ImageUrl) {
html += `<div class="cardImageContainer coveredImage" style="background-image:url('${result.ImageUrl}');"></div>`;
} else {
html += `<div class="cardImageContainer coveredImage defaultCardBackground defaultCardBackground1"><div class="cardText cardCenteredText">${escapeHtml(result.Name)}</div></div>`;
}
html += '</div>';
html += '</div>';
let numLines = 3;
if (currentItemType === 'MusicAlbum') {
numLines++;
}
const lines = [result.Name];
lines.push(result.SearchProviderName);
if (result.AlbumArtist) {
lines.push(result.AlbumArtist.Name);
}
if (result.ProductionYear) {
lines.push(result.ProductionYear);
}
for (let i = 0; i < numLines; i++) {
if (i === 0) {
html += '<div class="cardText cardText-first cardTextCentered">';
} else {
html += '<div class="cardText cardText-secondary cardTextCentered">';
}
html += escapeHtml(lines[i] || '') || '&nbsp;';
html += '</div>'; html += '</div>';
} }
html += '</div>'; page.querySelector('#txtLookupName').value = '';
html += '</button>';
return html;
}
function submitIdentficationResult(page) { if (item.Type === 'Person' || item.Type === 'BoxSet') {
loading.show(); page.querySelector('.fldLookupYear').classList.add('hide');
page.querySelector('#txtLookupYear').value = '';
const options = {
ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked
};
const apiClient = getApiClient();
apiClient.ajax({
type: 'POST',
url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options),
data: JSON.stringify(currentSearchResult),
contentType: 'application/json'
}).then(() => {
hasChanges = true;
loading.hide();
dialogHelper.close(page);
}, () => {
loading.hide();
dialogHelper.close(page);
});
}
function showIdentificationForm(page, item) {
const apiClient = getApiClient();
apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => {
let html = '';
for (let i = 0, length = idList.length; i < length; i++) {
const idInfo = idList[i];
const id = `txtLookup${idInfo.Key}`;
html += '<div class="inputContainer">';
let fullName = idInfo.Name;
if (idInfo.Type) {
fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`;
}
const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName));
html += `<input is="emby-input" class="txtLookupId" data-providerkey="${idInfo.Key}" id="${id}" label="${idLabel}"/>`;
html += '</div>';
}
page.querySelector('#txtLookupName').value = '';
if (item.Type === 'Person' || item.Type === 'BoxSet') {
page.querySelector('.fldLookupYear').classList.add('hide');
page.querySelector('#txtLookupYear').value = '';
} else {
page.querySelector('.fldLookupYear').classList.remove('hide');
page.querySelector('#txtLookupYear').value = '';
}
page.querySelector('.identifyProviderIds').innerHTML = html;
page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify');
});
}
function showEditor(itemId) {
loading.show();
const apiClient = getApiClient();
apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => {
currentItem = item;
currentItemType = currentItem.Type;
const dialogOptions = {
size: 'small',
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.classList.add('recordingDialog');
let html = '';
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', onDialogClosed);
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
}
if (item.Path) {
dlg.querySelector('.fldPath').classList.remove('hide');
} else {
dlg.querySelector('.fldPath').classList.add('hide');
}
dlg.querySelector('.txtPath').innerText = item.Path || '';
dialogHelper.open(dlg);
dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => {
e.preventDefault();
searchForIdentificationResults(dlg);
return false;
});
dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => {
e.preventDefault();
submitIdentficationResult(dlg);
return false;
});
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
dlg.classList.add('identifyDialog');
showIdentificationForm(dlg, item);
loading.hide();
});
}
function onDialogClosed() {
loading.hide();
if (hasChanges) {
currentResolve();
} else { } else {
currentReject(); page.querySelector('.fldLookupYear').classList.remove('hide');
page.querySelector('#txtLookupYear').value = '';
} }
}
// TODO investigate where this was used page.querySelector('.identifyProviderIds').innerHTML = html;
function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) {
currentItem = null; page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify');
currentItemType = itemType; });
}
function showEditor(itemId) {
loading.show();
const apiClient = getApiClient();
apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => {
currentItem = item;
currentItemType = currentItem.Type;
const dialogOptions = { const dialogOptions = {
size: 'small', size: 'small',
@@ -427,15 +352,22 @@ import datetime from '../../scripts/datetime';
dlg.innerHTML = html; dlg.innerHTML = html;
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', onDialogClosed);
if (layoutManager.tv) { if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
} }
dialogHelper.open(dlg); if (item.Path) {
dlg.querySelector('.fldPath').classList.remove('hide');
} else {
dlg.querySelector('.fldPath').classList.add('hide');
}
dlg.querySelector('.btnCancel').addEventListener('click', () => { dlg.querySelector('.txtPath').innerText = item.Path || '';
dialogHelper.close(dlg);
}); dialogHelper.open(dlg);
dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => {
e.preventDefault(); e.preventDefault();
@@ -443,53 +375,119 @@ import datetime from '../../scripts/datetime';
return false; return false;
}); });
dlg.addEventListener('close', () => { dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => {
loading.hide(); e.preventDefault();
const foundItem = hasChanges ? currentSearchResult : null; submitIdentficationResult(dlg);
return false;
});
resolveFunc(foundItem); dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
}); });
dlg.classList.add('identifyDialog'); dlg.classList.add('identifyDialog');
showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); showIdentificationForm(dlg, item);
loading.hide();
});
}
function onDialogClosed() {
loading.hide();
if (hasChanges) {
currentResolve();
} else {
currentReject();
}
}
// TODO investigate where this was used
function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) {
currentItem = null;
currentItemType = itemType;
const dialogOptions = {
size: 'small',
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} }
function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { const dlg = dialogHelper.createDialog(dialogOptions);
dlg.querySelector('#txtLookupName').value = itemName;
if (itemType === 'Person' || itemType === 'BoxSet') { dlg.classList.add('formDialog');
dlg.querySelector('.fldLookupYear').classList.add('hide'); dlg.classList.add('recordingDialog');
dlg.querySelector('#txtLookupYear').value = '';
} else {
dlg.querySelector('.fldLookupYear').classList.remove('hide');
dlg.querySelector('#txtLookupYear').value = itemYear;
}
dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); let html = '';
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
} }
export function show(itemId, serverId) { dialogHelper.open(dlg);
return new Promise((resolve, reject) => {
currentResolve = resolve;
currentReject = reject;
currentServerId = serverId;
hasChanges = false;
showEditor(itemId); dlg.querySelector('.btnCancel').addEventListener('click', () => {
}); dialogHelper.close(dlg);
});
dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => {
e.preventDefault();
searchForIdentificationResults(dlg);
return false;
});
dlg.addEventListener('close', () => {
loading.hide();
const foundItem = hasChanges ? currentSearchResult : null;
resolveFunc(foundItem);
});
dlg.classList.add('identifyDialog');
showIdentificationFormFindNew(dlg, itemName, itemYear, itemType);
}
function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) {
dlg.querySelector('#txtLookupName').value = itemName;
if (itemType === 'Person' || itemType === 'BoxSet') {
dlg.querySelector('.fldLookupYear').classList.add('hide');
dlg.querySelector('#txtLookupYear').value = '';
} else {
dlg.querySelector('.fldLookupYear').classList.remove('hide');
dlg.querySelector('#txtLookupYear').value = itemYear;
} }
export function showFindNew(itemName, itemYear, itemType, serverId) { dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search');
return new Promise((resolve) => { }
currentServerId = serverId;
hasChanges = false; export function show(itemId, serverId) {
showEditorFindNew(itemName, itemYear, itemType, resolve); return new Promise((resolve, reject) => {
}); currentResolve = resolve;
} currentReject = reject;
currentServerId = serverId;
hasChanges = false;
showEditor(itemId);
});
}
export function showFindNew(itemName, itemYear, itemType, serverId) {
return new Promise((resolve) => {
currentServerId = serverId;
hasChanges = false;
showEditorFindNew(itemName, itemYear, itemType, resolve);
});
}
/* eslint-enable indent */
export default { export default {
show: show, show: show,
showFindNew: showFindNew showFindNew: showFindNew
@@ -1,69 +1,68 @@
/* eslint-disable indent */
export class LazyLoader {
constructor(options) {
this.options = options;
}
createObserver() { export class LazyLoader {
const callback = this.options.callback; constructor(options) {
this.options = options;
}
const observer = new IntersectionObserver( createObserver() {
(entries) => { const callback = this.options.callback;
entries.forEach(entry => {
callback(entry); const observer = new IntersectionObserver(
}); (entries) => {
}, entries.forEach(entry => {
{ callback(entry);
rootMargin: '50%',
threshold: 0
}); });
},
this.observer = observer; {
} rootMargin: '50%',
threshold: 0
addElements(elements) {
let observer = this.observer;
if (!observer) {
this.createObserver();
observer = this.observer;
}
Array.from(elements).forEach(element => {
observer.observe(element);
}); });
}
destroyObserver() { this.observer = observer;
const observer = this.observer;
if (observer) {
observer.disconnect();
this.observer = null;
}
}
destroy() {
this.destroyObserver();
this.options = null;
}
} }
function unveilElements(elements, root, callback) { addElements(elements) {
if (!elements.length) { let observer = this.observer;
return;
if (!observer) {
this.createObserver();
observer = this.observer;
} }
const lazyLoader = new LazyLoader({
callback: callback Array.from(elements).forEach(element => {
observer.observe(element);
}); });
lazyLoader.addElements(elements);
} }
export function lazyChildren(elem, callback) { destroyObserver() {
unveilElements(elem.getElementsByClassName('lazy'), elem, callback); const observer = this.observer;
if (observer) {
observer.disconnect();
this.observer = null;
}
} }
/* eslint-enable indent */ destroy() {
this.destroyObserver();
this.options = null;
}
}
function unveilElements(elements, root, callback) {
if (!elements.length) {
return;
}
const lazyLoader = new LazyLoader({
callback: callback
});
lazyLoader.addElements(elements);
}
export function lazyChildren(elem, callback) {
unveilElements(elem.getElementsByClassName('lazy'), elem, callback);
}
export default { export default {
LazyLoader: LazyLoader, LazyLoader: LazyLoader,
lazyChildren: lazyChildren lazyChildren: lazyChildren
File diff suppressed because it is too large Load Diff
+401 -403
View File
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for display list view. * Module for display list view.
@@ -18,484 +17,483 @@ import '../../elements/emby-ratingbutton/emby-ratingbutton';
import '../../elements/emby-playstatebutton/emby-playstatebutton'; import '../../elements/emby-playstatebutton/emby-playstatebutton';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
function getIndex(item, options) { function getIndex(item, options) {
if (options.index === 'disc') { if (options.index === 'disc') {
return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber);
}
const sortBy = (options.sortBy || '').toLowerCase();
let code;
let name;
if (sortBy.indexOf('sortname') === 0) {
if (item.Type === 'Episode') {
return '';
}
// SortName
name = (item.SortName || item.Name || '?')[0].toUpperCase();
code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
}
return name.toUpperCase();
}
if (sortBy.indexOf('officialrating') === 0) {
return item.OfficialRating || globalize.translate('Unrated');
}
if (sortBy.indexOf('communityrating') === 0) {
if (item.CommunityRating == null) {
return globalize.translate('Unrated');
}
return Math.floor(item.CommunityRating);
}
if (sortBy.indexOf('criticrating') === 0) {
if (item.CriticRating == null) {
return globalize.translate('Unrated');
}
return Math.floor(item.CriticRating);
}
if (sortBy.indexOf('albumartist') === 0) {
// SortName
if (!item.AlbumArtist) {
return '';
}
name = item.AlbumArtist[0].toUpperCase();
code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
}
return name.toUpperCase();
}
return '';
} }
function getImageUrl(item, size) { const sortBy = (options.sortBy || '').toLowerCase();
const apiClient = ServerConnections.getApiClient(item.ServerId); let code;
let itemId; let name;
const options = { if (sortBy.indexOf('sortname') === 0) {
fillWidth: size, if (item.Type === 'Episode') {
fillHeight: size, return '';
type: 'Primary'
};
if (item.ImageTags && item.ImageTags.Primary) {
options.tag = item.ImageTags.Primary;
itemId = item.Id;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
options.tag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.ParentPrimaryImageTag) {
options.tag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
} }
if (itemId) { // SortName
return apiClient.getScaledImageUrl(itemId, options); name = (item.SortName || item.Name || '?')[0].toUpperCase();
code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
} }
return null;
return name.toUpperCase();
}
if (sortBy.indexOf('officialrating') === 0) {
return item.OfficialRating || globalize.translate('Unrated');
}
if (sortBy.indexOf('communityrating') === 0) {
if (item.CommunityRating == null) {
return globalize.translate('Unrated');
}
return Math.floor(item.CommunityRating);
}
if (sortBy.indexOf('criticrating') === 0) {
if (item.CriticRating == null) {
return globalize.translate('Unrated');
}
return Math.floor(item.CriticRating);
}
if (sortBy.indexOf('albumartist') === 0) {
// SortName
if (!item.AlbumArtist) {
return '';
}
name = item.AlbumArtist[0].toUpperCase();
code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
}
return name.toUpperCase();
}
return '';
}
function getImageUrl(item, size) {
const apiClient = ServerConnections.getApiClient(item.ServerId);
let itemId;
const options = {
fillWidth: size,
fillHeight: size,
type: 'Primary'
};
if (item.ImageTags && item.ImageTags.Primary) {
options.tag = item.ImageTags.Primary;
itemId = item.Id;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
options.tag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.ParentPrimaryImageTag) {
options.tag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
} }
function getChannelImageUrl(item, size) { if (itemId) {
const apiClient = ServerConnections.getApiClient(item.ServerId); return apiClient.getScaledImageUrl(itemId, options);
const options = { }
fillWidth: size, return null;
fillHeight: size, }
type: 'Primary'
};
if (item.ChannelId && item.ChannelPrimaryImageTag) { function getChannelImageUrl(item, size) {
options.tag = item.ChannelPrimaryImageTag; const apiClient = ServerConnections.getApiClient(item.ServerId);
} const options = {
fillWidth: size,
fillHeight: size,
type: 'Primary'
};
if (item.ChannelId) { if (item.ChannelId && item.ChannelPrimaryImageTag) {
return apiClient.getScaledImageUrl(item.ChannelId, options); options.tag = item.ChannelPrimaryImageTag;
}
} }
function getTextLinesHtml(textlines, isLargeStyle) { if (item.ChannelId) {
let html = ''; return apiClient.getScaledImageUrl(item.ChannelId, options);
}
}
const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; function getTextLinesHtml(textlines, isLargeStyle) {
let html = '';
for (const [i, text] of textlines.entries()) { const largeTitleTagName = layoutManager.tv ? 'h2' : 'div';
if (!text) {
continue;
}
let elem; for (const [i, text] of textlines.entries()) {
if (!text) {
continue;
}
if (i === 0) { let elem;
if (isLargeStyle) {
elem = document.createElement(largeTitleTagName); if (i === 0) {
} else { if (isLargeStyle) {
elem = document.createElement('div'); elem = document.createElement(largeTitleTagName);
}
} else { } else {
elem = document.createElement('div'); elem = document.createElement('div');
elem.classList.add('secondary');
} }
} else {
elem.classList.add('listItemBodyText'); elem = document.createElement('div');
elem.classList.add('secondary');
elem.innerHTML = '<bdi>' + escapeHtml(text) + '</bdi>';
html += elem.outerHTML;
} }
return html; elem.classList.add('listItemBodyText');
elem.innerHTML = '<bdi>' + escapeHtml(text) + '</bdi>';
html += elem.outerHTML;
} }
function getRightButtonsHtml(options) { return html;
}
function getRightButtonsHtml(options) {
let html = '';
for (let i = 0, length = options.rightButtons.length; i < length; i++) {
const button = options.rightButtons[i];
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="custom" data-customaction="${button.id}" title="${button.title}"><span class="material-icons ${button.icon}" aria-hidden="true"></span></button>`;
}
return html;
}
export function getListViewHtml(options) {
const items = options.items;
let groupTitle = '';
const action = options.action || 'link';
const isLargeStyle = options.imageSize === 'large';
const enableOverview = options.enableOverview;
const clickEntireItem = layoutManager.tv ? true : false;
const outerTagName = clickEntireItem ? 'button' : 'div';
const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true;
let outerHtml = '';
const enableContentWrapper = options.enableOverview && !layoutManager.tv;
for (let i = 0, length = items.length; i < length; i++) {
const item = items[i];
let html = ''; let html = '';
for (let i = 0, length = options.rightButtons.length; i < length; i++) { if (options.showIndex) {
const button = options.rightButtons[i]; const itemGroupTitle = getIndex(item, options);
html += `<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="custom" data-customaction="${button.id}" title="${button.title}"><span class="material-icons ${button.icon}" aria-hidden="true"></span></button>`; if (itemGroupTitle !== groupTitle) {
if (html) {
html += '</div>';
}
if (i === 0) {
html += '<h2 class="listGroupHeader listGroupHeader-first">';
} else {
html += '<h2 class="listGroupHeader">';
}
html += escapeHtml(itemGroupTitle);
html += '</h2>';
html += '<div>';
groupTitle = itemGroupTitle;
}
} }
return html; let cssClass = 'listItem';
}
export function getListViewHtml(options) { if (options.border || (options.highlight !== false && !layoutManager.tv)) {
const items = options.items; cssClass += ' listItem-border';
}
let groupTitle = ''; if (clickEntireItem) {
const action = options.action || 'link'; cssClass += ' itemAction listItem-button';
}
const isLargeStyle = options.imageSize === 'large'; if (layoutManager.tv) {
const enableOverview = options.enableOverview; cssClass += ' listItem-focusscale';
}
const clickEntireItem = layoutManager.tv ? true : false; let downloadWidth = 80;
const outerTagName = clickEntireItem ? 'button' : 'div';
const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true;
let outerHtml = ''; if (isLargeStyle) {
cssClass += ' listItem-largeImage';
downloadWidth = 500;
}
const enableContentWrapper = options.enableOverview && !layoutManager.tv; const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : '';
for (let i = 0, length = items.length; i < length; i++) { const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : '';
const item = items[i]; const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : '';
const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : '';
const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : '';
const collectionTypeData = item.CollectionType ? (` data-collectiontype="${item.CollectionType}"`) : '';
const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : '';
let html = ''; if (enableContentWrapper) {
cssClass += ' listItem-withContentWrapper';
}
if (options.showIndex) { html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`;
const itemGroupTitle = getIndex(item, options);
if (itemGroupTitle !== groupTitle) { if (enableContentWrapper) {
if (html) { html += '<div class="listItem-content">';
html += '</div>'; }
}
if (i === 0) { if (!clickEntireItem && options.dragHandle) {
html += '<h2 class="listGroupHeader listGroupHeader-first">'; html += '<span class="listViewDragHandle material-icons listItemIcon listItemIcon-transparent drag_handle" aria-hidden="true"></span>';
} else { }
html += '<h2 class="listGroupHeader">';
}
html += escapeHtml(itemGroupTitle);
html += '</h2>';
html += '<div>'; if (options.image !== false) {
const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
groupTitle = itemGroupTitle; if (options.imageSource === 'channel') {
} imageClass += ' listItemImage-channel';
} }
let cssClass = 'listItem'; if (isLargeStyle && layoutManager.tv) {
imageClass += ' listItemImage-large-tv';
if (options.border || (options.highlight !== false && !layoutManager.tv)) {
cssClass += ' listItem-border';
} }
if (clickEntireItem) { const playOnImageClick = options.imagePlayButton && !layoutManager.tv;
cssClass += ' itemAction listItem-button';
if (!clickEntireItem) {
imageClass += ' itemAction';
} }
if (layoutManager.tv) { const imageAction = playOnImageClick ? 'link' : action;
cssClass += ' listItem-focusscale';
if (imgUrl) {
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" item-icon>';
} else {
html += '<div class="' + imageClass + ' cardImageContainer ' + cardBuilder.getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
} }
let downloadWidth = 80; const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1 && options.disableIndicators !== true) {
if (isLargeStyle) { html += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
cssClass += ' listItem-largeImage';
downloadWidth = 500;
} }
const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; let indicatorsHtml = '';
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; if (indicatorsHtml) {
const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : ''; html += `<div class="indicators listItemIndicators">${indicatorsHtml}</div>`;
const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : '';
const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : '';
const collectionTypeData = item.CollectionType ? (` data-collectiontype="${item.CollectionType}"`) : '';
const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : '';
if (enableContentWrapper) {
cssClass += ' listItem-withContentWrapper';
} }
html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`; if (playOnImageClick) {
html += '<button is="paper-icon-button-light" class="listItemImageButton itemAction" data-action="resume"><span class="material-icons listItemImageButton-icon play_arrow" aria-hidden="true"></span></button>';
if (enableContentWrapper) {
html += '<div class="listItem-content">';
} }
if (!clickEntireItem && options.dragHandle) { const progressHtml = indicators.getProgressBarHtml(item, {
html += '<span class="listViewDragHandle material-icons listItemIcon listItemIcon-transparent drag_handle" aria-hidden="true"></span>'; containerClass: 'listItemProgressBar'
}
if (options.image !== false) {
const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth);
let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage';
if (options.imageSource === 'channel') {
imageClass += ' listItemImage-channel';
}
if (isLargeStyle && layoutManager.tv) {
imageClass += ' listItemImage-large-tv';
}
const playOnImageClick = options.imagePlayButton && !layoutManager.tv;
if (!clickEntireItem) {
imageClass += ' itemAction';
}
const imageAction = playOnImageClick ? 'link' : action;
if (imgUrl) {
html += '<div data-action="' + imageAction + '" class="' + imageClass + ' lazy" data-src="' + imgUrl + '" item-icon>';
} else {
html += '<div class="' + imageClass + ' cardImageContainer ' + cardBuilder.getDefaultBackgroundClass(item.Name) + '">' + cardBuilder.getDefaultText(item, options);
}
const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1 && options.disableIndicators !== true) {
html += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
}
let indicatorsHtml = '';
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
if (indicatorsHtml) {
html += `<div class="indicators listItemIndicators">${indicatorsHtml}</div>`;
}
if (playOnImageClick) {
html += '<button is="paper-icon-button-light" class="listItemImageButton itemAction" data-action="resume"><span class="material-icons listItemImageButton-icon play_arrow" aria-hidden="true"></span></button>';
}
const progressHtml = indicators.getProgressBarHtml(item, {
containerClass: 'listItemProgressBar'
});
if (progressHtml) {
html += progressHtml;
}
html += '</div>';
}
if (options.showIndexNumberLeft) {
html += '<div class="listItem-indexnumberleft">';
html += (item.IndexNumber || '&nbsp;');
html += '</div>';
}
const textlines = [];
if (options.showProgramDateTime) {
textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), {
weekday: 'long',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
}));
}
if (options.showProgramTime) {
textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate)));
}
if (options.showChannel && item.ChannelName) {
textlines.push(item.ChannelName);
}
let parentTitle = null;
if (options.showParentTitle) {
if (item.Type === 'Episode') {
parentTitle = item.SeriesName;
} else if (item.IsSeries || (item.EpisodeTitle && item.Name)) {
parentTitle = item.Name;
}
}
let displayName = itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle
}); });
if (options.showIndexNumber && item.IndexNumber != null) { if (progressHtml) {
displayName = `${item.IndexNumber}. ${displayName}`; html += progressHtml;
}
html += '</div>';
}
if (options.showIndexNumberLeft) {
html += '<div class="listItem-indexnumberleft">';
html += (item.IndexNumber || '&nbsp;');
html += '</div>';
}
const textlines = [];
if (options.showProgramDateTime) {
textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), {
weekday: 'long',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
}));
}
if (options.showProgramTime) {
textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate)));
}
if (options.showChannel && item.ChannelName) {
textlines.push(item.ChannelName);
}
let parentTitle = null;
if (options.showParentTitle) {
if (item.Type === 'Episode') {
parentTitle = item.SeriesName;
} else if (item.IsSeries || (item.EpisodeTitle && item.Name)) {
parentTitle = item.Name;
}
}
let displayName = itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle
});
if (options.showIndexNumber && item.IndexNumber != null) {
displayName = `${item.IndexNumber}. ${displayName}`;
}
if (options.showParentTitle && options.parentTitleWithTitle) {
if (displayName) {
if (parentTitle) {
parentTitle += ' - ';
}
parentTitle = (parentTitle || '') + displayName;
} }
if (options.showParentTitle && options.parentTitleWithTitle) { textlines.push(parentTitle || '');
if (displayName) { } else if (options.showParentTitle) {
if (parentTitle) { textlines.push(parentTitle || '');
parentTitle += ' - '; }
}
parentTitle = (parentTitle || '') + displayName; if (displayName && !options.parentTitleWithTitle) {
textlines.push(displayName);
}
if (item.IsFolder) {
if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') {
textlines.push(item.AlbumArtist);
}
} else {
if (options.artist) {
const artistItems = item.ArtistItems;
if (artistItems && item.Type !== 'MusicAlbum') {
textlines.push(artistItems.map(a => {
return a.Name;
}).join(', '));
}
}
}
if (item.Type === 'TvChannel' && item.CurrentProgram) {
textlines.push(itemHelper.getDisplayName(item.CurrentProgram));
}
cssClass = 'listItemBody';
if (!clickEntireItem) {
cssClass += ' itemAction';
}
if (options.image === false) {
cssClass += ' listItemBody-noleftpadding';
}
html += `<div class="${cssClass}">`;
html += getTextLinesHtml(textlines, isLargeStyle);
if (options.mediaInfo !== false && !enableSideMediaInfo) {
const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText';
html += `<div class="${mediaInfoClass}">`;
html += mediaInfo.getPrimaryMediaInfoHtml(item, {
episodeTitle: false,
originalAirDate: false,
subtitles: false
});
html += '</div>';
}
if (enableOverview && item.Overview) {
html += '<div class="secondary listItem-overview listItemBodyText">';
html += '<bdi>' + item.Overview + '</bdi>';
html += '</div>';
}
html += '</div>';
if (options.mediaInfo !== false && enableSideMediaInfo) {
html += '<div class="secondary listItemMediaInfo">';
html += mediaInfo.getPrimaryMediaInfoHtml(item, {
year: false,
container: false,
episodeTitle: false,
criticRating: false,
officialRating: false,
endsAt: false
});
html += '</div>';
}
if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) {
html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside');
}
html += '<div class="listViewUserDataButtons">';
if (!clickEntireItem) {
if (options.addToListButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="addtoplaylist"><span class="material-icons playlist_add" aria-hidden="true"></span></button>';
}
if (options.infoButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline" aria-hidden="true"></span></button>';
}
if (options.rightButtons) {
html += getRightButtonsHtml(options);
}
if (options.enableUserDataButtons !== false) {
const userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes;
if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) {
html += '<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons check" aria-hidden="true"></span></button>';
} }
textlines.push(parentTitle || ''); if (itemHelper.canRate(item) && options.enableRatingButton !== false) {
} else if (options.showParentTitle) { html += '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
textlines.push(parentTitle || '');
}
if (displayName && !options.parentTitleWithTitle) {
textlines.push(displayName);
}
if (item.IsFolder) {
if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') {
textlines.push(item.AlbumArtist);
}
} else {
if (options.artist) {
const artistItems = item.ArtistItems;
if (artistItems && item.Type !== 'MusicAlbum') {
textlines.push(artistItems.map(a => {
return a.Name;
}).join(', '));
}
} }
} }
if (item.Type === 'TvChannel' && item.CurrentProgram) { if (options.moreButton !== false) {
textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
} }
}
html += '</div>';
cssClass = 'listItemBody'; if (enableContentWrapper) {
if (!clickEntireItem) { html += '</div>';
cssClass += ' itemAction';
}
if (options.image === false) {
cssClass += ' listItemBody-noleftpadding';
}
html += `<div class="${cssClass}">`;
html += getTextLinesHtml(textlines, isLargeStyle);
if (options.mediaInfo !== false && !enableSideMediaInfo) {
const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText';
html += `<div class="${mediaInfoClass}">`;
html += mediaInfo.getPrimaryMediaInfoHtml(item, {
episodeTitle: false,
originalAirDate: false,
subtitles: false
});
html += '</div>';
}
if (enableOverview && item.Overview) { if (enableOverview && item.Overview) {
html += '<div class="secondary listItem-overview listItemBodyText">'; html += '<div class="listItem-bottomoverview secondary">';
html += '<bdi>' + item.Overview + '</bdi>'; html += '<bdi>' + item.Overview + '</bdi>';
html += '</div>'; html += '</div>';
} }
html += '</div>';
if (options.mediaInfo !== false && enableSideMediaInfo) {
html += '<div class="secondary listItemMediaInfo">';
html += mediaInfo.getPrimaryMediaInfoHtml(item, {
year: false,
container: false,
episodeTitle: false,
criticRating: false,
officialRating: false,
endsAt: false
});
html += '</div>';
}
if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) {
html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside');
}
html += '<div class="listViewUserDataButtons">';
if (!clickEntireItem) {
if (options.addToListButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="addtoplaylist"><span class="material-icons playlist_add" aria-hidden="true"></span></button>';
}
if (options.infoButton) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="link"><span class="material-icons info_outline" aria-hidden="true"></span></button>';
}
if (options.rightButtons) {
html += getRightButtonsHtml(options);
}
if (options.enableUserDataButtons !== false) {
const userData = item.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes;
if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) {
html += '<button is="emby-playstatebutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons check" aria-hidden="true"></span></button>';
}
if (itemHelper.canRate(item) && options.enableRatingButton !== false) {
html += '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
}
}
if (options.moreButton !== false) {
html += '<button is="paper-icon-button-light" class="listItemButton itemAction" data-action="menu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
}
}
html += '</div>';
if (enableContentWrapper) {
html += '</div>';
if (enableOverview && item.Overview) {
html += '<div class="listItem-bottomoverview secondary">';
html += '<bdi>' + item.Overview + '</bdi>';
html += '</div>';
}
}
html += `</${outerTagName}>`;
outerHtml += html;
} }
return outerHtml; html += `</${outerTagName}>`;
outerHtml += html;
} }
/* eslint-enable indent */ return outerHtml;
}
export default { export default {
getListViewHtml: getListViewHtml getListViewHtml: getListViewHtml
}; };
+157 -161
View File
@@ -4,200 +4,196 @@ import Events from '../utils/events.ts';
import '../elements/emby-tabs/emby-tabs'; import '../elements/emby-tabs/emby-tabs';
import '../elements/emby-button/emby-button'; import '../elements/emby-button/emby-button';
/* eslint-disable indent */ let tabOwnerView;
const queryScope = document.querySelector('.skinHeader');
let headerTabsContainer;
let tabsElem;
let tabOwnerView; function ensureElements() {
const queryScope = document.querySelector('.skinHeader'); if (!headerTabsContainer) {
let headerTabsContainer; headerTabsContainer = queryScope.querySelector('.headerTabs');
let tabsElem;
function ensureElements() {
if (!headerTabsContainer) {
headerTabsContainer = queryScope.querySelector('.headerTabs');
}
} }
}
function onViewTabsReady() { function onViewTabsReady() {
this.selectedIndex(this.readySelectedIndex); this.selectedIndex(this.readySelectedIndex);
this.readySelectedIndex = null; this.readySelectedIndex = null;
} }
function allowSwipe(target) { function allowSwipe(target) {
function allowSwipeOn(elem) { function allowSwipeOn(elem) {
if (dom.parentWithTag(elem, 'input')) { if (dom.parentWithTag(elem, 'input')) {
return false; return false;
}
const classList = elem.classList;
if (classList) {
return !classList.contains('scrollX') && !classList.contains('animatedScrollX');
}
return true;
} }
let parent = target; const classList = elem.classList;
while (parent != null) { if (classList) {
if (!allowSwipeOn(parent)) { return !classList.contains('scrollX') && !classList.contains('animatedScrollX');
return false;
}
parent = parent.parentNode;
} }
return true; return true;
} }
function configureSwipeTabs(view, currentElement) { let parent = target;
if (!browser.touch) { while (parent != null) {
return; if (!allowSwipeOn(parent)) {
return false;
} }
parent = parent.parentNode;
// implement without hammer
const onSwipeLeft = function (e, target) {
if (allowSwipe(target) && view.contains(target)) {
currentElement.selectNext();
}
};
const onSwipeRight = function (e, target) {
if (allowSwipe(target) && view.contains(target)) {
currentElement.selectPrevious();
}
};
import('../scripts/touchHelper').then(({default: TouchHelper}) => {
const touchHelper = new TouchHelper(view.parentNode.parentNode);
Events.on(touchHelper, 'swipeleft', onSwipeLeft);
Events.on(touchHelper, 'swiperight', onSwipeRight);
view.addEventListener('viewdestroy', function () {
touchHelper.destroy();
});
});
} }
export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { return true;
ensureElements(); }
if (!view) { function configureSwipeTabs(view, currentElement) {
if (tabOwnerView) { if (!browser.touch) {
document.body.classList.remove('withSectionTabs'); return;
}
headerTabsContainer.innerHTML = ''; // implement without hammer
headerTabsContainer.classList.add('hide'); const onSwipeLeft = function (e, target) {
if (allowSwipe(target) && view.contains(target)) {
tabOwnerView = null; currentElement.selectNext();
}
return {
tabsContainer: headerTabsContainer,
replaced: false
};
} }
};
const tabsContainerElem = headerTabsContainer; const onSwipeRight = function (e, target) {
if (allowSwipe(target) && view.contains(target)) {
if (!tabOwnerView) { currentElement.selectPrevious();
tabsContainerElem.classList.remove('hide');
} }
};
if (tabOwnerView !== view) { import('../scripts/touchHelper').then(({ default: TouchHelper }) => {
let index = 0; const touchHelper = new TouchHelper(view.parentNode.parentNode);
const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); Events.on(touchHelper, 'swipeleft', onSwipeLeft);
const tabsHtml = '<div is="emby-tabs"' + indexAttribute + ' class="tabs-viewmenubar"><div class="emby-tabs-slider" style="white-space:nowrap;">' + getTabsFn().map(function (t) { Events.on(touchHelper, 'swiperight', onSwipeRight);
let tabClass = 'emby-tab-button';
if (t.enabled === false) { view.addEventListener('viewdestroy', function () {
tabClass += ' hide'; touchHelper.destroy();
} });
});
}
let tabHtml; export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) {
ensureElements();
if (t.cssClass) { if (!view) {
tabClass += ' ' + t.cssClass; if (tabOwnerView) {
} document.body.classList.remove('withSectionTabs');
if (t.href) { headerTabsContainer.innerHTML = '';
tabHtml = '<a href="' + t.href + '" is="emby-linkbutton" class="' + tabClass + '" data-index="' + index + '"><div class="emby-button-foreground">' + t.name + '</div></a>'; headerTabsContainer.classList.add('hide');
} else {
tabHtml = '<button type="button" is="emby-button" class="' + tabClass + '" data-index="' + index + '"><div class="emby-button-foreground">' + t.name + '</div></button>';
}
index++; tabOwnerView = null;
return tabHtml;
}).join('') + '</div></div>';
tabsContainerElem.innerHTML = tabsHtml;
window.CustomElements.upgradeSubtree(tabsContainerElem);
document.body.classList.add('withSectionTabs');
tabOwnerView = view;
tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]');
configureSwipeTabs(view, tabsElem);
if (getTabContainersFn) {
tabsElem.addEventListener('beforetabchange', function (e) {
const tabContainers = getTabContainersFn();
if (e.detail.previousIndex != null) {
const previousPanel = tabContainers[e.detail.previousIndex];
if (previousPanel) {
previousPanel.classList.remove('is-active');
}
}
const newPanel = tabContainers[e.detail.selectedTabIndex];
if (newPanel) {
newPanel.classList.add('is-active');
}
});
}
if (onBeforeTabChange) {
tabsElem.addEventListener('beforetabchange', onBeforeTabChange);
}
if (onTabChange) {
tabsElem.addEventListener('tabchange', onTabChange);
}
if (setSelectedIndex !== false) {
if (tabsElem.selectedIndex) {
tabsElem.selectedIndex(selectedIndex);
} else {
tabsElem.readySelectedIndex = selectedIndex;
tabsElem.addEventListener('ready', onViewTabsReady);
}
}
return {
tabsContainer: tabsContainerElem,
tabs: tabsElem,
replaced: true
};
} }
tabsElem.selectedIndex(selectedIndex);
return { return {
tabsContainer: tabsContainerElem, tabsContainer: headerTabsContainer,
tabs: tabsElem,
replaced: false replaced: false
}; };
} }
export function selectedTabIndex(index) { const tabsContainerElem = headerTabsContainer;
if (index != null) {
tabsElem.selectedIndex(index); if (!tabOwnerView) {
} else { tabsContainerElem.classList.remove('hide');
tabsElem.triggerTabChange(); }
if (tabOwnerView !== view) {
let index = 0;
const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"');
const tabsHtml = '<div is="emby-tabs"' + indexAttribute + ' class="tabs-viewmenubar"><div class="emby-tabs-slider" style="white-space:nowrap;">' + getTabsFn().map(function (t) {
let tabClass = 'emby-tab-button';
if (t.enabled === false) {
tabClass += ' hide';
}
let tabHtml;
if (t.cssClass) {
tabClass += ' ' + t.cssClass;
}
if (t.href) {
tabHtml = '<a href="' + t.href + '" is="emby-linkbutton" class="' + tabClass + '" data-index="' + index + '"><div class="emby-button-foreground">' + t.name + '</div></a>';
} else {
tabHtml = '<button type="button" is="emby-button" class="' + tabClass + '" data-index="' + index + '"><div class="emby-button-foreground">' + t.name + '</div></button>';
}
index++;
return tabHtml;
}).join('') + '</div></div>';
tabsContainerElem.innerHTML = tabsHtml;
window.CustomElements.upgradeSubtree(tabsContainerElem);
document.body.classList.add('withSectionTabs');
tabOwnerView = view;
tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]');
configureSwipeTabs(view, tabsElem);
if (getTabContainersFn) {
tabsElem.addEventListener('beforetabchange', function (e) {
const tabContainers = getTabContainersFn();
if (e.detail.previousIndex != null) {
const previousPanel = tabContainers[e.detail.previousIndex];
if (previousPanel) {
previousPanel.classList.remove('is-active');
}
}
const newPanel = tabContainers[e.detail.selectedTabIndex];
if (newPanel) {
newPanel.classList.add('is-active');
}
});
} }
if (onBeforeTabChange) {
tabsElem.addEventListener('beforetabchange', onBeforeTabChange);
}
if (onTabChange) {
tabsElem.addEventListener('tabchange', onTabChange);
}
if (setSelectedIndex !== false) {
if (tabsElem.selectedIndex) {
tabsElem.selectedIndex(selectedIndex);
} else {
tabsElem.readySelectedIndex = selectedIndex;
tabsElem.addEventListener('ready', onViewTabsReady);
}
}
return {
tabsContainer: tabsContainerElem,
tabs: tabsElem,
replaced: true
};
} }
export function getTabsElement() { tabsElem.selectedIndex(selectedIndex);
return document.querySelector('.tabs-viewmenubar');
}
/* eslint-enable indent */ return {
tabsContainer: tabsContainerElem,
tabs: tabsElem,
replaced: false
};
}
export function selectedTabIndex(index) {
if (index != null) {
tabsElem.selectedIndex(index);
} else {
tabsElem.triggerTabChange();
}
}
export function getTabsElement() {
return document.querySelector('.tabs-viewmenubar');
}
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for media library creator. * Module for media library creator.
@@ -25,167 +24,169 @@ import toast from '../toast/toast';
import alert from '../alert'; import alert from '../alert';
import template from './mediaLibraryCreator.template.html'; import template from './mediaLibraryCreator.template.html';
function onAddLibrary() { function onAddLibrary(e) {
if (isCreating) { if (isCreating) {
return false;
}
if (pathInfos.length == 0) {
alert({
text: globalize.translate('PleaseAddAtLeastOneFolder'),
type: 'error'
});
return false;
}
isCreating = true;
loading.show();
const dlg = dom.parentWithClass(this, 'dlg-librarycreator');
const name = $('#txtValue', dlg).val();
let type = $('#selectCollectionType', dlg).val();
if (type == 'mixed') {
type = null;
}
const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions'));
libraryOptions.PathInfos = pathInfos;
ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => {
hasChanges = true;
isCreating = false;
loading.hide();
dialogHelper.close(dlg);
}, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder'));
isCreating = false;
loading.hide();
});
return false; return false;
} }
function getCollectionTypeOptionsHtml(collectionTypeOptions) { if (pathInfos.length == 0) {
return collectionTypeOptions.map(i => { alert({
return `<option value="${i.value}">${i.name}</option>`; text: globalize.translate('PleaseAddAtLeastOneFolder'),
}).join(''); type: 'error'
}
function initEditor(page, collectionTypeOptions) {
$('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () {
const value = this.value;
const dlg = $(this).parents('.dialog')[0];
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value);
if (value) {
dlg.querySelector('.libraryOptions').classList.remove('hide');
} else {
dlg.querySelector('.libraryOptions').classList.add('hide');
}
if (value != 'mixed') {
const index = this.selectedIndex;
if (index != -1) {
const name = this.options[index].innerHTML.replace('*', '').replace('&amp;', '&');
$('#txtValue', dlg).val(name);
}
}
const folderOption = collectionTypeOptions.find(i => i.value === value);
$('.collectionTypeFieldDescription', dlg).html(folderOption?.message || '');
}); });
page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick);
page.querySelector('.btnSubmit').addEventListener('click', onAddLibrary); return false;
page.querySelector('.folderList').addEventListener('click', onRemoveClick);
} }
function onAddButtonClick() { isCreating = true;
const page = dom.parentWithClass(this, 'dlg-librarycreator'); loading.show();
const dlg = dom.parentWithClass(this, 'dlg-librarycreator');
const name = $('#txtValue', dlg).val();
let type = $('#selectCollectionType', dlg).val();
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => { if (type == 'mixed') {
const picker = new DirectoryBrowser(); type = null;
picker.show({
enableNetworkSharePath: true,
callback: function (path, networkSharePath) {
if (path) {
addMediaLocation(page, path, networkSharePath);
}
picker.close();
}
});
});
} }
function getFolderHtml(pathInfo, index) { const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions'));
let html = ''; libraryOptions.PathInfos = pathInfos;
html += '<div class="listItem listItem-border lnkPath">'; ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => {
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`; hasChanges = true;
html += `<div class="listItemBodyText" dir="ltr">${escapeHtml(pathInfo.Path)}</div>`; isCreating = false;
loading.hide();
dialogHelper.close(dlg);
}, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder'));
if (pathInfo.NetworkPath) { isCreating = false;
html += `<div class="listItemBodyText secondary" dir="ltr">${escapeHtml(pathInfo.NetworkPath)}</div>`; loading.hide();
} });
e.preventDefault();
}
html += '</div>'; function getCollectionTypeOptionsHtml(collectionTypeOptions) {
html += `<button type="button" is="paper-icon-button-light"" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`; return collectionTypeOptions.map(i => {
html += '</div>'; return `<option value="${i.value}">${i.name}</option>`;
return html; }).join('');
} }
function renderPaths(page) { function initEditor(page, collectionTypeOptions) {
const foldersHtml = pathInfos.map(getFolderHtml).join(''); $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () {
const folderList = page.querySelector('.folderList'); const value = this.value;
folderList.innerHTML = foldersHtml; const dlg = $(this).parents('.dialog')[0];
libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value);
if (foldersHtml) { if (value) {
folderList.classList.remove('hide'); dlg.querySelector('.libraryOptions').classList.remove('hide');
} else { } else {
folderList.classList.add('hide'); dlg.querySelector('.libraryOptions').classList.add('hide');
} }
}
function addMediaLocation(page, path, networkSharePath) { if (value != 'mixed') {
const pathLower = path.toLowerCase(); const index = this.selectedIndex;
const pathFilter = pathInfos.filter(p => {
return p.Path.toLowerCase() == pathLower;
});
if (!pathFilter.length) { if (index != -1) {
const pathInfo = { const name = this.options[index].innerHTML
Path: path .replaceAll('*', '')
}; .replaceAll('&amp;', '&');
$('#txtValue', dlg).val(name);
if (networkSharePath) {
pathInfo.NetworkPath = networkSharePath;
} }
pathInfos.push(pathInfo);
renderPaths(page);
} }
}
function onRemoveClick(e) { const folderOption = collectionTypeOptions.find(i => i.value === value);
const button = dom.parentWithClass(e.target, 'btnRemovePath'); $('.collectionTypeFieldDescription', dlg).html(folderOption?.message || '');
const index = parseInt(button.getAttribute('data-index'), 10); });
const location = pathInfos[index].Path; page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick);
const locationLower = location.toLowerCase(); page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary);
pathInfos = pathInfos.filter(p => { page.querySelector('.folderList').addEventListener('click', onRemoveClick);
return p.Path.toLowerCase() != locationLower; }
function onAddButtonClick() {
const page = dom.parentWithClass(this, 'dlg-librarycreator');
import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
enableNetworkSharePath: true,
callback: function (path, networkSharePath) {
if (path) {
addMediaLocation(page, path, networkSharePath);
}
picker.close();
}
}); });
renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); });
}
function getFolderHtml(pathInfo, index) {
let html = '';
html += '<div class="listItem listItem-border lnkPath">';
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`;
html += `<div class="listItemBodyText" dir="ltr">${escapeHtml(pathInfo.Path)}</div>`;
if (pathInfo.NetworkPath) {
html += `<div class="listItemBodyText secondary" dir="ltr">${escapeHtml(pathInfo.NetworkPath)}</div>`;
} }
function onDialogClosed() { html += '</div>';
currentResolve(hasChanges); html += `<button type="button" is="paper-icon-button-light"" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`;
} html += '</div>';
return html;
}
function initLibraryOptions(dlg) { function renderPaths(page) {
libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => { const foldersHtml = pathInfos.map(getFolderHtml).join('');
$('#selectCollectionType', dlg).trigger('change'); const folderList = page.querySelector('.folderList');
}); folderList.innerHTML = foldersHtml;
if (foldersHtml) {
folderList.classList.remove('hide');
} else {
folderList.classList.add('hide');
} }
}
function addMediaLocation(page, path, networkSharePath) {
const pathLower = path.toLowerCase();
const pathFilter = pathInfos.filter(p => {
return p.Path.toLowerCase() == pathLower;
});
if (!pathFilter.length) {
const pathInfo = {
Path: path
};
if (networkSharePath) {
pathInfo.NetworkPath = networkSharePath;
}
pathInfos.push(pathInfo);
renderPaths(page);
}
}
function onRemoveClick(e) {
const button = dom.parentWithClass(e.target, 'btnRemovePath');
const index = parseInt(button.getAttribute('data-index'), 10);
const location = pathInfos[index].Path;
const locationLower = location.toLowerCase();
pathInfos = pathInfos.filter(p => {
return p.Path.toLowerCase() != locationLower;
});
renderPaths(dom.parentWithClass(button, 'dlg-librarycreator'));
}
function onDialogClosed() {
currentResolve(hasChanges);
}
function initLibraryOptions(dlg) {
libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => {
$('#selectCollectionType', dlg).trigger('change');
});
}
export class showEditor { export class showEditor {
constructor(options) { constructor(options) {
@@ -217,11 +218,10 @@ export class showEditor {
} }
} }
let pathInfos = []; let pathInfos = [];
let currentResolve; let currentResolve;
let currentOptions; let currentOptions;
let hasChanges = false; let hasChanges = false;
let isCreating = false; let isCreating = false;
/* eslint-enable indent */
export default showEditor; export default showEditor;
@@ -1,37 +1,39 @@
<div class="formDialogHeader"> <form class="addLibraryForm" style="max-width:100%;">
<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> <div class="formDialogHeader">
<h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3> <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>
</div> <h3 class="formDialogHeaderTitle">${ButtonAddMediaLibrary}</h3>
<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>
<div class="formDialogFooter"> <div class="formDialogContent scrollY" style="padding-top:2em;">
<button is="emby-button" type="button" class="raised btnSubmit button-submit block formDialogFooterItem"> <div class="dialogContentInner dialog-content-centered">
<span>${ButtonOk}</span>
</button> <div id="fldCollectionType" class="selectContainer">
</div> <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>
@@ -1,4 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for media library editor. * Module for media library editor.
@@ -23,180 +22,180 @@ import toast from '../toast/toast';
import confirm from '../confirm/confirm'; import confirm from '../confirm/confirm';
import template from './mediaLibraryEditor.template.html'; import template from './mediaLibraryEditor.template.html';
function onEditLibrary() { function onEditLibrary() {
if (isCreating) { if (isCreating) {
return false;
}
isCreating = true;
loading.show();
const dlg = dom.parentWithClass(this, 'dlg-libraryeditor');
let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions'));
libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions);
ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => {
hasChanges = true;
isCreating = false;
loading.hide();
dialogHelper.close(dlg);
}, () => {
isCreating = false;
loading.hide();
});
return false; return false;
} }
function addMediaLocation(page, path, networkSharePath) { isCreating = true;
const virtualFolder = currentOptions.library; loading.show();
const dlg = dom.parentWithClass(this, 'dlg-libraryeditor');
let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions'));
libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions);
ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => {
hasChanges = true;
isCreating = false;
loading.hide();
dialogHelper.close(dlg);
}, () => {
isCreating = false;
loading.hide();
});
return false;
}
function addMediaLocation(page, path, networkSharePath) {
const virtualFolder = currentOptions.library;
const refreshAfterChange = currentOptions.refresh;
ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => {
hasChanges = true;
refreshLibraryFromServer(page);
}, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder'));
});
}
function updateMediaLocation(page, path, networkSharePath) {
const virtualFolder = currentOptions.library;
ApiClient.updateMediaPath(virtualFolder.Name, {
Path: path,
NetworkPath: networkSharePath
}).then(() => {
hasChanges = true;
refreshLibraryFromServer(page);
}, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder'));
});
}
function onRemoveClick(btnRemovePath, location) {
const button = btnRemovePath;
const virtualFolder = currentOptions.library;
confirm({
title: globalize.translate('HeaderRemoveMediaLocation'),
text: globalize.translate('MessageConfirmRemoveMediaLocation'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(() => {
const refreshAfterChange = currentOptions.refresh; const refreshAfterChange = currentOptions.refresh;
ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => { ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => {
hasChanges = true; hasChanges = true;
refreshLibraryFromServer(page); refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor'));
}, () => { }, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); toast(globalize.translate('ErrorDefault'));
});
});
}
function onListItemClick(e) {
const listItem = dom.parentWithClass(e.target, 'listItem');
if (listItem) {
const index = parseInt(listItem.getAttribute('data-index'), 10);
const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || [];
const pathInfo = index == null ? {} : pathInfos[index] || {};
const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]);
const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath');
if (btnRemovePath) {
onRemoveClick(btnRemovePath, originalPath);
return;
}
showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath);
}
}
function getFolderHtml(pathInfo, index) {
let html = '';
html += `<div class="listItem listItem-border lnkPath" data-index="${index}">`;
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`;
html += '<h3 class="listItemBodyText">';
html += escapeHtml(pathInfo.Path);
html += '</h3>';
if (pathInfo.NetworkPath) {
html += `<div class="listItemBodyText secondary">${escapeHtml(pathInfo.NetworkPath)}</div>`;
}
html += '</div>';
html += `<button type="button" is="paper-icon-button-light" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`;
html += '</div>';
return html;
}
function refreshLibraryFromServer(page) {
ApiClient.getVirtualFolders().then(result => {
const library = result.filter(f => {
return f.Name === currentOptions.library.Name;
})[0];
if (library) {
currentOptions.library = library;
renderLibrary(page, currentOptions);
}
});
}
function renderLibrary(page, options) {
let pathInfos = (options.library.LibraryOptions || {}).PathInfos || [];
if (!pathInfos.length) {
pathInfos = options.library.Locations.map(p => {
return {
Path: p
};
}); });
} }
function updateMediaLocation(page, path, networkSharePath) { if (options.library.CollectionType === 'boxsets') {
const virtualFolder = currentOptions.library; page.querySelector('.folders').classList.add('hide');
ApiClient.updateMediaPath(virtualFolder.Name, { } else {
Path: path, page.querySelector('.folders').classList.remove('hide');
NetworkPath: networkSharePath
}).then(() => {
hasChanges = true;
refreshLibraryFromServer(page);
}, () => {
toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder'));
});
} }
function onRemoveClick(btnRemovePath, location) { page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join('');
const button = btnRemovePath; }
const virtualFolder = currentOptions.library;
confirm({ function onAddButtonClick() {
title: globalize.translate('HeaderRemoveMediaLocation'), showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor'));
text: globalize.translate('MessageConfirmRemoveMediaLocation'), }
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(() => {
const refreshAfterChange = currentOptions.refresh;
ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => {
hasChanges = true;
refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor'));
}, () => {
toast(globalize.translate('ErrorDefault'));
});
});
}
function onListItemClick(e) { function showDirectoryBrowser(context, originalPath, networkPath) {
const listItem = dom.parentWithClass(e.target, 'listItem'); import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
if (listItem) { picker.show({
const index = parseInt(listItem.getAttribute('data-index'), 10); enableNetworkSharePath: true,
const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; pathReadOnly: originalPath != null,
const pathInfo = index == null ? {} : pathInfos[index] || {}; path: originalPath,
const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); networkSharePath: networkPath,
const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); callback: function (path, networkSharePath) {
if (path) {
if (btnRemovePath) { if (originalPath) {
onRemoveClick(btnRemovePath, originalPath); updateMediaLocation(context, originalPath, networkSharePath);
return; } else {
} addMediaLocation(context, path, networkSharePath);
showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath);
}
}
function getFolderHtml(pathInfo, index) {
let html = '';
html += `<div class="listItem listItem-border lnkPath" data-index="${index}">`;
html += `<div class="${pathInfo.NetworkPath ? 'listItemBody two-line' : 'listItemBody'}">`;
html += '<h3 class="listItemBodyText">';
html += escapeHtml(pathInfo.Path);
html += '</h3>';
if (pathInfo.NetworkPath) {
html += `<div class="listItemBodyText secondary">${escapeHtml(pathInfo.NetworkPath)}</div>`;
}
html += '</div>';
html += `<button type="button" is="paper-icon-button-light" class="listItemButton btnRemovePath" data-index="${index}"><span class="material-icons remove_circle" aria-hidden="true"></span></button>`;
html += '</div>';
return html;
}
function refreshLibraryFromServer(page) {
ApiClient.getVirtualFolders().then(result => {
const library = result.filter(f => {
return f.Name === currentOptions.library.Name;
})[0];
if (library) {
currentOptions.library = library;
renderLibrary(page, currentOptions);
}
});
}
function renderLibrary(page, options) {
let pathInfos = (options.library.LibraryOptions || {}).PathInfos || [];
if (!pathInfos.length) {
pathInfos = options.library.Locations.map(p => {
return {
Path: p
};
});
}
if (options.library.CollectionType === 'boxsets') {
page.querySelector('.folders').classList.add('hide');
} else {
page.querySelector('.folders').classList.remove('hide');
}
page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join('');
}
function onAddButtonClick() {
showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor'));
}
function showDirectoryBrowser(context, originalPath, networkPath) {
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
enableNetworkSharePath: true,
pathReadOnly: originalPath != null,
path: originalPath,
networkSharePath: networkPath,
callback: function (path, networkSharePath) {
if (path) {
if (originalPath) {
updateMediaLocation(context, originalPath, networkSharePath);
} else {
addMediaLocation(context, path, networkSharePath);
}
} }
picker.close();
} }
});
picker.close();
}
}); });
} });
}
function initEditor(dlg, options) { function initEditor(dlg, options) {
renderLibrary(dlg, options); renderLibrary(dlg, options);
dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick);
dlg.querySelector('.folderList').addEventListener('click', onListItemClick); dlg.querySelector('.folderList').addEventListener('click', onListItemClick);
dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary);
libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions); libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions);
} }
function onDialogClosed() { function onDialogClosed() {
currentDeferred.resolveWith(null, [hasChanges]); currentDeferred.resolveWith(null, [hasChanges]);
} }
export class showEditor { export class showEditor {
constructor(options) { constructor(options) {
@@ -227,10 +226,9 @@ export class showEditor {
} }
} }
let currentDeferred; let currentDeferred;
let currentOptions; let currentOptions;
let hasChanges = false; let hasChanges = false;
let isCreating = false; let isCreating = false;
/* eslint-enable indent */
export default showEditor; export default showEditor;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+76 -79
View File
@@ -8,94 +8,91 @@ import '../../elements/emby-select/emby-select';
import '../formdialog.scss'; import '../formdialog.scss';
import template from './personEditor.template.html'; import template from './personEditor.template.html';
/* eslint-disable indent */ function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function centerFocus(elem, horiz, on) { function show(person) {
import('../../scripts/scrollHelper').then((scrollHelper) => { return new Promise(function (resolve, reject) {
const fn = on ? 'on' : 'off'; const dialogOptions = {
scrollHelper.centerFocus[fn](elem, horiz); removeOnClose: true,
}); scrollY: false
} };
function show(person) { if (layoutManager.tv) {
return new Promise(function (resolve, reject) { dialogOptions.size = 'fullscreen';
const dialogOptions = { } else {
removeOnClose: true, dialogOptions.size = 'small';
scrollY: false }
};
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
let submitted = false;
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
dlg.querySelector('.txtPersonName', dlg).value = person.Name || '';
dlg.querySelector('.selectPersonType', dlg).value = person.Type || '';
dlg.querySelector('.txtPersonRole', dlg).value = person.Role || '';
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
dialogHelper.open(dlg);
dlg.addEventListener('close', function () {
if (layoutManager.tv) { if (layoutManager.tv) {
dialogOptions.size = 'fullscreen'; centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (submitted) {
resolve(person);
} else { } else {
dialogOptions.size = 'small'; reject();
} }
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
let submitted = false;
html += globalize.translateHtml(template, 'core');
dlg.innerHTML = html;
dlg.querySelector('.txtPersonName', dlg).value = person.Name || '';
dlg.querySelector('.selectPersonType', dlg).value = person.Type || '';
dlg.querySelector('.txtPersonRole', dlg).value = person.Role || '';
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
dialogHelper.open(dlg);
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (submitted) {
resolve(person);
} else {
reject();
}
});
dlg.querySelector('.selectPersonType').addEventListener('change', function () {
if (this.value === 'Actor') {
dlg.querySelector('.fldRole').classList.remove('hide');
} else {
dlg.querySelector('.fldRole').classList.add('hide');
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', function (e) {
submitted = true;
person.Name = dlg.querySelector('.txtPersonName', dlg).value;
person.Type = dlg.querySelector('.selectPersonType', dlg).value;
person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null;
dialogHelper.close(dlg);
e.preventDefault();
return false;
});
dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
}); });
}
dlg.querySelector('.selectPersonType').addEventListener('change', function () {
if (this.value === 'Actor') {
dlg.querySelector('.fldRole').classList.remove('hide');
} else {
dlg.querySelector('.fldRole').classList.add('hide');
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', function (e) {
submitted = true;
person.Name = dlg.querySelector('.txtPersonName', dlg).value;
person.Type = dlg.querySelector('.selectPersonType', dlg).value;
person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null;
dialogHelper.close(dlg);
e.preventDefault();
return false;
});
dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
});
}
export default { export default {
show: show show: show
}; };
/* eslint-enable indent */
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@ import serverNotifications from '../../scripts/serverNotifications';
import { playbackManager } from '../playback/playbackmanager'; import { playbackManager } from '../playback/playbackmanager';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
import NotificationIcon from './notificationicon.png'; import NotificationIcon from './notificationicon.png';
@@ -130,7 +131,7 @@ function onLibraryChanged(data, apiClient) {
newItems.length = 12; newItems.length = 12;
} }
apiClient.getItems(apiClient.getCurrentUserId(), { getItems(apiClient, apiClient.getCurrentUserId(), {
Recursive: true, Recursive: true,
Limit: 3, Limit: 3,
File diff suppressed because it is too large Load Diff
+119 -122
View File
@@ -1,18 +1,17 @@
import appSettings from '../scripts/settings/appSettings'; import appSettings from '../scripts/settings/appSettings';
import { pluginManager } from './pluginManager'; import { pluginManager } from './pluginManager';
/* eslint-disable indent */
class PackageManager { class PackageManager {
#packagesList = []; #packagesList = [];
#settingsKey = 'installedpackages1'; #settingsKey = 'installedpackages1';
init() { init() {
console.groupCollapsed('loading packages'); console.groupCollapsed('loading packages');
const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]');
return Promise.all(manifestUrls.map((url) => { return Promise.all(manifestUrls.map((url) => {
return this.loadPackage(url); return this.loadPackage(url);
})) }))
.then(() => { .then(() => {
console.debug('finished loading packages'); console.debug('finished loading packages');
return Promise.resolve(); return Promise.resolve();
@@ -22,119 +21,117 @@ import { pluginManager } from './pluginManager';
}).finally(() => { }).finally(() => {
console.groupEnd('loading packages'); console.groupEnd('loading packages');
}); });
}
get packages() {
return this.#packagesList.slice(0);
}
install(url) {
return this.loadPackage(url, true).then((pkg) => {
const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]');
if (!manifestUrls.includes(url)) {
manifestUrls.push(url);
appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls));
}
return pkg;
});
}
uninstall(name) {
const pkg = this.#packagesList.filter((p) => {
return p.name === name;
})[0];
if (pkg) {
this.#packagesList = this.#packagesList.filter((p) => {
return p.name !== name;
});
this.removeUrl(pkg.url);
}
return Promise.resolve();
}
mapPath(pkg, pluginUrl) {
const urlLower = pluginUrl.toLowerCase();
if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) {
return pluginUrl;
}
let packageUrl = pkg.url;
packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/'));
packageUrl += '/';
packageUrl += pluginUrl;
return packageUrl;
}
addPackage(pkg) {
this.#packagesList = this.#packagesList.filter((p) => {
return p.name !== pkg.name;
});
this.#packagesList.push(pkg);
}
removeUrl(url) {
let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]');
manifestUrls = manifestUrls.filter((i) => {
return i !== url;
});
appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls));
}
loadPackage(url, throwError = false) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const originalUrl = url;
url += url.indexOf('?') === -1 ? '?' : '&';
url += 't=' + new Date().getTime();
xhr.open('GET', url, true);
const onError = () => {
if (throwError === true) {
reject();
} else {
this.removeUrl(originalUrl);
resolve();
}
};
xhr.onload = () => {
if (this.status < 400) {
const pkg = JSON.parse(this.response);
pkg.url = originalUrl;
this.addPackage(pkg);
const plugins = pkg.plugins || [];
if (pkg.plugin) {
plugins.push(pkg.plugin);
}
const promises = plugins.map((pluginUrl) => {
return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl));
});
Promise.all(promises).then(resolve, resolve);
} else {
onError();
}
};
xhr.onerror = onError;
xhr.send();
});
}
} }
/* eslint-enable indent */ get packages() {
return this.#packagesList.slice(0);
}
install(url) {
return this.loadPackage(url, true).then((pkg) => {
const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]');
if (!manifestUrls.includes(url)) {
manifestUrls.push(url);
appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls));
}
return pkg;
});
}
uninstall(name) {
const pkg = this.#packagesList.filter((p) => {
return p.name === name;
})[0];
if (pkg) {
this.#packagesList = this.#packagesList.filter((p) => {
return p.name !== name;
});
this.removeUrl(pkg.url);
}
return Promise.resolve();
}
mapPath(pkg, pluginUrl) {
const urlLower = pluginUrl.toLowerCase();
if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) {
return pluginUrl;
}
let packageUrl = pkg.url;
packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/'));
packageUrl += '/';
packageUrl += pluginUrl;
return packageUrl;
}
addPackage(pkg) {
this.#packagesList = this.#packagesList.filter((p) => {
return p.name !== pkg.name;
});
this.#packagesList.push(pkg);
}
removeUrl(url) {
let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]');
manifestUrls = manifestUrls.filter((i) => {
return i !== url;
});
appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls));
}
loadPackage(url, throwError = false) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const originalUrl = url;
url += url.indexOf('?') === -1 ? '?' : '&';
url += 't=' + new Date().getTime();
xhr.open('GET', url, true);
const onError = () => {
if (throwError === true) {
reject();
} else {
this.removeUrl(originalUrl);
resolve();
}
};
xhr.onload = () => {
if (this.status < 400) {
const pkg = JSON.parse(this.response);
pkg.url = originalUrl;
this.addPackage(pkg);
const plugins = pkg.plugins || [];
if (pkg.plugin) {
plugins.push(pkg.plugin);
}
const promises = plugins.map((pluginUrl) => {
return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl));
});
Promise.all(promises).then(resolve, resolve);
} else {
onError();
}
};
xhr.onerror = onError;
xhr.send();
});
}
}
export default new PackageManager(); export default new PackageManager();
+215 -218
View File
@@ -4,259 +4,256 @@ import Events from '../../utils/events.ts';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import shell from '../../scripts/shell'; import shell from '../../scripts/shell';
/* eslint-disable indent */ // Reports media playback to the device for lock screen control
// Reports media playback to the device for lock screen control let currentPlayer;
let currentPlayer; function seriesImageUrl(item, options = {}) {
options.type = options.type || 'Primary';
function seriesImageUrl(item, options = {}) { if (item.Type !== 'Episode') {
options.type = options.type || 'Primary'; return null;
} else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) {
options.tag = item.SeriesPrimaryImageTag;
if (item.Type !== 'Episode') { return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
return null; } else if (options.type === 'Thumb') {
} else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { if (item.SeriesThumbImageTag) {
options.tag = item.SeriesPrimaryImageTag; options.tag = item.SeriesThumbImageTag;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options);
} else if (options.type === 'Thumb') { } else if (item.ParentThumbImageTag) {
if (item.SeriesThumbImageTag) { options.tag = item.ParentThumbImageTag;
options.tag = item.SeriesThumbImageTag;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options);
} else if (item.ParentThumbImageTag) {
options.tag = item.ParentThumbImageTag;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options);
}
} }
}
return null;
}
function imageUrl(item, options = {}) {
options.type = options.type || 'Primary';
if (item.ImageTags && item.ImageTags[options.type]) {
options.tag = item.ImageTags[options.type];
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options);
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options);
}
return null;
}
function getImageUrl(item, imageOptions = {}) {
const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions);
if (url) {
const height = imageOptions.height || imageOptions.maxHeight;
return {
src: url,
sizes: height + 'x' + height
};
} else {
return null; return null;
} }
}
function imageUrl(item, options = {}) { function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) {
options.type = options.type || 'Primary'; const list = [];
if (item.ImageTags && item.ImageTags[options.type]) { imageSizes.forEach((size) => {
options.tag = item.ImageTags[options.type]; const url = getImageUrl(item, { height: size });
if (url !== null) {
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); list.push(url);
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
options.tag = item.AlbumPrimaryImageTag;
return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options);
} }
});
return null; return list;
}
function updatePlayerState(player, state, eventName) {
// Don't go crazy reporting position changes
if (eventName === 'timeupdate') {
// Only report if this item hasn't been reported yet, or if there's an actual playback change.
// Don't report on simple time updates
return;
} }
function getImageUrl(item, imageOptions = {}) { const item = state.NowPlayingItem;
const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions);
if (url) { if (!item) {
const height = imageOptions.height || imageOptions.maxHeight;
return {
src: url,
sizes: height + 'x' + height
};
} else {
return null;
}
}
function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) {
const list = [];
imageSizes.forEach((size) => {
const url = getImageUrl(item, {height: size});
if (url !== null) {
list.push(url);
}
});
return list;
}
function updatePlayerState(player, state, eventName) {
// Don't go crazy reporting position changes
if (eventName === 'timeupdate') {
// Only report if this item hasn't been reported yet, or if there's an actual playback change.
// Don't report on simple time updates
return;
}
const item = state.NowPlayingItem;
if (!item) {
hideMediaControls();
return;
}
if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate
eventName = 'timeupdate';
}
const isVideo = item.MediaType === 'Video';
const isLocalPlayer = player.isLocalPlayer || false;
// Local players do their own notifications
if (isLocalPlayer && isVideo) {
return;
}
const playState = state.PlayState || {};
const parts = nowPlayingHelper.getNowPlayingNames(item);
const artist = parts[parts.length - 1].text;
const title = parts.length === 1 ? '' : parts[0].text;
const album = item.Album || '';
const itemId = item.Id;
// Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10);
const isPaused = playState.IsPaused || false;
const canSeek = playState.CanSeek || false;
if ('mediaSession' in navigator) {
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.metadata = new MediaMetadata({
title: title,
artist: artist,
album: album,
artwork: getImageUrls(item)
});
} else {
const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 });
shell.updateMediaSession({
action: eventName,
isLocalPlayer: isLocalPlayer,
itemId: itemId,
title: title,
artist: artist,
album: album,
duration: duration,
position: currentTime,
imageUrl: itemImageUrl,
canSeek: canSeek,
isPaused: isPaused
});
}
}
function onGeneralEvent(e) {
const state = playbackManager.getPlayerState(this);
updatePlayerState(this, state, e.type);
}
function onStateChanged(e, state) {
updatePlayerState(this, state, 'statechange');
}
function onPlaybackStart(e, state) {
updatePlayerState(this, state, e.type);
}
function onPlaybackStopped() {
hideMediaControls(); hideMediaControls();
return;
} }
function releaseCurrentPlayer() { if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate
if (currentPlayer) { eventName = 'timeupdate';
Events.off(currentPlayer, 'playbackstart', onPlaybackStart);
Events.off(currentPlayer, 'playbackstop', onPlaybackStopped);
Events.off(currentPlayer, 'unpause', onGeneralEvent);
Events.off(currentPlayer, 'pause', onGeneralEvent);
Events.off(currentPlayer, 'statechange', onStateChanged);
Events.off(currentPlayer, 'timeupdate', onGeneralEvent);
currentPlayer = null;
hideMediaControls();
}
} }
function hideMediaControls() { const isVideo = item.MediaType === 'Video';
if ('mediaSession' in navigator) { const isLocalPlayer = player.isLocalPlayer || false;
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.metadata = null; // Local players do their own notifications
} else { if (isLocalPlayer && isVideo) {
shell.hideMediaSession(); return;
}
} }
function bindToPlayer(player) { const playState = state.PlayState || {};
releaseCurrentPlayer(); const parts = nowPlayingHelper.getNowPlayingNames(item);
const artist = parts[parts.length - 1].text;
const title = parts.length === 1 ? '' : parts[0].text;
if (!player) { const album = item.Album || '';
return; const itemId = item.Id;
}
currentPlayer = player; // Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10);
const state = playbackManager.getPlayerState(player); const isPaused = playState.IsPaused || false;
updatePlayerState(player, state, 'init'); const canSeek = playState.CanSeek || false;
Events.on(currentPlayer, 'playbackstart', onPlaybackStart);
Events.on(currentPlayer, 'playbackstop', onPlaybackStopped);
Events.on(currentPlayer, 'unpause', onGeneralEvent);
Events.on(currentPlayer, 'pause', onGeneralEvent);
Events.on(currentPlayer, 'statechange', onStateChanged);
Events.on(currentPlayer, 'timeupdate', onGeneralEvent);
}
function execute(name) {
playbackManager[name](currentPlayer);
}
if ('mediaSession' in navigator) { if ('mediaSession' in navigator) {
/* eslint-disable-next-line compat/compat */ /* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('previoustrack', function () { navigator.mediaSession.metadata = new MediaMetadata({
execute('previousTrack'); title: title,
artist: artist,
album: album,
artwork: getImageUrls(item)
}); });
} else {
/* eslint-disable-next-line compat/compat */ const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 });
navigator.mediaSession.setActionHandler('nexttrack', function () { shell.updateMediaSession({
execute('nextTrack'); action: eventName,
}); isLocalPlayer: isLocalPlayer,
itemId: itemId,
/* eslint-disable-next-line compat/compat */ title: title,
navigator.mediaSession.setActionHandler('play', function () { artist: artist,
execute('unpause'); album: album,
}); duration: duration,
position: currentTime,
/* eslint-disable-next-line compat/compat */ imageUrl: itemImageUrl,
navigator.mediaSession.setActionHandler('pause', function () { canSeek: canSeek,
execute('pause'); isPaused: isPaused
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekbackward', function () {
execute('rewind');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekforward', function () {
execute('fastForward');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekto', function (object) {
const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
}); });
} }
}
Events.on(playbackManager, 'playerchange', function () { function onGeneralEvent(e) {
bindToPlayer(playbackManager.getCurrentPlayer()); const state = playbackManager.getPlayerState(this);
updatePlayerState(this, state, e.type);
}
function onStateChanged(e, state) {
updatePlayerState(this, state, 'statechange');
}
function onPlaybackStart(e, state) {
updatePlayerState(this, state, e.type);
}
function onPlaybackStopped() {
hideMediaControls();
}
function releaseCurrentPlayer() {
if (currentPlayer) {
Events.off(currentPlayer, 'playbackstart', onPlaybackStart);
Events.off(currentPlayer, 'playbackstop', onPlaybackStopped);
Events.off(currentPlayer, 'unpause', onGeneralEvent);
Events.off(currentPlayer, 'pause', onGeneralEvent);
Events.off(currentPlayer, 'statechange', onStateChanged);
Events.off(currentPlayer, 'timeupdate', onGeneralEvent);
currentPlayer = null;
hideMediaControls();
}
}
function hideMediaControls() {
if ('mediaSession' in navigator) {
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.metadata = null;
} else {
shell.hideMediaSession();
}
}
function bindToPlayer(player) {
releaseCurrentPlayer();
if (!player) {
return;
}
currentPlayer = player;
const state = playbackManager.getPlayerState(player);
updatePlayerState(player, state, 'init');
Events.on(currentPlayer, 'playbackstart', onPlaybackStart);
Events.on(currentPlayer, 'playbackstop', onPlaybackStopped);
Events.on(currentPlayer, 'unpause', onGeneralEvent);
Events.on(currentPlayer, 'pause', onGeneralEvent);
Events.on(currentPlayer, 'statechange', onStateChanged);
Events.on(currentPlayer, 'timeupdate', onGeneralEvent);
}
function execute(name) {
playbackManager[name](currentPlayer);
}
if ('mediaSession' in navigator) {
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('previoustrack', function () {
execute('previousTrack');
}); });
bindToPlayer(playbackManager.getCurrentPlayer()); /* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('nexttrack', function () {
execute('nextTrack');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('play', function () {
execute('unpause');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('pause', function () {
execute('pause');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekbackward', function () {
execute('rewind');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekforward', function () {
execute('fastForward');
});
/* eslint-disable-next-line compat/compat */
navigator.mediaSession.setActionHandler('seekto', function (object) {
const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem;
// Convert to ms
const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10);
const wantedTime = object.seekTime * 1000;
playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer);
});
}
Events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer());
});
bindToPlayer(playbackManager.getCurrentPlayer());
/* eslint-enable indent */
+2 -2
View File
@@ -13,6 +13,7 @@ import ServerConnections from '../ServerConnections';
import alert from '../alert'; import alert from '../alert';
import { PluginType } from '../../types/plugin.ts'; import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts'; import { includesAny } from '../../utils/container.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
const UNLIMITED_ITEMS = -1; const UNLIMITED_ITEMS = -1;
@@ -127,7 +128,7 @@ function getItemsForPlayback(serverId, query) {
query.EnableTotalRecordCount = false; query.EnableTotalRecordCount = false;
query.CollapseBoxSetItems = false; query.CollapseBoxSetItems = false;
return apiClient.getItems(apiClient.getCurrentUserId(), query); return getItems(apiClient, apiClient.getCurrentUserId(), query);
} }
} }
@@ -1038,7 +1039,6 @@ class PlaybackManager {
} }
} }
//var mediaType = item.MediaType;
return getPlayer(item, getDefaultPlayOptions()) != null; return getPlayer(item, getDefaultPlayOptions()) != null;
}; };
@@ -12,102 +12,120 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import template from './playbackSettings.template.html'; import template from './playbackSettings.template.html';
/* eslint-disable indent */ function fillSkipLengths(select) {
const options = [5, 10, 15, 20, 25, 30];
function fillSkipLengths(select) { select.innerHTML = options.map(option => {
const options = [5, 10, 15, 20, 25, 30]; return {
name: globalize.translate('ValueSeconds', option),
value: option * 1000
};
}).map(o => {
return `<option value="${o.value}">${o.name}</option>`;
}).join('');
}
select.innerHTML = options.map(option => { function populateLanguages(select, languages) {
return { let html = '';
name: globalize.translate('ValueSeconds', option),
value: option * 1000 html += `<option value=''>${globalize.translate('AnyLanguage')}</option>`;
};
}).map(o => { for (let i = 0, length = languages.length; i < length; i++) {
return `<option value="${o.value}">${o.name}</option>`; const culture = languages[i];
}).join('');
html += `<option value='${culture.ThreeLetterISOLanguageName}'>${culture.DisplayName}</option>`;
} }
function populateLanguages(select, languages) { select.innerHTML = html;
let html = ''; }
html += `<option value=''>${globalize.translate('AnyLanguage')}</option>`; function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) {
const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({
for (let i = 0, length = languages.length; i < length; i++) { currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
const culture = languages[i]; isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype),
enableAuto: true
html += `<option value='${culture.ThreeLetterISOLanguageName}'>${culture.DisplayName}</option>`; }) : qualityoptions.getVideoQualityOptions({
}
select.innerHTML = html; currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype),
enableAuto: true,
maxVideoWidth
});
select.innerHTML = options.map(i => {
// render empty string instead of 0 for the auto option
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
}).join('');
}
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
fillQuality(select, isInNetwork, mediatype);
if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) {
select.value = '';
} else {
select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype);
}
}
function fillChromecastQuality(select, maxVideoWidth) {
const options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: appSettings.maxChromecastBitrate(),
isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(),
enableAuto: true,
maxVideoWidth
});
select.innerHTML = options.map(i => {
// render empty string instead of 0 for the auto option
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
}).join('');
select.value = appSettings.maxChromecastBitrate() || '';
}
function setMaxBitrateFromField(select, isInNetwork, mediatype) {
if (select.value) {
appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value);
appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false);
} else {
appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true);
}
}
function showHideQualityFields(context, user, apiClient) {
if (user.Policy.EnableVideoPlaybackTranscoding) {
context.querySelector('.videoQualitySection').classList.remove('hide');
} else {
context.querySelector('.videoQualitySection').classList.add('hide');
} }
function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) { if (appHost.supports('multiserver')) {
const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({ context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide');
context.querySelector('.fldVideoInternetQuality').classList.remove('hide');
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), if (user.Policy.EnableAudioPlaybackTranscoding) {
isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), context.querySelector('.musicQualitySection').classList.remove('hide');
enableAuto: true
}) : qualityoptions.getVideoQualityOptions({
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype),
enableAuto: true,
maxVideoWidth
});
select.innerHTML = options.map(i => {
// render empty string instead of 0 for the auto option
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
}).join('');
}
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
fillQuality(select, isInNetwork, mediatype);
if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) {
select.value = '';
} else { } else {
select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype); context.querySelector('.musicQualitySection').classList.add('hide');
}
}
function fillChromecastQuality(select, maxVideoWidth) {
const options = qualityoptions.getVideoQualityOptions({
currentMaxBitrate: appSettings.maxChromecastBitrate(),
isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(),
enableAuto: true,
maxVideoWidth
});
select.innerHTML = options.map(i => {
// render empty string instead of 0 for the auto option
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
}).join('');
select.value = appSettings.maxChromecastBitrate() || '';
}
function setMaxBitrateFromField(select, isInNetwork, mediatype) {
if (select.value) {
appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value);
appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false);
} else {
appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true);
}
}
function showHideQualityFields(context, user, apiClient) {
if (user.Policy.EnableVideoPlaybackTranscoding) {
context.querySelector('.videoQualitySection').classList.remove('hide');
} else {
context.querySelector('.videoQualitySection').classList.add('hide');
} }
if (appHost.supports('multiserver')) { return;
}
apiClient.getEndpointInfo().then(endpointInfo => {
if (endpointInfo.IsInNetwork) {
context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide');
context.querySelector('.fldVideoInternetQuality').classList.add('hide');
context.querySelector('.musicQualitySection').classList.add('hide');
} else {
context.querySelector('.fldVideoInNetworkQuality').classList.add('hide');
context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); context.querySelector('.fldVideoInternetQuality').classList.remove('hide');
if (user.Policy.EnableAudioPlaybackTranscoding) { if (user.Policy.EnableAudioPlaybackTranscoding) {
@@ -115,250 +133,229 @@ import template from './playbackSettings.template.html';
} else { } else {
context.querySelector('.musicQualitySection').classList.add('hide'); context.querySelector('.musicQualitySection').classList.add('hide');
} }
return;
} }
});
}
apiClient.getEndpointInfo().then(endpointInfo => { function showOrHideEpisodesField(context) {
if (endpointInfo.IsInNetwork) { if (browser.tizen || browser.web0s) {
context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); context.querySelector('.fldEpisodeAutoPlay').classList.add('hide');
return;
context.querySelector('.fldVideoInternetQuality').classList.add('hide');
context.querySelector('.musicQualitySection').classList.add('hide');
} else {
context.querySelector('.fldVideoInNetworkQuality').classList.add('hide');
context.querySelector('.fldVideoInternetQuality').classList.remove('hide');
if (user.Policy.EnableAudioPlaybackTranscoding) {
context.querySelector('.musicQualitySection').classList.remove('hide');
} else {
context.querySelector('.musicQualitySection').classList.add('hide');
}
}
});
} }
function showOrHideEpisodesField(context) { context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide');
if (browser.tizen || browser.web0s) { }
context.querySelector('.fldEpisodeAutoPlay').classList.add('hide');
return;
}
context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide'); function loadForm(context, user, userSettings, apiClient) {
const loggedInUserId = apiClient.getCurrentUserId();
const userId = user.Id;
showHideQualityFields(context, user, apiClient);
context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels();
apiClient.getCultures().then(allCultures => {
populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures);
context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || '';
context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false;
});
if (appHost.supports('externalplayerintent') && userId === loggedInUserId) {
context.querySelector('.fldExternalPlayer').classList.remove('hide');
} else {
context.querySelector('.fldExternalPlayer').classList.add('hide');
} }
function loadForm(context, user, userSettings, apiClient) { if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) {
const loggedInUserId = apiClient.getCurrentUserId(); context.querySelector('.qualitySections').classList.remove('hide');
const userId = user.Id;
showHideQualityFields(context, user, apiClient); if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) {
context.querySelector('.fldChromecastQuality').classList.remove('hide');
context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels();
apiClient.getCultures().then(allCultures => {
populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures);
context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || '';
context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false;
});
if (appHost.supports('externalplayerintent') && userId === loggedInUserId) {
context.querySelector('.fldExternalPlayer').classList.remove('hide');
} else { } else {
context.querySelector('.fldExternalPlayer').classList.add('hide');
}
if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) {
context.querySelector('.qualitySections').classList.remove('hide');
if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) {
context.querySelector('.fldChromecastQuality').classList.remove('hide');
} else {
context.querySelector('.fldChromecastQuality').classList.add('hide');
}
} else {
context.querySelector('.qualitySections').classList.add('hide');
context.querySelector('.fldChromecastQuality').classList.add('hide'); context.querySelector('.fldChromecastQuality').classList.add('hide');
} }
} else {
context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; context.querySelector('.qualitySections').classList.add('hide');
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); context.querySelector('.fldChromecastQuality').classList.add('hide');
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
context.querySelector('.chkEnableAudioNormalization').checked = userSettings.enableAudioNormalization();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio');
fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality'));
const selectChromecastVersion = context.querySelector('.selectChromecastVersion');
selectChromecastVersion.value = userSettings.chromecastVersion();
const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth');
selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth();
const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength');
fillSkipLengths(selectSkipForwardLength);
selectSkipForwardLength.value = userSettings.skipForwardLength();
const selectSkipBackLength = context.querySelector('.selectSkipBackLength');
fillSkipLengths(selectSkipBackLength);
selectSkipBackLength.value = userSettings.skipBackLength();
showOrHideEpisodesField(context);
loading.hide();
} }
function saveUser(context, user, userSettingsInstance, apiClient) { context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false;
appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
context.querySelector('.chkEnableAudioNormalization').checked = userSettings.enableAudioNormalization();
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio');
setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality'));
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio');
userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value); const selectChromecastVersion = context.querySelector('.selectChromecastVersion');
user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value; selectChromecastVersion.value = userSettings.chromecastVersion();
user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked;
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
userSettingsInstance.enableAudioNormalization(context.querySelector('.chkEnableAudioNormalization').checked);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
return apiClient.updateUserConfiguration(user.Id, user.Configuration); const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth');
selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth();
const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength');
fillSkipLengths(selectSkipForwardLength);
selectSkipForwardLength.value = userSettings.skipForwardLength();
const selectSkipBackLength = context.querySelector('.selectSkipBackLength');
fillSkipLengths(selectSkipBackLength);
selectSkipBackLength.value = userSettings.skipBackLength();
showOrHideEpisodesField(context);
loading.hide();
}
function saveUser(context, user, userSettingsInstance, apiClient) {
appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked);
appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value);
appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value);
setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio');
userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value);
user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value;
user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked;
user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked;
userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked);
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
userSettingsInstance.enableAudioNormalization(context.querySelector('.chkEnableAudioNormalization').checked);
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
return apiClient.updateUserConfiguration(user.Id, user.Configuration);
}
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show();
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
toast(globalize.translate('SettingsSaved'));
}
Events.trigger(instance, 'saved');
}, () => {
loading.hide();
});
});
}
function setSelectValue(select, value, defaultValue) {
select.value = value;
if (select.selectedIndex < 0) {
select.value = defaultValue;
}
}
function onMaxVideoWidthChange(e) {
const context = this.options.element;
const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality');
const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality');
const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality');
const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value;
const selectVideoInternetQualityValue = selectVideoInternetQuality.value;
const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value;
const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0;
fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth);
fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth);
fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth);
setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, '');
setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, '');
setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, '');
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId;
const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => {
const enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
// Disable default form submission
if (e) {
e.preventDefault();
}
return false;
}
function embed(options, self) {
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
} }
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self));
self.loadData();
if (options.autoFocus) {
focusManager.autoFocus(options.element);
}
}
class PlaybackSettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData() {
const self = this;
const context = self.options.element;
loading.show(); loading.show();
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
toast(globalize.translate('SettingsSaved'));
}
Events.trigger(instance, 'saved');
}, () => {
loading.hide();
});
});
}
function setSelectValue(select, value, defaultValue) {
select.value = value;
if (select.selectedIndex < 0) {
select.value = defaultValue;
}
}
function onMaxVideoWidthChange(e) {
const context = this.options.element;
const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality');
const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality');
const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality');
const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value;
const selectVideoInternetQualityValue = selectVideoInternetQuality.value;
const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value;
const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0;
fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth);
fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth);
fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth);
setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, '');
setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, '');
setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, '');
}
function onSubmit(e) {
const self = this;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userId = self.options.userId; const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings; const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(() => { apiClient.getUser(userId).then(user => {
const enableSaveConfirmation = self.options.enableSaveConfirmation; userSettings.setUserInfo(userId, apiClient).then(() => {
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); self.dataLoaded = true;
});
// Disable default form submission loadForm(context, user, userSettings, apiClient);
if (e) {
e.preventDefault();
}
return false;
}
function embed(options, self) {
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self));
self.loadData();
if (options.autoFocus) {
focusManager.autoFocus(options.element);
}
}
class PlaybackSettings {
constructor(options) {
this.options = options;
embed(options, this);
}
loadData() {
const self = this;
const context = self.options.element;
loading.show();
const userId = self.options.userId;
const apiClient = ServerConnections.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings;
apiClient.getUser(userId).then(user => {
userSettings.setUserInfo(userId, apiClient).then(() => {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
});
}); });
} });
submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
} }
/* eslint-enable indent */ submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
}
export default PlaybackSettings; export default PlaybackSettings;
+409 -406
View File
@@ -4,471 +4,476 @@ import Events from '../../utils/events.ts';
import layoutManager from '../layoutManager'; import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager'; import { playbackManager } from '../playback/playbackmanager';
import playMethodHelper from '../playback/playmethodhelper'; 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 './playerstats.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
/* eslint-disable indent */ function init(instance) {
const parent = document.createElement('div');
function init(instance) { parent.classList.add('playerStats');
const parent = document.createElement('div');
parent.classList.add('playerStats'); if (layoutManager.tv) {
parent.classList.add('playerStats-tv');
if (layoutManager.tv) {
parent.classList.add('playerStats-tv');
}
parent.classList.add('hide');
let button;
if (layoutManager.tv) {
button = '';
} else {
button = '<button type="button" is="paper-icon-button-light" class="playerStats-closeButton"><span class="material-icons close" aria-hidden="true"></span></button>';
}
const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content';
parent.innerHTML = '<div class="' + contentClass + '">' + button + '<div class="playerStats-stats"></div></div>';
button = parent.querySelector('.playerStats-closeButton');
if (button) {
button.addEventListener('click', onCloseButtonClick.bind(instance));
}
document.body.appendChild(parent);
instance.element = parent;
} }
function onCloseButtonClick() { parent.classList.add('hide');
this.enabled(false);
let button;
if (layoutManager.tv) {
button = '';
} else {
button = '<button type="button" is="paper-icon-button-light" class="playerStats-closeButton"><span class="material-icons close" aria-hidden="true"></span></button>';
} }
function renderStats(elem, categories) { const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content';
elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) {
let categoryHtml = '';
const stats = category.stats; parent.innerHTML = '<div class="' + contentClass + '">' + button + '<div class="playerStats-stats"></div></div>';
if (stats.length && category.name) { button = parent.querySelector('.playerStats-closeButton');
categoryHtml += '<div class="playerStats-stat playerStats-stat-header">';
categoryHtml += '<div class="playerStats-stat-label">'; if (button) {
categoryHtml += category.name; button.addEventListener('click', onCloseButtonClick.bind(instance));
categoryHtml += '</div>';
categoryHtml += '<div class="playerStats-stat-value">';
categoryHtml += category.subText || '';
categoryHtml += '</div>';
categoryHtml += '</div>';
}
for (let i = 0, length = stats.length; i < length; i++) {
categoryHtml += '<div class="playerStats-stat">';
const stat = stats[i];
categoryHtml += '<div class="playerStats-stat-label">';
categoryHtml += stat.label;
categoryHtml += '</div>';
categoryHtml += '<div class="playerStats-stat-value">';
categoryHtml += stat.value;
categoryHtml += '</div>';
categoryHtml += '</div>';
}
return categoryHtml;
}).join('');
} }
function getSession(instance, player) { document.body.appendChild(parent);
const now = new Date().getTime();
if ((now - (instance.lastSessionTime || 0)) < 10000) { instance.element = parent;
return Promise.resolve(instance.lastSession); }
function onCloseButtonClick() {
this.enabled(false);
}
function renderStats(elem, categories) {
elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) {
let categoryHtml = '';
const stats = category.stats;
if (stats.length && category.name) {
categoryHtml += '<div class="playerStats-stat playerStats-stat-header">';
categoryHtml += '<div class="playerStats-stat-label">';
categoryHtml += category.name;
categoryHtml += '</div>';
categoryHtml += '<div class="playerStats-stat-value">';
categoryHtml += category.subText || '';
categoryHtml += '</div>';
categoryHtml += '</div>';
} }
const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); for (let i = 0, length = stats.length; i < length; i++) {
categoryHtml += '<div class="playerStats-stat">';
return apiClient.getSessions({ const stat = stats[i];
deviceId: apiClient.deviceId()
}).then(function (sessions) {
instance.lastSession = sessions[0] || {};
instance.lastSessionTime = new Date().getTime();
return Promise.resolve(instance.lastSession); categoryHtml += '<div class="playerStats-stat-label">';
}, function () { categoryHtml += stat.label;
return Promise.resolve({}); categoryHtml += '</div>';
categoryHtml += '<div class="playerStats-stat-value">';
categoryHtml += stat.value;
categoryHtml += '</div>';
categoryHtml += '</div>';
}
return categoryHtml;
}).join('');
}
function getSession(instance, player) {
const now = new Date().getTime();
if ((now - (instance.lastSessionTime || 0)) < 10000) {
return Promise.resolve(instance.lastSession);
}
const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId);
return apiClient.getSessions({
deviceId: apiClient.deviceId()
}).then(function (sessions) {
instance.lastSession = sessions[0] || {};
instance.lastSessionTime = new Date().getTime();
return Promise.resolve(instance.lastSession);
}, function () {
return Promise.resolve({});
});
}
function translateReason(reason) {
return globalize.translate('' + reason);
}
function getTranscodingStats(session, player, displayPlayMethod) {
const sessionStats = [];
let videoCodec;
let audioCodec;
let totalBitrate;
let audioChannels;
if (session.TranscodingInfo) {
videoCodec = session.TranscodingInfo.VideoCodec;
audioCodec = session.TranscodingInfo.AudioCodec;
totalBitrate = session.TranscodingInfo.Bitrate;
audioChannels = session.TranscodingInfo.AudioChannels;
}
if (videoCodec) {
sessionStats.push({
label: globalize.translate('LabelVideoCodec'),
value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase()
}); });
} }
function translateReason(reason) { if (audioCodec) {
return globalize.translate('' + reason); sessionStats.push({
label: globalize.translate('LabelAudioCodec'),
value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase()
});
} }
function getTranscodingStats(session, player, displayPlayMethod) { if (displayPlayMethod === 'Transcode') {
const sessionStats = [];
let videoCodec;
let audioCodec;
let totalBitrate;
let audioChannels;
if (session.TranscodingInfo) {
videoCodec = session.TranscodingInfo.VideoCodec;
audioCodec = session.TranscodingInfo.AudioCodec;
totalBitrate = session.TranscodingInfo.Bitrate;
audioChannels = session.TranscodingInfo.AudioChannels;
}
if (videoCodec) {
sessionStats.push({
label: globalize.translate('LabelVideoCodec'),
value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase()
});
}
if (audioCodec) {
sessionStats.push({
label: globalize.translate('LabelAudioCodec'),
value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase()
});
}
if (displayPlayMethod === 'Transcode') {
if (audioChannels) {
sessionStats.push({
label: globalize.translate('LabelAudioChannels'),
value: audioChannels
});
}
if (totalBitrate) {
sessionStats.push({
label: globalize.translate('LabelBitrate'),
value: getDisplayBitrate(totalBitrate)
});
}
if (session.TranscodingInfo.CompletionPercentage) {
sessionStats.push({
label: globalize.translate('LabelTranscodingProgress'),
value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%'
});
}
if (session.TranscodingInfo.Framerate) {
sessionStats.push({
label: globalize.translate('LabelTranscodingFramerate'),
value: session.TranscodingInfo.Framerate + ' fps'
});
}
if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) {
sessionStats.push({
label: globalize.translate('LabelReasonForTranscoding'),
value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('<br/>')
});
}
if (session.TranscodingInfo.HardwareAccelerationType) {
sessionStats.push({
label: globalize.translate('LabelHardwareEncoding'),
value: session.TranscodingInfo.HardwareAccelerationType
});
}
}
return sessionStats;
}
function getDisplayBitrate(bitrate) {
if (bitrate > 1000000) {
return (bitrate / 1000000).toFixed(1) + ' Mbps';
} else {
return Math.floor(bitrate / 1000) + ' kbps';
}
}
function getReadableSize(size) {
if (size >= 1073741824) {
return parseFloat((size / 1073741824).toFixed(1)) + ' GiB';
} else if (size >= 1048576) {
return parseFloat((size / 1048576).toFixed(1)) + ' MiB';
} else {
return Math.floor(size / 1024) + ' KiB';
}
}
function getMediaSourceStats(session, player) {
const sessionStats = [];
const mediaSource = playbackManager.currentMediaSource(player) || {};
const totalBitrate = mediaSource.Bitrate;
const mediaFileSize = mediaSource.Size;
if (mediaSource.Container) {
sessionStats.push({
label: globalize.translate('LabelProfileContainer'),
value: mediaSource.Container
});
}
if (mediaFileSize) {
sessionStats.push({
label: globalize.translate('LabelSize'),
value: getReadableSize(mediaFileSize)
});
}
if (totalBitrate) {
sessionStats.push({
label: globalize.translate('LabelBitrate'),
value: getDisplayBitrate(totalBitrate)
});
}
const mediaStreams = mediaSource.MediaStreams || [];
const videoStream = mediaStreams.filter(function (s) {
return s.Type === 'Video';
})[0] || {};
const videoCodec = videoStream.Codec;
const audioStreamIndex = playbackManager.getAudioStreamIndex(player);
const audioStream = playbackManager.audioTracks(player).filter(function (s) {
return s.Type === 'Audio' && s.Index === audioStreamIndex;
})[0] || {};
const audioCodec = audioStream.Codec;
const audioChannels = audioStream.Channels;
const videoInfos = [];
if (videoCodec) {
videoInfos.push(videoCodec.toUpperCase());
}
if (videoStream.Profile) {
videoInfos.push(videoStream.Profile);
}
if (videoInfos.length) {
sessionStats.push({
label: globalize.translate('LabelVideoCodec'),
value: videoInfos.join(' ')
});
}
if (videoStream.BitRate) {
sessionStats.push({
label: globalize.translate('LabelVideoBitrate'),
value: getDisplayBitrate(videoStream.BitRate)
});
}
if (videoStream.VideoRangeType) {
sessionStats.push({
label: globalize.translate('LabelVideoRangeType'),
value: videoStream.VideoRangeType
});
}
const audioInfos = [];
if (audioCodec) {
audioInfos.push(audioCodec.toUpperCase());
}
if (audioStream.Profile) {
audioInfos.push(audioStream.Profile);
}
if (audioInfos.length) {
sessionStats.push({
label: globalize.translate('LabelAudioCodec'),
value: audioInfos.join(' ')
});
}
if (audioStream.BitRate) {
sessionStats.push({
label: globalize.translate('LabelAudioBitrate'),
value: getDisplayBitrate(audioStream.BitRate)
});
}
if (audioChannels) { if (audioChannels) {
sessionStats.push({ sessionStats.push({
label: globalize.translate('LabelAudioChannels'), label: globalize.translate('LabelAudioChannels'),
value: audioChannels value: audioChannels
}); });
} }
if (totalBitrate) {
if (audioStream.SampleRate) {
sessionStats.push({ sessionStats.push({
label: globalize.translate('LabelAudioSampleRate'), label: globalize.translate('LabelBitrate'),
value: audioStream.SampleRate + ' Hz' value: getDisplayBitrate(totalBitrate)
}); });
} }
if (session.TranscodingInfo.CompletionPercentage) {
if (audioStream.BitDepth) {
sessionStats.push({ sessionStats.push({
label: globalize.translate('LabelAudioBitDepth'), label: globalize.translate('LabelTranscodingProgress'),
value: audioStream.BitDepth value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%'
});
}
if (session.TranscodingInfo.Framerate) {
sessionStats.push({
label: globalize.translate('LabelTranscodingFramerate'),
value: session.TranscodingInfo.Framerate + ' fps'
});
}
if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) {
sessionStats.push({
label: globalize.translate('LabelReasonForTranscoding'),
value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('<br/>')
});
}
if (session.TranscodingInfo.HardwareAccelerationType) {
sessionStats.push({
label: globalize.translate('LabelHardwareEncoding'),
value: session.TranscodingInfo.HardwareAccelerationType
}); });
} }
return sessionStats;
} }
function getSyncPlayStats() { return sessionStats;
const syncStats = []; }
const stats = SyncPlay.Manager.getStats();
syncStats.push({ function getDisplayBitrate(bitrate) {
label: globalize.translate('LabelSyncPlayTimeSyncDevice'), if (bitrate > 1000000) {
value: stats.TimeSyncDevice return (bitrate / 1000000).toFixed(1) + ' Mbps';
} else {
return Math.floor(bitrate / 1000) + ' kbps';
}
}
function getReadableSize(size) {
if (size >= 1073741824) {
return parseFloat((size / 1073741824).toFixed(1)) + ' GiB';
} else if (size >= 1048576) {
return parseFloat((size / 1048576).toFixed(1)) + ' MiB';
} else {
return Math.floor(size / 1024) + ' KiB';
}
}
function getMediaSourceStats(session, player) {
const sessionStats = [];
const mediaSource = playbackManager.currentMediaSource(player) || {};
const totalBitrate = mediaSource.Bitrate;
const mediaFileSize = mediaSource.Size;
if (mediaSource.Container) {
sessionStats.push({
label: globalize.translate('LabelProfileContainer'),
value: mediaSource.Container
}); });
syncStats.push({
// TODO: clean old string 'LabelSyncPlayTimeOffset' from translations.
label: globalize.translate('LabelSyncPlayTimeSyncOffset'),
value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit')
});
syncStats.push({
label: globalize.translate('LabelSyncPlayPlaybackDiff'),
value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit')
});
syncStats.push({
label: globalize.translate('LabelSyncPlaySyncMethod'),
value: stats.SyncMethod
});
return syncStats;
} }
function getStats(instance, player) { if (mediaFileSize) {
const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); sessionStats.push({
const sessionPromise = getSession(instance, player); label: globalize.translate('LabelSize'),
value: getReadableSize(mediaFileSize)
});
}
return Promise.all([statsPromise, sessionPromise]).then(function (responses) { if (totalBitrate) {
const playerStatsResult = responses[0]; sessionStats.push({
const playerStats = playerStatsResult.categories || []; label: globalize.translate('LabelBitrate'),
const session = responses[1]; value: getDisplayBitrate(totalBitrate)
});
}
const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); const mediaStreams = mediaSource.MediaStreams || [];
let localizedDisplayMethod = displayPlayMethod; const videoStream = mediaStreams.filter(function (s) {
return s.Type === 'Video';
})[0] || {};
if (displayPlayMethod === 'DirectPlay') { const videoCodec = videoStream.Codec;
localizedDisplayMethod = globalize.translate('DirectPlaying');
} else if (displayPlayMethod === 'Remux') { const audioStreamIndex = playbackManager.getAudioStreamIndex(player);
localizedDisplayMethod = globalize.translate('Remuxing'); const audioStream = playbackManager.audioTracks(player).filter(function (s) {
} else if (displayPlayMethod === 'DirectStream') { return s.Type === 'Audio' && s.Index === audioStreamIndex;
localizedDisplayMethod = globalize.translate('DirectStreaming'); })[0] || {};
} else if (displayPlayMethod === 'Transcode') {
localizedDisplayMethod = globalize.translate('Transcoding'); const audioCodec = audioStream.Codec;
const audioChannels = audioStream.Channels;
const videoInfos = [];
if (videoCodec) {
videoInfos.push(videoCodec.toUpperCase());
}
if (videoStream.Profile) {
videoInfos.push(videoStream.Profile);
}
if (videoInfos.length) {
sessionStats.push({
label: globalize.translate('LabelVideoCodec'),
value: videoInfos.join(' ')
});
}
if (videoStream.BitRate) {
sessionStats.push({
label: globalize.translate('LabelVideoBitrate'),
value: getDisplayBitrate(videoStream.BitRate)
});
}
if (videoStream.VideoRangeType) {
sessionStats.push({
label: globalize.translate('LabelVideoRangeType'),
value: videoStream.VideoRangeType
});
}
const audioInfos = [];
if (audioCodec) {
audioInfos.push(audioCodec.toUpperCase());
}
if (audioStream.Profile) {
audioInfos.push(audioStream.Profile);
}
if (audioInfos.length) {
sessionStats.push({
label: globalize.translate('LabelAudioCodec'),
value: audioInfos.join(' ')
});
}
if (audioStream.BitRate) {
sessionStats.push({
label: globalize.translate('LabelAudioBitrate'),
value: getDisplayBitrate(audioStream.BitRate)
});
}
if (audioChannels) {
sessionStats.push({
label: globalize.translate('LabelAudioChannels'),
value: audioChannels
});
}
if (audioStream.SampleRate) {
sessionStats.push({
label: globalize.translate('LabelAudioSampleRate'),
value: audioStream.SampleRate + ' Hz'
});
}
if (audioStream.BitDepth) {
sessionStats.push({
label: globalize.translate('LabelAudioBitDepth'),
value: audioStream.BitDepth
});
}
return sessionStats;
}
function getSyncPlayStats() {
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
if (!SyncPlay?.Manager.isSyncPlayEnabled()) {
return [];
}
const syncStats = [];
const stats = SyncPlay.Manager.getStats();
syncStats.push({
label: globalize.translate('LabelSyncPlayTimeSyncDevice'),
value: stats.TimeSyncDevice
});
syncStats.push({
// TODO: clean old string 'LabelSyncPlayTimeOffset' from translations.
label: globalize.translate('LabelSyncPlayTimeSyncOffset'),
value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit')
});
syncStats.push({
label: globalize.translate('LabelSyncPlayPlaybackDiff'),
value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit')
});
syncStats.push({
label: globalize.translate('LabelSyncPlaySyncMethod'),
value: stats.SyncMethod
});
return syncStats;
}
function getStats(instance, player) {
const statsPromise = player.getStats ? player.getStats() : Promise.resolve({});
const sessionPromise = getSession(instance, player);
return Promise.all([statsPromise, sessionPromise]).then(function (responses) {
const playerStatsResult = responses[0];
const playerStats = playerStatsResult.categories || [];
const session = responses[1];
const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session);
let localizedDisplayMethod = displayPlayMethod;
if (displayPlayMethod === 'DirectPlay') {
localizedDisplayMethod = globalize.translate('DirectPlaying');
} else if (displayPlayMethod === 'Remux') {
localizedDisplayMethod = globalize.translate('Remuxing');
} else if (displayPlayMethod === 'DirectStream') {
localizedDisplayMethod = globalize.translate('DirectStreaming');
} else if (displayPlayMethod === 'Transcode') {
localizedDisplayMethod = globalize.translate('Transcoding');
}
const baseCategory = {
stats: [],
name: globalize.translate('LabelPlaybackInfo')
};
baseCategory.stats.unshift({
label: globalize.translate('LabelPlayMethod'),
value: localizedDisplayMethod
});
baseCategory.stats.unshift({
label: globalize.translate('LabelPlayer'),
value: player.name
});
const categories = [];
categories.push(baseCategory);
for (let i = 0, length = playerStats.length; i < length; i++) {
const category = playerStats[i];
if (category.type === 'audio') {
category.name = globalize.translate('LabelAudioInfo');
} else if (category.type === 'video') {
category.name = globalize.translate('LabelVideoInfo');
} }
categories.push(category);
}
const baseCategory = { let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo');
stats: [], if (displayPlayMethod === 'Remux') {
name: globalize.translate('LabelPlaybackInfo') localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo');
}; } else if (displayPlayMethod === 'DirectStream') {
localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo');
baseCategory.stats.unshift({ }
label: globalize.translate('LabelPlayMethod'),
value: localizedDisplayMethod
});
baseCategory.stats.unshift({
label: globalize.translate('LabelPlayer'),
value: player.name
});
const categories = [];
categories.push(baseCategory);
for (let i = 0, length = playerStats.length; i < length; i++) {
const category = playerStats[i];
if (category.type === 'audio') {
category.name = globalize.translate('LabelAudioInfo');
} else if (category.type === 'video') {
category.name = globalize.translate('LabelVideoInfo');
}
categories.push(category);
}
let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo');
if (displayPlayMethod === 'Remux') {
localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo');
} else if (displayPlayMethod === 'DirectStream') {
localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo');
}
if (session.TranscodingInfo) {
categories.push({
stats: getTranscodingStats(session, player, displayPlayMethod),
name: localizedTranscodingInfo
});
}
if (session.TranscodingInfo) {
categories.push({ categories.push({
stats: getMediaSourceStats(session, player), stats: getTranscodingStats(session, player, displayPlayMethod),
name: globalize.translate('LabelOriginalMediaInfo') name: localizedTranscodingInfo
}); });
}
const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); categories.push({
if (SyncPlay.Manager.isSyncPlayEnabled() && apiClient.isMinServerVersion('10.6.0')) { stats: getMediaSourceStats(session, player),
categories.push({ name: globalize.translate('LabelOriginalMediaInfo')
stats: getSyncPlayStats(),
name: globalize.translate('LabelSyncPlayInfo')
});
}
return Promise.resolve(categories);
}); });
const syncPlayStats = getSyncPlayStats();
if (syncPlayStats.length > 0) {
categories.push({
stats: syncPlayStats,
name: globalize.translate('LabelSyncPlayInfo')
});
}
return Promise.resolve(categories);
});
}
function renderPlayerStats(instance, player) {
const now = new Date().getTime();
if ((now - (instance.lastRender || 0)) < 700) {
return;
} }
function renderPlayerStats(instance, player) { instance.lastRender = now;
const now = new Date().getTime();
if ((now - (instance.lastRender || 0)) < 700) { getStats(instance, player).then(function (stats) {
const elem = instance.element;
if (!elem) {
return; return;
} }
instance.lastRender = now; renderStats(elem, stats);
});
}
getStats(instance, player).then(function (stats) { function bindEvents(instance, player) {
const elem = instance.element; const localOnTimeUpdate = function () {
if (!elem) { renderPlayerStats(instance, player);
return; };
}
renderStats(elem, stats); instance.onTimeUpdate = localOnTimeUpdate;
}); Events.on(player, 'timeupdate', localOnTimeUpdate);
} }
function bindEvents(instance, player) { function unbindEvents(instance, player) {
const localOnTimeUpdate = function () { const localOnTimeUpdate = instance.onTimeUpdate;
renderPlayerStats(instance, player);
}; if (localOnTimeUpdate) {
Events.off(player, 'timeupdate', localOnTimeUpdate);
instance.onTimeUpdate = localOnTimeUpdate;
Events.on(player, 'timeupdate', localOnTimeUpdate);
}
function unbindEvents(instance, player) {
const localOnTimeUpdate = instance.onTimeUpdate;
if (localOnTimeUpdate) {
Events.off(player, 'timeupdate', localOnTimeUpdate);
}
} }
}
class PlayerStats { class PlayerStats {
constructor(options) { constructor(options) {
@@ -520,6 +525,4 @@ class PlayerStats {
} }
} }
/* eslint-enable indent */
export default PlayerStats; export default PlayerStats;
+234 -233
View File
@@ -4,10 +4,12 @@ import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading'; import loading from '../loading/loading';
import layoutManager from '../layoutManager'; import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager'; import { playbackManager } from '../playback/playbackmanager';
import SyncPlay from '../../plugins/syncPlay/core'; import { pluginManager } from '../pluginManager';
import * as userSettings from '../../scripts/settings/userSettings'; import * as userSettings from '../../scripts/settings/userSettings';
import { appRouter } from '../appRouter'; import { appRouter } from '../appRouter';
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import { PluginType } from '../../types/plugin.ts';
import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/emby-button';
import '../../elements/emby-input/emby-input'; import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light'; import '../../elements/emby-button/paper-icon-button-light';
@@ -16,267 +18,266 @@ import 'material-design-icons-iconfont';
import '../formdialog.scss'; import '../formdialog.scss';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
/* eslint-disable indent */ let currentServerId;
let currentServerId; function onSubmit(e) {
const panel = dom.parentWithClass(this, 'dialog');
function onSubmit(e) { const playlistId = panel.querySelector('#selectPlaylistToAddTo').value;
const panel = dom.parentWithClass(this, 'dialog'); const apiClient = ServerConnections.getApiClient(currentServerId);
const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; if (playlistId) {
const apiClient = ServerConnections.getApiClient(currentServerId); userSettings.set('playlisteditor-lastplaylistid', playlistId);
addToPlaylist(apiClient, panel, playlistId);
if (playlistId) { } else {
userSettings.set('playlisteditor-lastplaylistid', playlistId); createPlaylist(apiClient, panel);
addToPlaylist(apiClient, panel, playlistId);
} else {
createPlaylist(apiClient, panel);
}
e.preventDefault();
return false;
} }
function createPlaylist(apiClient, dlg) { e.preventDefault();
loading.show(); return false;
}
const url = apiClient.getUrl('Playlists', { function createPlaylist(apiClient, dlg) {
Name: dlg.querySelector('#txtNewPlaylistName').value, loading.show();
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
userId: apiClient.getCurrentUserId()
}); const url = apiClient.getUrl('Playlists', {
Name: dlg.querySelector('#txtNewPlaylistName').value,
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
userId: apiClient.getCurrentUserId()
apiClient.ajax({ });
type: 'POST',
url: url,
dataType: 'json',
contentType: 'application/json'
}).then(result => {
loading.hide();
const id = result.Id;
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToPlaylist(apiClient, id);
});
}
function redirectToPlaylist(apiClient, id) {
appRouter.showItem(id, apiClient.serverId());
}
function addToPlaylist(apiClient, dlg, id) {
const itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
if (id === 'queue') {
playbackManager.queue({
serverId: apiClient.serverId(),
ids: itemIds.split(',')
});
dlg.submitted = true;
dialogHelper.close(dlg);
return;
}
loading.show();
const url = apiClient.getUrl(`Playlists/${id}/Items`, {
Ids: itemIds,
userId: apiClient.getCurrentUserId()
});
apiClient.ajax({
type: 'POST',
url: url
}).then(() => {
loading.hide();
dlg.submitted = true;
dialogHelper.close(dlg);
});
}
function triggerChange(select) {
select.dispatchEvent(new CustomEvent('change', {}));
}
function populatePlaylists(editorOptions, panel) {
const select = panel.querySelector('#selectPlaylistToAddTo');
apiClient.ajax({
type: 'POST',
url: url,
dataType: 'json',
contentType: 'application/json'
}).then(result => {
loading.hide(); loading.hide();
panel.querySelector('.newPlaylistInfo').classList.add('hide'); const id = result.Id;
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToPlaylist(apiClient, id);
});
}
const options = { function redirectToPlaylist(apiClient, id) {
Recursive: true, appRouter.showItem(id, apiClient.serverId());
IncludeItemTypes: 'Playlist', }
SortBy: 'SortName',
EnableTotalRecordCount: false
};
const apiClient = ServerConnections.getApiClient(currentServerId); function addToPlaylist(apiClient, dlg, id) {
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { const itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
let html = '';
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay.Manager.isSyncPlayEnabled()) { if (id === 'queue') {
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`; playbackManager.queue({
} serverId: apiClient.serverId(),
ids: itemIds.split(',')
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += result.Items.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
});
select.innerHTML = html;
let defaultValue = editorOptions.defaultValue;
if (!defaultValue) {
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
}
select.value = defaultValue === 'new' ? '' : defaultValue;
// If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
if (!select.value) {
select.value = '';
}
triggerChange(select);
loading.hide();
}); });
dlg.submitted = true;
dialogHelper.close(dlg);
return;
} }
function getEditorHtml(items) { loading.show();
const url = apiClient.getUrl(`Playlists/${id}/Items`, {
Ids: itemIds,
userId: apiClient.getCurrentUserId()
});
apiClient.ajax({
type: 'POST',
url: url
}).then(() => {
loading.hide();
dlg.submitted = true;
dialogHelper.close(dlg);
});
}
function triggerChange(select) {
select.dispatchEvent(new CustomEvent('change', {}));
}
function populatePlaylists(editorOptions, panel) {
const select = panel.querySelector('#selectPlaylistToAddTo');
loading.hide();
panel.querySelector('.newPlaylistInfo').classList.add('hide');
const options = {
Recursive: true,
IncludeItemTypes: 'Playlist',
SortBy: 'SortName',
EnableTotalRecordCount: false
};
const apiClient = ServerConnections.getApiClient(currentServerId);
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
let html = ''; let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">'; if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
html += '<div class="dialogContentInner dialog-content-centered">'; html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
html += '<form style="margin:auto;">'; }
html += '<div class="fldSelectPlaylist selectContainer">'; html += `<option value="">${globalize.translate('OptionNew')}</option>`;
let autoFocus = items.length ? ' autofocus' : '';
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
html += '</div>';
html += '<div class="newPlaylistInfo">'; html += result.Items.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
html += '<div class="inputContainer">';
autoFocus = items.length ? '' : ' autofocus';
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
html += '</div>';
// newPlaylistInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, options, items) {
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newPlaylistInfo').classList.add('hide');
content.querySelector('#txtNewPlaylistName').removeAttribute('required');
} else {
content.querySelector('.newPlaylistInfo').classList.remove('hide');
content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required');
}
}); });
content.querySelector('form').addEventListener('submit', onSubmit); select.innerHTML = html;
content.querySelector('.fldSelectedItemIds', content).value = items.join(','); let defaultValue = editorOptions.defaultValue;
if (!defaultValue) {
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
}
select.value = defaultValue === 'new' ? '' : defaultValue;
if (items.length) { // If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
content.querySelector('.fldSelectPlaylist').classList.remove('hide'); if (!select.value) {
populatePlaylists(options, content); select.value = '';
}
triggerChange(select);
loading.hide();
});
}
function getEditorHtml(items) {
let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">';
html += '<div class="fldSelectPlaylist selectContainer">';
let autoFocus = items.length ? ' autofocus' : '';
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
html += '</div>';
html += '<div class="newPlaylistInfo">';
html += '<div class="inputContainer">';
autoFocus = items.length ? '' : ' autofocus';
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
html += '</div>';
// newPlaylistInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, options, items) {
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newPlaylistInfo').classList.add('hide');
content.querySelector('#txtNewPlaylistName').removeAttribute('required');
} else { } else {
content.querySelector('.fldSelectPlaylist').classList.add('hide'); content.querySelector('.newPlaylistInfo').classList.remove('hide');
content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required');
const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo');
selectPlaylistToAddTo.innerHTML = '';
selectPlaylistToAddTo.value = '';
triggerChange(selectPlaylistToAddTo);
} }
} });
function centerFocus(elem, horiz, on) { content.querySelector('form').addEventListener('submit', onSubmit);
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off'; content.querySelector('.fldSelectedItemIds', content).value = items.join(',');
scrollHelper.centerFocus[fn](elem, horiz);
if (items.length) {
content.querySelector('.fldSelectPlaylist').classList.remove('hide');
populatePlaylists(options, content);
} else {
content.querySelector('.fldSelectPlaylist').classList.add('hide');
const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo');
selectPlaylistToAddTo.innerHTML = '';
selectPlaylistToAddTo.value = '';
triggerChange(selectPlaylistToAddTo);
}
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export class showEditor {
constructor(options) {
const items = options.items || {};
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = globalize.translate('HeaderAddToPlaylist');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml(items);
dlg.innerHTML = html;
initEditor(dlg, options, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
}); });
} }
}
export class showEditor {
constructor(options) {
const items = options.items || {};
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = globalize.translate('HeaderAddToPlaylist');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml(items);
dlg.innerHTML = html;
initEditor(dlg, options, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
}
}
/* eslint-enable indent */
export default showEditor; export default showEditor;
+8 -3
View File
@@ -119,9 +119,14 @@ class PluginManager {
} }
ofType(type) { ofType(type) {
return this.pluginsList.filter((o) => { return this.pluginsList.filter(plugin => plugin.type === type);
return o.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) { #mapRoute(plugin, route) {
@@ -141,7 +141,7 @@ function onManageRecordingClick() {
} }
const self = this; const self = this;
import('./recordingeditor').then(({default: recordingEditor}) => { import('./recordingeditor').then(({ default: recordingEditor }) => {
recordingEditor.show(self.TimerId, options.serverId, { recordingEditor.show(self.TimerId, options.serverId, {
enableCancel: false enableCancel: false
}).then(function () { }).then(function () {
@@ -159,7 +159,7 @@ function onManageSeriesRecordingClick() {
const self = this; const self = this;
import('./seriesrecordingeditor').then(({default: seriesRecordingEditor}) => { import('./seriesrecordingeditor').then(({ default: seriesRecordingEditor }) => {
seriesRecordingEditor.show(self.SeriesTimerId, options.serverId, { seriesRecordingEditor.show(self.SeriesTimerId, options.serverId, {
enableCancel: false enableCancel: false
+18 -13
View File
@@ -325,10 +325,11 @@ export default function () {
if (layoutManager.mobile) { if (layoutManager.mobile) {
const playingVideo = playbackManager.isPlayingVideo() && item !== null; const playingVideo = playbackManager.isPlayingVideo() && item !== null;
const playingAudio = !playbackManager.isPlayingVideo() && item !== null; const playingAudio = !playbackManager.isPlayingVideo() && item !== null;
buttonVisible(context.querySelector('.btnRepeat'), playingAudio); const playingAudioBook = playingAudio && item.Type == 'AudioBook';
buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio); buttonVisible(context.querySelector('.btnRepeat'), playingAudio && !playingAudioBook);
buttonVisible(context.querySelector('.btnRewind'), playingVideo); buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio && !playingAudioBook);
buttonVisible(context.querySelector('.btnFastForward'), playingVideo); buttonVisible(context.querySelector('.btnRewind'), playingVideo || playingAudioBook);
buttonVisible(context.querySelector('.btnFastForward'), playingVideo || playingAudioBook);
buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnShuffleQueue'), playingVideo); buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnShuffleQueue'), playingVideo);
buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnRepeat'), playingVideo); buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnRepeat'), playingVideo);
} else { } else {
@@ -763,18 +764,22 @@ export default function () {
context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) {
if (currentPlayer) { if (currentPlayer) {
if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') {
// Cancel this event if doubleclick is fired // Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event
if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { if (e.detail > 1 ) {
return;
}
// Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move
// to the previous track, unless we are at the first track so no previous track exists.
if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) {
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager, however, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
return; return;
} }
playbackManager.seekPercent(0, currentPlayer);
// This is done automatically by playbackManager. However, setting this here gives instant visual feedback.
// TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround.
positionSlider.value = 0;
} else {
playbackManager.previousTrack(currentPlayer);
} }
playbackManager.previousTrack(currentPlayer);
} }
}); });
+356 -360
View File
@@ -1,5 +1,3 @@
/* eslint-disable indent */
/** /**
* Module for controlling scroll behavior. * Module for controlling scroll behavior.
* @module components/scrollManager * @module components/scrollManager
@@ -9,50 +7,50 @@ import dom from '../scripts/dom';
import browser from '../scripts/browser'; import browser from '../scripts/browser';
import layoutManager from './layoutManager'; import layoutManager from './layoutManager';
/** /**
* Scroll time in ms. * Scroll time in ms.
*/ */
const ScrollTime = 270; const ScrollTime = 270;
/** /**
* Epsilon for comparing values. * Epsilon for comparing values.
*/ */
const Epsilon = 1e-6; const Epsilon = 1e-6;
// FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers // FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers
/** /**
* Returns minimum vertical scroll. * Returns minimum vertical scroll.
* Scroll less than that value will be zeroed. * Scroll less than that value will be zeroed.
* *
* @return {number} Minimum vertical scroll. * @return {number} Minimum vertical scroll.
*/ */
function minimumScrollY() { function minimumScrollY() {
const topMenu = document.querySelector('.headerTop'); const topMenu = document.querySelector('.headerTop');
if (topMenu) { if (topMenu) {
return topMenu.clientHeight; return topMenu.clientHeight;
}
return 0;
}
const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style;
let supportsScrollToOptions = false;
try {
const elem = document.createElement('div');
const opts = Object.defineProperty({}, 'behavior', {
// eslint-disable-next-line getter-return
get: function () {
supportsScrollToOptions = true;
} }
return 0; });
}
const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; elem.scrollTo(opts);
} catch (e) {
console.error('error checking ScrollToOptions support');
}
let supportsScrollToOptions = false; /**
try {
const elem = document.createElement('div');
const opts = Object.defineProperty({}, 'behavior', {
// eslint-disable-next-line getter-return
get: function () {
supportsScrollToOptions = true;
}
});
elem.scrollTo(opts);
} catch (e) {
console.error('error checking ScrollToOptions support');
}
/**
* Returns value clamped by range [min, max]. * Returns value clamped by range [min, max].
* *
* @param {number} value - Clamped value. * @param {number} value - Clamped value.
@@ -60,16 +58,16 @@ import layoutManager from './layoutManager';
* @param {number} max - Ending of range. * @param {number} max - Ending of range.
* @return {number} Clamped value. * @return {number} Clamped value.
*/ */
function clamp(value, min, max) { function clamp(value, min, max) {
if (value <= min) { if (value <= min) {
return min; return min;
} else if (value >= max) { } else if (value >= max) {
return max; return max;
}
return value;
} }
return value;
}
/** /**
* Returns the required delta to fit range 1 into range 2. * Returns the required delta to fit range 1 into range 2.
* In case of range 1 is bigger than range 2 returns delta to fit most out of range part. * In case of range 1 is bigger than range 2 returns delta to fit most out of range part.
* *
@@ -79,28 +77,28 @@ import layoutManager from './layoutManager';
* @param {number} end2 - Ending of range 2. * @param {number} end2 - Ending of range 2.
* @return {number} Delta: <0 move range1 to the left, >0 - to the right. * @return {number} Delta: <0 move range1 to the left, >0 - to the right.
*/ */
function fitRange(begin1, end1, begin2, end2) { function fitRange(begin1, end1, begin2, end2) {
const delta1 = begin1 - begin2; const delta1 = begin1 - begin2;
const delta2 = end2 - end1; const delta2 = end2 - end1;
if (delta1 < 0 && delta1 < delta2) { if (delta1 < 0 && delta1 < delta2) {
return -delta1; return -delta1;
} else if (delta2 < 0) { } else if (delta2 < 0) {
return delta2; return delta2;
}
return 0;
} }
return 0;
}
/** /**
* Ease value. * Ease value.
* *
* @param {number} t - Value in range [0, 1]. * @param {number} t - Value in range [0, 1].
* @return {number} Eased value in range [0, 1]. * @return {number} Eased value in range [0, 1].
*/ */
function ease(t) { function ease(t) {
return t * (2 - t); // easeOutQuad === ease-out return t * (2 - t); // easeOutQuad === ease-out
} }
/** /**
* @typedef {Object} Rect * @typedef {Object} Rect
* @property {number} left - X coordinate of top-left corner. * @property {number} left - X coordinate of top-left corner.
* @property {number} top - Y coordinate of top-left corner. * @property {number} top - Y coordinate of top-left corner.
@@ -108,7 +106,7 @@ import layoutManager from './layoutManager';
* @property {number} height - Height. * @property {number} height - Height.
*/ */
/** /**
* Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * Document scroll wrapper helps to unify scrolling and fix issues of some browsers.
* *
* webOS 2 Browser: scrolls documentElement (and window), but body has a scroll size * webOS 2 Browser: scrolls documentElement (and window), but body has a scroll size
@@ -121,157 +119,157 @@ import layoutManager from './layoutManager';
* *
* Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement
*/ */
class DocumentScroller { class DocumentScroller {
/** /**
* Horizontal scroll position. * Horizontal scroll position.
* @type {number} * @type {number}
*/ */
get scrollLeft() { get scrollLeft() {
return window.pageXOffset; return window.pageXOffset;
} }
set scrollLeft(val) { set scrollLeft(val) {
window.scroll(val, window.pageYOffset); window.scroll(val, window.pageYOffset);
} }
/** /**
* Vertical scroll position. * Vertical scroll position.
* @type {number} * @type {number}
*/ */
get scrollTop() { get scrollTop() {
return window.pageYOffset; return window.pageYOffset;
} }
set scrollTop(val) { set scrollTop(val) {
window.scroll(window.pageXOffset, val); window.scroll(window.pageXOffset, val);
} }
/** /**
* Horizontal scroll size (scroll width). * Horizontal scroll size (scroll width).
* @type {number} * @type {number}
*/ */
get scrollWidth() { get scrollWidth() {
return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth);
} }
/** /**
* Vertical scroll size (scroll height). * Vertical scroll size (scroll height).
* @type {number} * @type {number}
*/ */
get scrollHeight() { get scrollHeight() {
return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
} }
/** /**
* Horizontal client size (client width). * Horizontal client size (client width).
* @type {number} * @type {number}
*/ */
get clientWidth() { get clientWidth() {
return Math.min(document.documentElement.clientWidth, document.body.clientWidth); return Math.min(document.documentElement.clientWidth, document.body.clientWidth);
} }
/** /**
* Vertical client size (client height). * Vertical client size (client height).
* @type {number} * @type {number}
*/ */
get clientHeight() { get clientHeight() {
return Math.min(document.documentElement.clientHeight, document.body.clientHeight); return Math.min(document.documentElement.clientHeight, document.body.clientHeight);
} }
/** /**
* Returns attribute value. * Returns attribute value.
* @param {string} attributeName - Attibute name. * @param {string} attributeName - Attibute name.
* @return {string} Attibute value. * @return {string} Attibute value.
*/ */
getAttribute(attributeName) { getAttribute(attributeName) {
return document.body.getAttribute(attributeName); return document.body.getAttribute(attributeName);
}
/**
* Returns bounding client rect.
* @return {Rect} Bounding client rect.
*/
getBoundingClientRect() {
// Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport
return {
left: 0,
top: 0,
width: this.clientWidth,
height: this.clientHeight
};
}
/**
* Scrolls window.
* @param {...mixed} args See window.scrollTo.
*/
scrollTo() {
window.scrollTo.apply(window, arguments);
}
} }
/** /**
* Default (document) scroller. * Returns bounding client rect.
*/ * @return {Rect} Bounding client rect.
const documentScroller = new DocumentScroller(); */
getBoundingClientRect() {
const scrollerHints = { // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport
x: { return {
nameScroll: 'scrollWidth', left: 0,
nameClient: 'clientWidth', top: 0,
nameStyle: 'overflowX', width: this.clientWidth,
nameScrollMode: 'data-scroll-mode-x' height: this.clientHeight
}, };
y: { }
nameScroll: 'scrollHeight',
nameClient: 'clientHeight',
nameStyle: 'overflowY',
nameScrollMode: 'data-scroll-mode-y'
}
};
/** /**
* Scrolls window.
* @param {...mixed} args See window.scrollTo.
*/
scrollTo() {
window.scrollTo.apply(window, arguments);
}
}
/**
* Default (document) scroller.
*/
const documentScroller = new DocumentScroller();
const scrollerHints = {
x: {
nameScroll: 'scrollWidth',
nameClient: 'clientWidth',
nameStyle: 'overflowX',
nameScrollMode: 'data-scroll-mode-x'
},
y: {
nameScroll: 'scrollHeight',
nameClient: 'clientHeight',
nameStyle: 'overflowY',
nameScrollMode: 'data-scroll-mode-y'
}
};
/**
* Returns parent element that can be scrolled. If no such, returns document scroller. * Returns parent element that can be scrolled. If no such, returns document scroller.
* *
* @param {HTMLElement} element - Element for which parent is being searched. * @param {HTMLElement} element - Element for which parent is being searched.
* @param {boolean} vertical - Search for vertical scrollable parent. * @param {boolean} vertical - Search for vertical scrollable parent.
* @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller. * @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller.
*/ */
function getScrollableParent(element, vertical) { function getScrollableParent(element, vertical) {
if (element) { if (element) {
const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x;
let parent = element.parentElement; let parent = element.parentElement;
while (parent && parent !== document.body) { while (parent && parent !== document.body) {
const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode);
// Stop on self-scrolled containers // Stop on self-scrolled containers
if (scrollMode === 'custom') { if (scrollMode === 'custom') {
return parent; return parent;
}
const styles = window.getComputedStyle(parent);
// Stop on fixed parent
if (styles.position === 'fixed') {
return parent;
}
const overflow = styles[scrollerHint.nameStyle];
if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) {
return parent;
}
parent = parent.parentElement;
} }
}
return documentScroller; const styles = window.getComputedStyle(parent);
// Stop on fixed parent
if (styles.position === 'fixed') {
return parent;
}
const overflow = styles[scrollerHint.nameStyle];
if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) {
return parent;
}
parent = parent.parentElement;
}
} }
/** return documentScroller;
}
/**
* @typedef {Object} ScrollerData * @typedef {Object} ScrollerData
* @property {number} scrollPos - Current scroll position. * @property {number} scrollPos - Current scroll position.
* @property {number} scrollSize - Scroll size. * @property {number} scrollSize - Scroll size.
@@ -280,34 +278,34 @@ import layoutManager from './layoutManager';
* @property {boolean} custom - Custom scrolling mode. * @property {boolean} custom - Custom scrolling mode.
*/ */
/** /**
* Returns scroller data for specified orientation. * Returns scroller data for specified orientation.
* *
* @param {HTMLElement} scroller - Scroller. * @param {HTMLElement} scroller - Scroller.
* @param {boolean} vertical - Vertical scroller data. * @param {boolean} vertical - Vertical scroller data.
* @return {ScrollerData} Scroller data. * @return {ScrollerData} Scroller data.
*/ */
function getScrollerData(scroller, vertical) { function getScrollerData(scroller, vertical) {
const data = {}; const data = {};
if (!vertical) { if (!vertical) {
data.scrollPos = scroller.scrollLeft; data.scrollPos = scroller.scrollLeft;
data.scrollSize = scroller.scrollWidth; data.scrollSize = scroller.scrollWidth;
data.clientSize = scroller.clientWidth; data.clientSize = scroller.clientWidth;
data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode);
} else { } else {
data.scrollPos = scroller.scrollTop; data.scrollPos = scroller.scrollTop;
data.scrollSize = scroller.scrollHeight; data.scrollSize = scroller.scrollHeight;
data.clientSize = scroller.clientHeight; data.clientSize = scroller.clientHeight;
data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode);
}
data.custom = data.mode === 'custom';
return data;
} }
/** data.custom = data.mode === 'custom';
return data;
}
/**
* Returns position of child of scroller for specified orientation. * Returns position of child of scroller for specified orientation.
* *
* @param {HTMLElement} scroller - Scroller. * @param {HTMLElement} scroller - Scroller.
@@ -315,18 +313,18 @@ import layoutManager from './layoutManager';
* @param {boolean} vertical - Vertical scroll. * @param {boolean} vertical - Vertical scroll.
* @return {number} Child position. * @return {number} Child position.
*/ */
function getScrollerChildPos(scroller, element, vertical) { function getScrollerChildPos(scroller, element, vertical) {
const elementRect = element.getBoundingClientRect(); const elementRect = element.getBoundingClientRect();
const scrollerRect = scroller.getBoundingClientRect(); const scrollerRect = scroller.getBoundingClientRect();
if (!vertical) { if (!vertical) {
return scroller.scrollLeft + elementRect.left - scrollerRect.left; return scroller.scrollLeft + elementRect.left - scrollerRect.left;
} else { } else {
return scroller.scrollTop + elementRect.top - scrollerRect.top; return scroller.scrollTop + elementRect.top - scrollerRect.top;
}
} }
}
/** /**
* Returns scroll position for element. * Returns scroll position for element.
* *
* @param {ScrollerData} scrollerData - Scroller data. * @param {ScrollerData} scrollerData - Scroller data.
@@ -335,47 +333,47 @@ import layoutManager from './layoutManager';
* @param {boolean} centered - Scroll to center. * @param {boolean} centered - Scroll to center.
* @return {number} Scroll position. * @return {number} Scroll position.
*/ */
function calcScroll(scrollerData, elementPos, elementSize, centered) { function calcScroll(scrollerData, elementPos, elementSize, centered) {
const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; const maxScroll = scrollerData.scrollSize - scrollerData.clientSize;
let scroll; let scroll;
if (centered) { if (centered) {
scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; scroll = elementPos + (elementSize - scrollerData.clientSize) / 2;
} else { } else {
const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1);
scroll = scrollerData.scrollPos - delta; scroll = scrollerData.scrollPos - delta;
}
return clamp(Math.round(scroll), 0, maxScroll);
} }
/** return clamp(Math.round(scroll), 0, maxScroll);
}
/**
* Calls scrollTo function in proper way. * Calls scrollTo function in proper way.
* *
* @param {HTMLElement} scroller - Scroller. * @param {HTMLElement} scroller - Scroller.
* @param {ScrollToOptions} options - Scroll options. * @param {ScrollToOptions} options - Scroll options.
*/ */
function scrollToHelper(scroller, options) { function scrollToHelper(scroller, options) {
if ('scrollTo' in scroller) { if ('scrollTo' in scroller) {
if (!supportsScrollToOptions) { if (!supportsScrollToOptions) {
const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft);
const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop);
scroller.scrollTo(scrollX, scrollY); scroller.scrollTo(scrollX, scrollY);
} else { } else {
scroller.scrollTo(options); scroller.scrollTo(options);
} }
} else if ('scrollLeft' in scroller) { } else if ('scrollLeft' in scroller) {
if (options.left !== undefined) { if (options.left !== undefined) {
scroller.scrollLeft = options.left; scroller.scrollLeft = options.left;
} }
if (options.top !== undefined) { if (options.top !== undefined) {
scroller.scrollTop = options.top; scroller.scrollTop = options.top;
}
} }
} }
}
/** /**
* Performs built-in scroll. * Performs built-in scroll.
* *
* @param {HTMLElement} xScroller - Horizontal scroller. * @param {HTMLElement} xScroller - Horizontal scroller.
@@ -384,35 +382,35 @@ import layoutManager from './layoutManager';
* @param {number} scrollY - Vertical coordinate. * @param {number} scrollY - Vertical coordinate.
* @param {boolean} smooth - Smooth scrolling. * @param {boolean} smooth - Smooth scrolling.
*/ */
function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
const scrollBehavior = smooth ? 'smooth' : 'instant'; const scrollBehavior = smooth ? 'smooth' : 'instant';
if (xScroller !== yScroller) { if (xScroller !== yScroller) {
if (xScroller) { if (xScroller) {
scrollToHelper(xScroller, {left: scrollX, behavior: scrollBehavior}); scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior });
}
if (yScroller) {
scrollToHelper(yScroller, {top: scrollY, behavior: scrollBehavior});
}
} else if (xScroller) {
scrollToHelper(xScroller, {left: scrollX, top: scrollY, behavior: scrollBehavior});
} }
if (yScroller) {
scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior });
}
} else if (xScroller) {
scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior });
} }
}
/** /**
* Requested frame for animated scroll. * Requested frame for animated scroll.
*/ */
let scrollTimer; let scrollTimer;
/** /**
* Resets scroll timer to stop scrolling. * Resets scroll timer to stop scrolling.
*/ */
function resetScrollTimer() { function resetScrollTimer() {
cancelAnimationFrame(scrollTimer); cancelAnimationFrame(scrollTimer);
scrollTimer = undefined; scrollTimer = undefined;
} }
/** /**
* Performs animated scroll. * Performs animated scroll.
* *
* @param {HTMLElement} xScroller - Horizontal scroller. * @param {HTMLElement} xScroller - Horizontal scroller.
@@ -420,43 +418,43 @@ import layoutManager from './layoutManager';
* @param {HTMLElement} yScroller - Vertical scroller. * @param {HTMLElement} yScroller - Vertical scroller.
* @param {number} scrollY - Vertical coordinate. * @param {number} scrollY - Vertical coordinate.
*/ */
function animateScroll(xScroller, scrollX, yScroller, scrollY) { function animateScroll(xScroller, scrollX, yScroller, scrollY) {
const ox = xScroller ? xScroller.scrollLeft : scrollX; const ox = xScroller ? xScroller.scrollLeft : scrollX;
const oy = yScroller ? yScroller.scrollTop : scrollY; const oy = yScroller ? yScroller.scrollTop : scrollY;
const dx = scrollX - ox; const dx = scrollX - ox;
const dy = scrollY - oy; const dy = scrollY - oy;
if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) {
return;
}
let start;
function scrollAnim(currentTimestamp) {
start = start || currentTimestamp;
let k = Math.min(1, (currentTimestamp - start) / ScrollTime);
if (k === 1) {
resetScrollTimer();
builtinScroll(xScroller, scrollX, yScroller, scrollY, false);
return; return;
} }
let start; k = ease(k);
function scrollAnim(currentTimestamp) { const x = ox + dx * k;
start = start || currentTimestamp; const y = oy + dy * k;
let k = Math.min(1, (currentTimestamp - start) / ScrollTime); builtinScroll(xScroller, x, yScroller, y, false);
if (k === 1) {
resetScrollTimer();
builtinScroll(xScroller, scrollX, yScroller, scrollY, false);
return;
}
k = ease(k);
const x = ox + dx * k;
const y = oy + dy * k;
builtinScroll(xScroller, x, yScroller, y, false);
scrollTimer = requestAnimationFrame(scrollAnim);
}
scrollTimer = requestAnimationFrame(scrollAnim); scrollTimer = requestAnimationFrame(scrollAnim);
} }
/** scrollTimer = requestAnimationFrame(scrollAnim);
}
/**
* Performs scroll. * Performs scroll.
* *
* @param {HTMLElement} xScroller - Horizontal scroller. * @param {HTMLElement} xScroller - Horizontal scroller.
@@ -465,142 +463,140 @@ import layoutManager from './layoutManager';
* @param {number} scrollY - Vertical coordinate. * @param {number} scrollY - Vertical coordinate.
* @param {boolean} smooth - Smooth scrolling. * @param {boolean} smooth - Smooth scrolling.
*/ */
function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
resetScrollTimer(); resetScrollTimer();
if (smooth && useAnimatedScroll()) { if (smooth && useAnimatedScroll()) {
animateScroll(xScroller, scrollX, yScroller, scrollY); animateScroll(xScroller, scrollX, yScroller, scrollY);
} else { } else {
builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth); builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth);
}
} }
}
/** /**
* Returns true if smooth scroll must be used. * Returns true if smooth scroll must be used.
*/ */
function useSmoothScroll() { function useSmoothScroll() {
return !!browser.tizen; return !!browser.tizen;
} }
/** /**
* Returns true if animated implementation of smooth scroll must be used. * Returns true if animated implementation of smooth scroll must be used.
*/ */
function useAnimatedScroll() { function useAnimatedScroll() {
// Add block to force using (or not) of animated implementation // Add block to force using (or not) of animated implementation
return !supportsSmoothScroll; return !supportsSmoothScroll;
} }
/** /**
* Returns true if scroll manager is enabled. * Returns true if scroll manager is enabled.
*/ */
export function isEnabled() { export function isEnabled() {
return layoutManager.tv; return layoutManager.tv;
} }
/** /**
* Scrolls the document to a given position. * Scrolls the document to a given position.
* *
* @param {number} scrollX - Horizontal coordinate. * @param {number} scrollX - Horizontal coordinate.
* @param {number} scrollY - Vertical coordinate. * @param {number} scrollY - Vertical coordinate.
* @param {boolean} [smooth=false] - Smooth scrolling. * @param {boolean} [smooth=false] - Smooth scrolling.
*/ */
export function scrollTo(scrollX, scrollY, smooth) { export function scrollTo(scrollX, scrollY, smooth) {
smooth = !!smooth; smooth = !!smooth;
// Scroller is document itself by default // Scroller is document itself by default
const scroller = getScrollableParent(null, false); const scroller = getScrollableParent(null, false);
const xScrollerData = getScrollerData(scroller, false); const xScrollerData = getScrollerData(scroller, false);
const yScrollerData = getScrollerData(scroller, true); const yScrollerData = getScrollerData(scroller, true);
scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize);
scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize);
doScroll(scroller, scrollX, scroller, scrollY, smooth); doScroll(scroller, scrollX, scroller, scrollY, smooth);
} }
/** /**
* Scrolls the document to a given element. * Scrolls the document to a given element.
* *
* @param {HTMLElement} element - Target element of scroll task. * @param {HTMLElement} element - Target element of scroll task.
* @param {boolean} [smooth=false] - Smooth scrolling. * @param {boolean} [smooth=false] - Smooth scrolling.
*/ */
export function scrollToElement(element, smooth) { export function scrollToElement(element, smooth) {
smooth = !!smooth; smooth = !!smooth;
let scrollCenterX = true; let scrollCenterX = true;
let scrollCenterY = true; let scrollCenterY = true;
const offsetParent = element.offsetParent; const offsetParent = element.offsetParent;
// In Firefox offsetParent.offsetParent is BODY // In Firefox offsetParent.offsetParent is BODY
const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed'); const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed');
// Scroll fixed elements to nearest edge (or do not scroll at all) // Scroll fixed elements to nearest edge (or do not scroll at all)
if (isFixed) { if (isFixed) {
scrollCenterX = scrollCenterY = false; scrollCenterX = scrollCenterY = false;
}
let xScroller = getScrollableParent(element, false);
let yScroller = getScrollableParent(element, true);
const xScrollerData = getScrollerData(xScroller, false);
const yScrollerData = getScrollerData(yScroller, true);
// Exit, since we have no control over scrolling in this container
if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) {
return;
}
// Exit, since we have no control over scrolling in these containers
if (xScrollerData.custom && yScrollerData.custom) {
return;
}
const elementRect = element.getBoundingClientRect();
let scrollX = 0;
let scrollY = 0;
if (!xScrollerData.custom) {
const xPos = getScrollerChildPos(xScroller, element, false);
scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX);
} else {
xScroller = null;
}
if (!yScrollerData.custom) {
const yPos = getScrollerChildPos(yScroller, element, true);
scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY);
// HACK: Scroll to top for top menu because it is hidden
// FIXME: Need a marker to scroll top/bottom
if (isFixed && elementRect.bottom < 0) {
scrollY = 0;
}
// HACK: Ensure we are at the top
// FIXME: Need a marker to scroll top/bottom
if (scrollY < minimumScrollY() && yScroller === documentScroller) {
scrollY = 0;
}
} else {
yScroller = null;
}
doScroll(xScroller, scrollX, yScroller, scrollY, smooth);
} }
if (isEnabled()) { let xScroller = getScrollableParent(element, false);
dom.addEventListener(window, 'focusin', function(e) { let yScroller = getScrollableParent(element, true);
setTimeout(function() {
scrollToElement(e.target, useSmoothScroll()); const xScrollerData = getScrollerData(xScroller, false);
}, 0); const yScrollerData = getScrollerData(yScroller, true);
}, {capture: true});
// Exit, since we have no control over scrolling in this container
if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) {
return;
} }
/* eslint-enable indent */ // Exit, since we have no control over scrolling in these containers
if (xScrollerData.custom && yScrollerData.custom) {
return;
}
const elementRect = element.getBoundingClientRect();
let scrollX = 0;
let scrollY = 0;
if (!xScrollerData.custom) {
const xPos = getScrollerChildPos(xScroller, element, false);
scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX);
} else {
xScroller = null;
}
if (!yScrollerData.custom) {
const yPos = getScrollerChildPos(yScroller, element, true);
scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY);
// HACK: Scroll to top for top menu because it is hidden
// FIXME: Need a marker to scroll top/bottom
if (isFixed && elementRect.bottom < 0) {
scrollY = 0;
}
// HACK: Ensure we are at the top
// FIXME: Need a marker to scroll top/bottom
if (scrollY < minimumScrollY() && yScroller === documentScroller) {
scrollY = 0;
}
} else {
yScroller = null;
}
doScroll(xScroller, scrollX, yScroller, scrollY, smooth);
}
if (isEnabled()) {
dom.addEventListener(window, 'focusin', function(e) {
setTimeout(function() {
scrollToElement(e.target, useSmoothScroll());
}, 0);
}, { capture: true });
}
export default { export default {
isEnabled: isEnabled, isEnabled: isEnabled,
+2 -2
View File
@@ -85,8 +85,8 @@ const SearchFields: FunctionComponent<SearchFieldsProps> = ({ onSearch = () => {
dangerouslySetInnerHTML={createInputElement()} dangerouslySetInnerHTML={createInputElement()}
/> />
</div> </div>
{layoutManager.tv && !browser.tv && {layoutManager.tv && !browser.tv
<AlphaPicker onAlphaPicked={onAlphaPicked} /> && <AlphaPicker onAlphaPicked={onAlphaPicked} />
} }
</div> </div>
); );
+328 -332
View File
@@ -1,5 +1,3 @@
/* eslint-disable indent */
/** /**
* Module shortcuts. * Module shortcuts.
* @module components/shortcuts * @module components/shortcuts
@@ -14,385 +12,383 @@ import recordingHelper from './recordingcreator/recordinghelper';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import toast from './toast/toast'; import toast from './toast/toast';
function playAllFromHere(card, serverId, queue) { function playAllFromHere(card, serverId, queue) {
const parent = card.parentNode; const parent = card.parentNode;
const className = card.classList.length ? (`.${card.classList[0]}`) : ''; const className = card.classList.length ? (`.${card.classList[0]}`) : '';
const cards = parent.querySelectorAll(`${className}[data-id]`); const cards = parent.querySelectorAll(`${className}[data-id]`);
const ids = []; const ids = [];
let foundCard = false; let foundCard = false;
let startIndex; let startIndex;
for (let i = 0, length = cards.length; i < length; i++) { for (let i = 0, length = cards.length; i < length; i++) {
if (cards[i] === card) { if (cards[i] === card) {
foundCard = true; foundCard = true;
startIndex = i; startIndex = i;
} }
if (foundCard || !queue) { if (foundCard || !queue) {
ids.push(cards[i].getAttribute('data-id')); ids.push(cards[i].getAttribute('data-id'));
}
}
const itemsContainer = dom.parentWithClass(card, 'itemsContainer');
if (itemsContainer && itemsContainer.fetchData) {
const queryOptions = queue ? { StartIndex: startIndex } : {};
return itemsContainer.fetchData(queryOptions).then(result => {
if (queue) {
return playbackManager.queue({
items: result.Items
});
} else {
return playbackManager.play({
items: result.Items,
startIndex: startIndex
});
} }
});
}
if (!ids.length) {
return;
}
if (queue) {
return playbackManager.queue({
ids: ids,
serverId: serverId
});
} else {
return playbackManager.play({
ids: ids,
serverId: serverId,
startIndex: startIndex
});
}
}
function showProgramDialog(item) {
import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => {
recordingCreator.show(item.Id, item.ServerId);
});
}
function getItem(button) {
button = dom.parentWithAttribute(button, 'data-id');
const serverId = button.getAttribute('data-serverid');
const id = button.getAttribute('data-id');
const type = button.getAttribute('data-type');
const apiClient = ServerConnections.getApiClient(serverId);
if (type === 'Timer') {
return apiClient.getLiveTvTimer(id);
}
if (type === 'SeriesTimer') {
return apiClient.getLiveTvSeriesTimer(id);
}
return apiClient.getItem(apiClient.getCurrentUserId(), id);
}
function notifyRefreshNeeded(childElement, itemsContainer) {
itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer');
if (itemsContainer) {
itemsContainer.notifyRefreshNeeded(true);
}
}
function showContextMenu(card, options) {
getItem(card).then(item => {
const playlistId = card.getAttribute('data-playlistid');
const collectionId = card.getAttribute('data-collectionid');
if (playlistId) {
const elem = dom.parentWithAttribute(card, 'data-playlistitemid');
item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null;
} }
const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); import('./itemContextMenu').then((itemContextMenu) => {
if (itemsContainer && itemsContainer.fetchData) { ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => {
const queryOptions = queue ? { StartIndex: startIndex } : {}; itemContextMenu.show(Object.assign({
item: item,
play: true,
queue: true,
playAllFromHere: !item.IsFolder,
queueAllFromHere: !item.IsFolder,
playlistId: playlistId,
collectionId: collectionId,
user: user
return itemsContainer.fetchData(queryOptions).then(result => { }, options || {})).then(result => {
if (queue) { if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') {
return playbackManager.queue({ executeAction(card, options.positionTo, result.command);
items: result.Items } else if (result.updated || result.deleted) {
}); notifyRefreshNeeded(card, options.itemsContainer);
} else { }
return playbackManager.play({ });
items: result.Items,
startIndex: startIndex
});
}
}); });
} });
});
}
if (!ids.length) { function getItemInfoFromCard(card) {
return; return {
Type: card.getAttribute('data-type'),
Id: card.getAttribute('data-id'),
TimerId: card.getAttribute('data-timerid'),
CollectionType: card.getAttribute('data-collectiontype'),
ChannelId: card.getAttribute('data-channelid'),
SeriesId: card.getAttribute('data-seriesid'),
ServerId: card.getAttribute('data-serverid'),
MediaType: card.getAttribute('data-mediatype'),
Path: card.getAttribute('data-path'),
IsFolder: card.getAttribute('data-isfolder') === 'true',
StartDate: card.getAttribute('data-startdate'),
EndDate: card.getAttribute('data-enddate'),
UserData: {
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10)
} }
};
}
if (queue) { function showPlayMenu(card, target) {
return playbackManager.queue({ const item = getItemInfoFromCard(card);
ids: ids,
import('./playmenu').then((playMenu) => {
playMenu.show({
item: item,
positionTo: target
});
});
}
function executeAction(card, target, action) {
target = target || card;
let id = card.getAttribute('data-id');
if (!id) {
card = dom.parentWithAttribute(card, 'data-id');
id = card.getAttribute('data-id');
}
const item = getItemInfoFromCard(card);
const serverId = item.ServerId;
const type = item.Type;
const playableItemId = type === 'Program' ? item.ChannelId : item.Id;
if (item.MediaType === 'Photo' && action === 'link') {
action = 'play';
}
if (action === 'link') {
appRouter.showItem(item, {
context: card.getAttribute('data-context'),
parentId: card.getAttribute('data-parentid')
});
} else if (action === 'programdialog') {
showProgramDialog(item);
} else if (action === 'instantmix') {
playbackManager.instantMix({
Id: playableItemId,
ServerId: serverId
});
} else if (action === 'play' || action === 'resume') {
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10);
if (playbackManager.canPlay(item)) {
playbackManager.play({
ids: [playableItemId],
startPositionTicks: startPositionTicks,
serverId: serverId serverId: serverId
}); });
} else { } else {
return playbackManager.play({ console.warn('Unable to play item', item);
ids: ids, }
serverId: serverId, } else if (action === 'queue') {
startIndex: startIndex if (playbackManager.isPlaying()) {
playbackManager.queue({
ids: [playableItemId],
serverId: serverId
});
toast(globalize.translate('MediaQueued'));
} else {
playbackManager.queue({
ids: [playableItemId],
serverId: serverId
}); });
} }
} } else if (action === 'playallfromhere') {
playAllFromHere(card, serverId);
} else if (action === 'queueallfromhere') {
playAllFromHere(card, serverId, true);
} else if (action === 'setplaylistindex') {
playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid'));
} else if (action === 'record') {
onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid'));
} else if (action === 'menu') {
const options = target.getAttribute('data-playoptions') === 'false' ?
{
shuffle: false,
instantMix: false,
play: false,
playAllFromHere: false,
queue: false,
queueAllFromHere: false
} :
{};
function showProgramDialog(item) { options.positionTo = target;
import('./recordingcreator/recordingcreator').then(({default:recordingCreator}) => {
recordingCreator.show(item.Id, item.ServerId); showContextMenu(card, options);
} else if (action === 'playmenu') {
showPlayMenu(card, target);
} else if (action === 'edit') {
getItem(target).then(itemToEdit => {
editItem(itemToEdit, serverId);
}); });
} else if (action === 'playtrailer') {
getItem(target).then(playTrailer);
} else if (action === 'addtoplaylist') {
getItem(target).then(addToPlaylist);
} else if (action === 'custom') {
const customAction = target.getAttribute('data-customaction');
card.dispatchEvent(new CustomEvent(`action-${customAction}`, {
detail: {
playlistItemId: card.getAttribute('data-playlistitemid')
},
cancelable: false,
bubbles: true
}));
} }
}
function getItem(button) { function addToPlaylist(item) {
button = dom.parentWithAttribute(button, 'data-id'); import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => {
const serverId = button.getAttribute('data-serverid'); new playlistEditor().show({
const id = button.getAttribute('data-id'); items: [item.Id],
const type = button.getAttribute('data-type'); serverId: item.ServerId
const apiClient = ServerConnections.getApiClient(serverId);
if (type === 'Timer') {
return apiClient.getLiveTvTimer(id);
}
if (type === 'SeriesTimer') {
return apiClient.getLiveTvSeriesTimer(id);
}
return apiClient.getItem(apiClient.getCurrentUserId(), id);
}
function notifyRefreshNeeded(childElement, itemsContainer) {
itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer');
if (itemsContainer) {
itemsContainer.notifyRefreshNeeded(true);
}
}
function showContextMenu(card, options) {
getItem(card).then(item => {
const playlistId = card.getAttribute('data-playlistid');
const collectionId = card.getAttribute('data-collectionid');
if (playlistId) {
const elem = dom.parentWithAttribute(card, 'data-playlistitemid');
item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null;
}
import('./itemContextMenu').then((itemContextMenu) => {
ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => {
itemContextMenu.show(Object.assign({
item: item,
play: true,
queue: true,
playAllFromHere: !item.IsFolder,
queueAllFromHere: !item.IsFolder,
playlistId: playlistId,
collectionId: collectionId,
user: user
}, options || {})).then(result => {
if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') {
executeAction(card, options.positionTo, result.command);
} else if (result.updated || result.deleted) {
notifyRefreshNeeded(card, options.itemsContainer);
}
});
});
});
}); });
} });
}
function getItemInfoFromCard(card) { function playTrailer(item) {
return { const apiClient = ServerConnections.getApiClient(item.ServerId);
Type: card.getAttribute('data-type'),
Id: card.getAttribute('data-id'),
TimerId: card.getAttribute('data-timerid'),
CollectionType: card.getAttribute('data-collectiontype'),
ChannelId: card.getAttribute('data-channelid'),
SeriesId: card.getAttribute('data-seriesid'),
ServerId: card.getAttribute('data-serverid'),
MediaType: card.getAttribute('data-mediatype'),
Path: card.getAttribute('data-path'),
IsFolder: card.getAttribute('data-isfolder') === 'true',
StartDate: card.getAttribute('data-startdate'),
EndDate: card.getAttribute('data-enddate'),
UserData: {
PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10)
}
};
}
function showPlayMenu(card, target) { apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => {
const item = getItemInfoFromCard(card); playbackManager.play({ items: trailers });
});
}
import('./playmenu').then((playMenu) => { function editItem(item, serverId) {
playMenu.show({ const apiClient = ServerConnections.getApiClient(serverId);
item: item, return new Promise((resolve, reject) => {
positionTo: target const currentServerId = apiClient.serverInfo().Id;
});
});
}
function executeAction(card, target, action) { if (item.Type === 'Timer') {
target = target || card; if (item.ProgramId) {
import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => {
let id = card.getAttribute('data-id'); recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject);
if (!id) {
card = dom.parentWithAttribute(card, 'data-id');
id = card.getAttribute('data-id');
}
const item = getItemInfoFromCard(card);
const serverId = item.ServerId;
const type = item.Type;
const playableItemId = type === 'Program' ? item.ChannelId : item.Id;
if (item.MediaType === 'Photo' && action === 'link') {
action = 'play';
}
if (action === 'link') {
appRouter.showItem(item, {
context: card.getAttribute('data-context'),
parentId: card.getAttribute('data-parentid')
});
} else if (action === 'programdialog') {
showProgramDialog(item);
} else if (action === 'instantmix') {
playbackManager.instantMix({
Id: playableItemId,
ServerId: serverId
});
} else if (action === 'play' || action === 'resume') {
const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10);
if (playbackManager.canPlay(item)) {
playbackManager.play({
ids: [playableItemId],
startPositionTicks: startPositionTicks,
serverId: serverId
}); });
} else { } else {
console.warn('Unable to play item', item); import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => {
} recordingEditor.show(item.Id, currentServerId).then(resolve, reject);
} else if (action === 'queue') {
if (playbackManager.isPlaying()) {
playbackManager.queue({
ids: [playableItemId],
serverId: serverId
});
toast(globalize.translate('MediaQueued'));
} else {
playbackManager.queue({
ids: [playableItemId],
serverId: serverId
}); });
} }
} else if (action === 'playallfromhere') { } else {
playAllFromHere(card, serverId); import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => {
} else if (action === 'queueallfromhere') { metadataEditor.show(item.Id, currentServerId).then(resolve, reject);
playAllFromHere(card, serverId, true);
} else if (action === 'setplaylistindex') {
playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid'));
} else if (action === 'record') {
onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid'));
} else if (action === 'menu') {
const options = target.getAttribute('data-playoptions') === 'false' ?
{
shuffle: false,
instantMix: false,
play: false,
playAllFromHere: false,
queue: false,
queueAllFromHere: false
} :
{};
options.positionTo = target;
showContextMenu(card, options);
} else if (action === 'playmenu') {
showPlayMenu(card, target);
} else if (action === 'edit') {
getItem(target).then(itemToEdit => {
editItem(itemToEdit, serverId);
}); });
} else if (action === 'playtrailer') { }
getItem(target).then(playTrailer); });
} else if (action === 'addtoplaylist') { }
getItem(target).then(addToPlaylist);
} else if (action === 'custom') {
const customAction = target.getAttribute('data-customaction');
card.dispatchEvent(new CustomEvent(`action-${customAction}`, { function onRecordCommand(serverId, id, type, timerId, seriesTimerId) {
detail: { if (type === 'Program' || timerId || seriesTimerId) {
playlistItemId: card.getAttribute('data-playlistitemid') const programId = type === 'Program' ? id : null;
}, recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId);
cancelable: false, }
bubbles: true }
}));
export function onClick(e) {
const card = dom.parentWithClass(e.target, 'itemAction');
if (card) {
let actionElement = card;
let action = actionElement.getAttribute('data-action');
if (!action) {
actionElement = dom.parentWithAttribute(actionElement, 'data-action');
if (actionElement) {
action = actionElement.getAttribute('data-action');
}
}
if (action) {
executeAction(card, actionElement, action);
e.preventDefault();
e.stopPropagation();
return false;
} }
} }
}
function addToPlaylist(item) { function onCommand(e) {
import('./playlisteditor/playlisteditor').then(({default: playlistEditor}) => { const cmd = e.detail.command;
new playlistEditor().show({
items: [item.Id],
serverId: item.ServerId
}); if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') {
}); const target = e.target;
} const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id');
function playTrailer(item) {
const apiClient = ServerConnections.getApiClient(item.ServerId);
apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => {
playbackManager.play({ items: trailers });
});
}
function editItem(item, serverId) {
const apiClient = ServerConnections.getApiClient(serverId);
return new Promise((resolve, reject) => {
const currentServerId = apiClient.serverInfo().Id;
if (item.Type === 'Timer') {
if (item.ProgramId) {
import('./recordingcreator/recordingcreator').then(({default: recordingCreator}) => {
recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject);
});
} else {
import('./recordingcreator/recordingeditor').then(({default: recordingEditor}) => {
recordingEditor.show(item.Id, currentServerId).then(resolve, reject);
});
}
} else {
import('./metadataEditor/metadataEditor').then(({default: metadataEditor}) => {
metadataEditor.show(item.Id, currentServerId).then(resolve, reject);
});
}
});
}
function onRecordCommand(serverId, id, type, timerId, seriesTimerId) {
if (type === 'Program' || timerId || seriesTimerId) {
const programId = type === 'Program' ? id : null;
recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId);
}
}
export function onClick(e) {
const card = dom.parentWithClass(e.target, 'itemAction');
if (card) { if (card) {
let actionElement = card; e.preventDefault();
let action = actionElement.getAttribute('data-action'); e.stopPropagation();
executeAction(card, card, cmd);
if (!action) {
actionElement = dom.parentWithAttribute(actionElement, 'data-action');
if (actionElement) {
action = actionElement.getAttribute('data-action');
}
}
if (action) {
executeAction(card, actionElement, action);
e.preventDefault();
e.stopPropagation();
return false;
}
} }
} }
}
function onCommand(e) { export function on(context, options) {
const cmd = e.detail.command; options = options || {};
if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { if (options.click !== false) {
const target = e.target; context.addEventListener('click', onClick);
const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id');
if (card) {
e.preventDefault();
e.stopPropagation();
executeAction(card, card, cmd);
}
}
} }
export function on(context, options) { if (options.command !== false) {
options = options || {}; inputManager.on(context, onCommand);
}
}
if (options.click !== false) { export function off(context, options) {
context.addEventListener('click', onClick); options = options || {};
}
if (options.command !== false) { context.removeEventListener('click', onClick);
inputManager.on(context, onCommand);
} if (options.command !== false) {
inputManager.off(context, onCommand);
}
}
export function getShortcutAttributesHtml(item, serverId) {
let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`;
const collectionType = item.CollectionType;
if (collectionType) {
html += ` data-collectiontype="${collectionType}"`;
} }
export function off(context, options) { return html;
options = options || {}; }
context.removeEventListener('click', onClick);
if (options.command !== false) {
inputManager.off(context, onCommand);
}
}
export function getShortcutAttributesHtml(item, serverId) {
let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`;
const collectionType = item.CollectionType;
if (collectionType) {
html += ` data-collectiontype="${collectionType}"`;
}
return html;
}
/* eslint-enable indent */
export default { export default {
on: on, on: on,
@@ -342,7 +342,7 @@ function showDownloadOptions(button, context, subtitleId) {
} }
function centerFocus(elem, horiz, on) { function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then(({default: scrollHelper}) => { import('../../scripts/scrollHelper').then(({ default: scrollHelper }) => {
const fn = on ? 'on' : 'off'; const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz); scrollHelper.centerFocus[fn](elem, horiz);
}); });
@@ -353,7 +353,7 @@ function onOpenUploadMenu(e) {
const selectLanguage = dialog.querySelector('#selectLanguage'); const selectLanguage = dialog.querySelector('#selectLanguage');
const apiClient = ServerConnections.getApiClient(currentItem.ServerId); const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
import('../subtitleuploader/subtitleuploader').then(({default: subtitleUploader}) => { import('../subtitleuploader/subtitleuploader').then(({ default: subtitleUploader }) => {
subtitleUploader.show({ subtitleUploader.show({
languages: { languages: {
list: selectLanguage.innerHTML, list: selectLanguage.innerHTML,
+3 -3
View File
@@ -94,9 +94,9 @@ function init(instance) {
subtitleSyncSlider.getBubbleHtml = function (value) { subtitleSyncSlider.getBubbleHtml = function (value) {
const newOffset = getOffsetFromPercentage(value); const newOffset = getOffsetFromPercentage(value);
return '<h1 class="sliderBubbleText">' + return '<h1 class="sliderBubbleText">'
(newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's' + + (newOffset > 0 ? '+' : '') + parseFloat(newOffset) + 's'
'</h1>'; + '</h1>';
}; };
subtitleSyncCloseButton.addEventListener('click', function () { subtitleSyncCloseButton.addEventListener('click', function () {
+114
View File
@@ -0,0 +1,114 @@
import { clearBackdrop } from '../backdrop/backdrop';
import * as mainTabsManager from '../maintabsmanager';
import layoutManager from '../layoutManager';
import '../../elements/emby-tabs/emby-tabs';
import LibraryMenu from '../../scripts/libraryMenu';
function onViewDestroy() {
const tabControllers = this.tabControllers;
if (tabControllers) {
tabControllers.forEach(function (t) {
if (t.destroy) {
t.destroy();
}
});
this.tabControllers = null;
}
this.view = null;
this.params = null;
this.currentTabController = null;
this.initialTabIndex = null;
}
class TabbedView {
constructor(view, params) {
this.tabControllers = [];
this.view = view;
this.params = params;
const self = this;
let currentTabIndex = parseInt(params.tab || this.getDefaultTabIndex(params.parentId), 10);
this.initialTabIndex = currentTabIndex;
function validateTabLoad(index) {
return self.validateTabLoad ? self.validateTabLoad(index) : Promise.resolve();
}
function loadTab(index, previousIndex) {
validateTabLoad(index).then(function () {
self.getTabController(index).then(function (controller) {
const refresh = !controller.refreshed;
controller.onResume({
autoFocus: previousIndex == null && layoutManager.tv,
refresh: refresh
});
controller.refreshed = true;
currentTabIndex = index;
self.currentTabController = controller;
});
});
}
function getTabContainers() {
return view.querySelectorAll('.tabContent');
}
function onTabChange(e) {
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
const previousIndex = e.detail.previousIndex;
const previousTabController = previousIndex == null ? null : self.tabControllers[previousIndex];
if (previousTabController && previousTabController.onPause) {
previousTabController.onPause();
}
loadTab(newIndex, previousIndex);
}
view.addEventListener('viewbeforehide', this.onPause.bind(this));
view.addEventListener('viewbeforeshow', function () {
mainTabsManager.setTabs(view, currentTabIndex, self.getTabs, getTabContainers, null, onTabChange, false);
});
view.addEventListener('viewshow', function (e) {
self.onResume(e.detail);
});
view.addEventListener('viewdestroy', onViewDestroy.bind(this));
}
onResume() {
this.setTitle();
clearBackdrop();
const currentTabController = this.currentTabController;
if (!currentTabController) {
mainTabsManager.selectedTabIndex(this.initialTabIndex);
} else if (currentTabController && currentTabController.onResume) {
currentTabController.onResume({});
}
}
onPause() {
const currentTabController = this.currentTabController;
if (currentTabController && currentTabController.onPause) {
currentTabController.onPause();
}
}
setTitle() {
LibraryMenu.setTitle('');
}
}
export default TabbedView;
+1 -1
View File
@@ -48,7 +48,7 @@ function refreshTunerDevices(page, providerInfo, devices) {
function onSelectPathClick(e) { function onSelectPathClick(e) {
const page = $(e.target).parents('.xmltvForm')[0]; const page = $(e.target).parents('.xmltvForm')[0];
import('../directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => { import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser(); const picker = new DirectoryBrowser();
picker.show({ picker.show({
includeFiles: true, includeFiles: true,
+153 -156
View File
@@ -10,199 +10,197 @@ import './upnextdialog.scss';
import '../../elements/emby-button/emby-button'; import '../../elements/emby-button/emby-button';
import '../../styles/flexstyles.scss'; import '../../styles/flexstyles.scss';
/* eslint-disable indent */ const transitionEndEventName = dom.whichTransitionEvent();
const transitionEndEventName = dom.whichTransitionEvent(); function getHtml() {
let html = '';
function getHtml() { html += '<div class="flex flex-direction-column flex-grow">';
let html = '';
html += '<div class="flex flex-direction-column flex-grow">'; html += '<h2 class="upNextDialog-nextVideoText" style="margin:.25em 0;">&nbsp;</h2>';
html += '<h2 class="upNextDialog-nextVideoText" style="margin:.25em 0;">&nbsp;</h2>'; html += '<h3 class="upNextDialog-title" style="margin:.25em 0 .5em;"></h3>';
html += '<h3 class="upNextDialog-title" style="margin:.25em 0 .5em;"></h3>'; html += '<div class="flex flex-direction-row upNextDialog-mediainfo">';
html += '</div>';
html += '<div class="flex flex-direction-row upNextDialog-mediainfo">'; html += '<div class="flex flex-direction-row upNextDialog-buttons" style="margin-top:1em;">';
html += '</div>';
html += '<div class="flex flex-direction-row upNextDialog-buttons" style="margin-top:1em;">'; html += '<button type="button" is="emby-button" class="raised raised-mini btnStartNow upNextDialog-button">';
html += globalize.translate('HeaderStartNow');
html += '</button>';
html += '<button type="button" is="emby-button" class="raised raised-mini btnStartNow upNextDialog-button">'; html += '<button type="button" is="emby-button" class="raised raised-mini btnHide upNextDialog-button">';
html += globalize.translate('HeaderStartNow'); html += globalize.translate('Hide');
html += '</button>'; html += '</button>';
html += '<button type="button" is="emby-button" class="raised raised-mini btnHide upNextDialog-button">'; // buttons
html += globalize.translate('Hide'); html += '</div>';
html += '</button>';
// buttons // main
html += '</div>'; html += '</div>';
// main return html;
html += '</div>'; }
return html; function setNextVideoText() {
const instance = this;
const elem = instance.options.parent;
const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0);
console.debug('up next seconds remaining: ' + secondsRemaining);
const timeText = '<span class="upNextDialog-countdownText">' + globalize.translate('HeaderSecondsValue', secondsRemaining) + '</span>';
const nextVideoText = instance.itemType === 'Episode' ?
globalize.translate('HeaderNextEpisodePlayingInValue', timeText) :
globalize.translate('HeaderNextVideoPlayingInValue', timeText);
elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText;
}
function fillItem(item) {
const instance = this;
const elem = instance.options.parent;
elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, {
criticRating: true,
originalAirDate: false,
starRating: true,
subtitles: false
});
let title = itemHelper.getDisplayName(item);
if (item.SeriesName) {
title = item.SeriesName + ' - ' + title;
} }
function setNextVideoText() { elem.querySelector('.upNextDialog-title').innerText = title || '';
const instance = this;
const elem = instance.options.parent; instance.itemType = item.Type;
const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); instance.show();
}
console.debug('up next seconds remaining: ' + secondsRemaining); function clearCountdownTextTimeout(instance) {
if (instance._countdownTextTimeout) {
clearInterval(instance._countdownTextTimeout);
instance._countdownTextTimeout = null;
}
}
const timeText = '<span class="upNextDialog-countdownText">' + globalize.translate('HeaderSecondsValue', secondsRemaining) + '</span>'; async function onStartNowClick() {
const options = this.options;
const nextVideoText = instance.itemType === 'Episode' ? if (options) {
globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : const player = options.player;
globalize.translate('HeaderNextVideoPlayingInValue', timeText);
elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText; await this.hide();
playbackManager.nextTrack(player);
}
}
function init(instance, options) {
options.parent.innerHTML = getHtml();
options.parent.classList.add('hide');
options.parent.classList.add('upNextDialog');
options.parent.classList.add('upNextDialog-hidden');
fillItem.call(instance, options.nextItem);
options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance));
options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance));
}
function clearHideAnimationEventListeners(instance, elem) {
const fn = instance._onHideAnimationComplete;
if (fn) {
dom.removeEventListener(elem, transitionEndEventName, fn, {
once: true
});
}
}
function onHideAnimationComplete(e) {
const instance = this;
const elem = e.target;
elem.classList.add('hide');
clearHideAnimationEventListeners(instance, elem);
Events.trigger(instance, 'hide');
}
async function hideComingUpNext() {
const instance = this;
clearCountdownTextTimeout(this);
if (!instance.options) {
return;
} }
function fillItem(item) { const elem = instance.options.parent;
const instance = this;
const elem = instance.options.parent; if (!elem) {
return;
}
elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { clearHideAnimationEventListeners(this, elem);
criticRating: false,
originalAirDate: false, if (elem.classList.contains('upNextDialog-hidden')) {
starRating: false, return;
subtitles: false }
const fn = onHideAnimationComplete.bind(instance);
instance._onHideAnimationComplete = fn;
const transitionEvent = await new Promise((resolve) => {
dom.addEventListener(elem, transitionEndEventName, resolve, {
once: true
}); });
let title = itemHelper.getDisplayName(item); // trigger a reflow to force it to animate again
if (item.SeriesName) { void elem.offsetWidth;
title = item.SeriesName + ' - ' + title;
}
elem.querySelector('.upNextDialog-title').innerText = title || ''; elem.classList.add('upNextDialog-hidden');
});
instance.itemType = item.Type; instance._onHideAnimationComplete(transitionEvent);
}
instance.show(); function getTimeRemainingMs(instance) {
} const options = instance.options;
if (options) {
const runtimeTicks = playbackManager.duration(options.player);
function clearCountdownTextTimeout(instance) { if (runtimeTicks) {
if (instance._countdownTextTimeout) { const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000;
clearInterval(instance._countdownTextTimeout);
instance._countdownTextTimeout = null; return Math.round(timeRemainingTicks / 10000);
} }
} }
async function onStartNowClick() { return 0;
const options = this.options; }
if (options) { function startComingUpNextHideTimer(instance) {
const player = options.player; const timeRemainingMs = getTimeRemainingMs(instance);
await this.hide(); if (timeRemainingMs <= 0) {
return;
playbackManager.nextTrack(player);
}
} }
function init(instance, options) { setNextVideoText.call(instance);
options.parent.innerHTML = getHtml(); clearCountdownTextTimeout(instance);
options.parent.classList.add('hide'); instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400);
options.parent.classList.add('upNextDialog'); }
options.parent.classList.add('upNextDialog-hidden');
fillItem.call(instance, options.nextItem);
options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance));
options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance));
}
function clearHideAnimationEventListeners(instance, elem) {
const fn = instance._onHideAnimationComplete;
if (fn) {
dom.removeEventListener(elem, transitionEndEventName, fn, {
once: true
});
}
}
function onHideAnimationComplete(e) {
const instance = this;
const elem = e.target;
elem.classList.add('hide');
clearHideAnimationEventListeners(instance, elem);
Events.trigger(instance, 'hide');
}
async function hideComingUpNext() {
const instance = this;
clearCountdownTextTimeout(this);
if (!instance.options) {
return;
}
const elem = instance.options.parent;
if (!elem) {
return;
}
clearHideAnimationEventListeners(this, elem);
if (elem.classList.contains('upNextDialog-hidden')) {
return;
}
const fn = onHideAnimationComplete.bind(instance);
instance._onHideAnimationComplete = fn;
const transitionEvent = await new Promise((resolve) => {
dom.addEventListener(elem, transitionEndEventName, resolve, {
once: true
});
// trigger a reflow to force it to animate again
void elem.offsetWidth;
elem.classList.add('upNextDialog-hidden');
});
instance._onHideAnimationComplete(transitionEvent);
}
function getTimeRemainingMs(instance) {
const options = instance.options;
if (options) {
const runtimeTicks = playbackManager.duration(options.player);
if (runtimeTicks) {
const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000;
return Math.round(timeRemainingTicks / 10000);
}
}
return 0;
}
function startComingUpNextHideTimer(instance) {
const timeRemainingMs = getTimeRemainingMs(instance);
if (timeRemainingMs <= 0) {
return;
}
setNextVideoText.call(instance);
clearCountdownTextTimeout(instance);
instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400);
}
class UpNextDialog { class UpNextDialog {
constructor(options) { constructor(options) {
@@ -243,4 +241,3 @@ class UpNextDialog {
export default UpNextDialog; export default UpNextDialog;
/* eslint-enable indent */
+228 -219
View File
@@ -2,243 +2,252 @@ import { importModule } from '@uupaa/dynamic-import-polyfill';
import './viewManager/viewContainer.scss'; import './viewManager/viewContainer.scss';
import Dashboard from '../utils/dashboard'; import Dashboard from '../utils/dashboard';
/* eslint-disable indent */ const getMainAnimatedPages = () => {
if (!mainAnimatedPages) {
mainAnimatedPages = document.querySelector('.mainAnimatedPages');
}
function setControllerClass(view, options) { return mainAnimatedPages;
if (options.controllerFactory) { };
return Promise.resolve();
}
let controllerUrl = view.getAttribute('data-controller');
if (controllerUrl) {
if (controllerUrl.indexOf('__plugin/') === 0) {
controllerUrl = controllerUrl.substring('__plugin/'.length);
}
controllerUrl = Dashboard.getPluginUrl(controllerUrl);
const apiUrl = ApiClient.getUrl('/web/' + controllerUrl);
return importModule(apiUrl).then((ControllerFactory) => {
options.controllerFactory = ControllerFactory;
});
}
function setControllerClass(view, options) {
if (options.controllerFactory) {
return Promise.resolve(); return Promise.resolve();
} }
export function loadView(options) { let controllerUrl = view.getAttribute('data-controller');
if (!options.cancel) {
if (controllerUrl) {
if (controllerUrl.indexOf('__plugin/') === 0) {
controllerUrl = controllerUrl.substring('__plugin/'.length);
}
controllerUrl = Dashboard.getPluginUrl(controllerUrl);
const apiUrl = ApiClient.getUrl('/web/' + controllerUrl);
return importModule(apiUrl).then((ControllerFactory) => {
options.controllerFactory = ControllerFactory;
});
}
return Promise.resolve();
}
export function loadView(options) {
if (!options.cancel) {
const selected = selectedPageIndex;
const previousAnimatable = selected === -1 ? null : allPages[selected];
let pageIndex = selected + 1;
if (pageIndex >= pageContainerCount) {
pageIndex = 0;
}
const isPluginpage = options.url.includes('configurationpage');
const newViewInfo = normalizeNewView(options, isPluginpage);
const newView = newViewInfo.elem;
const currentPage = allPages[pageIndex];
if (currentPage) {
triggerDestroy(currentPage);
}
let view = newView;
if (typeof view == 'string') {
view = document.createElement('div');
view.innerHTML = newView;
}
view.classList.add('mainAnimatedPage');
if (!getMainAnimatedPages()) {
console.warn('[viewContainer] main animated pages element is not present');
return;
}
if (currentPage) {
if (newViewInfo.hasScript && window.$) {
mainAnimatedPages.removeChild(currentPage);
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.replaceChild(view, currentPage);
}
} else {
if (newViewInfo.hasScript && window.$) {
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.appendChild(view);
}
}
if (options.type) {
view.setAttribute('data-type', options.type);
}
const properties = [];
if (options.fullscreen) {
properties.push('fullscreen');
}
if (properties.length) {
view.setAttribute('data-properties', properties.join(','));
}
allPages[pageIndex] = view;
return setControllerClass(view, options)
// Timeout for polyfilled CustomElements (webOS 1.2)
.then(() => new Promise((resolve) => setTimeout(resolve, 0)))
.then(() => {
if (onBeforeChange) {
onBeforeChange(view, false, options);
}
beforeAnimate(allPages, pageIndex, selected);
selectedPageIndex = pageIndex;
currentUrls[pageIndex] = options.url;
if (!options.cancel && previousAnimatable) {
afterAnimate(allPages, pageIndex);
}
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view;
});
}
}
function parseHtml(html, hasScript) {
if (hasScript) {
html = html
.replaceAll('\x3c!--<script', '<script')
.replaceAll('</script>--\x3e', '</script>');
}
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.querySelector('div[data-role="page"]');
}
function normalizeNewView(options, isPluginpage) {
const viewHtml = options.view;
if (viewHtml.indexOf('data-role="page"') === -1) {
return viewHtml;
}
let hasScript = viewHtml.indexOf('<script') !== -1;
const elem = parseHtml(viewHtml, hasScript);
if (hasScript) {
hasScript = elem.querySelector('script') != null;
}
let hasjQuery = false;
let hasjQuerySelect = false;
let hasjQueryChecked = false;
if (isPluginpage) {
hasjQuery = viewHtml.indexOf('jQuery') != -1 || viewHtml.indexOf('$(') != -1 || viewHtml.indexOf('$.') != -1;
hasjQueryChecked = viewHtml.indexOf('.checked(') != -1;
hasjQuerySelect = viewHtml.indexOf('.selectmenu(') != -1;
}
return {
elem: elem,
hasScript: hasScript,
hasjQuerySelect: hasjQuerySelect,
hasjQueryChecked: hasjQueryChecked,
hasjQuery: hasjQuery
};
}
function beforeAnimate(allPages, newPageIndex, oldPageIndex) {
for (let index = 0, length = allPages.length; index < length; index++) {
if (newPageIndex !== index && oldPageIndex !== index) {
allPages[index].classList.add('hide');
}
}
}
function afterAnimate(allPages, newPageIndex) {
for (let index = 0, length = allPages.length; index < length; index++) {
if (newPageIndex !== index) {
allPages[index].classList.add('hide');
}
}
}
export function setOnBeforeChange(fn) {
onBeforeChange = fn;
}
export function tryRestoreView(options) {
const url = options.url;
const index = currentUrls.indexOf(url);
if (index !== -1) {
const animatable = allPages[index];
const view = animatable;
if (view) {
if (options.cancel) {
return;
}
const selected = selectedPageIndex; const selected = selectedPageIndex;
const previousAnimatable = selected === -1 ? null : allPages[selected]; const previousAnimatable = selected === -1 ? null : allPages[selected];
let pageIndex = selected + 1; return setControllerClass(view, options).then(() => {
if (onBeforeChange) {
if (pageIndex >= pageContainerCount) { onBeforeChange(view, true, options);
pageIndex = 0;
}
const isPluginpage = options.url.includes('configurationpage');
const newViewInfo = normalizeNewView(options, isPluginpage);
const newView = newViewInfo.elem;
const currentPage = allPages[pageIndex];
if (currentPage) {
triggerDestroy(currentPage);
}
let view = newView;
if (typeof view == 'string') {
view = document.createElement('div');
view.innerHTML = newView;
}
view.classList.add('mainAnimatedPage');
if (currentPage) {
if (newViewInfo.hasScript && window.$) {
mainAnimatedPages.removeChild(currentPage);
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.replaceChild(view, currentPage);
}
} else {
if (newViewInfo.hasScript && window.$) {
view = $(view).appendTo(mainAnimatedPages)[0];
} else {
mainAnimatedPages.appendChild(view);
}
}
if (options.type) {
view.setAttribute('data-type', options.type);
}
const properties = [];
if (options.fullscreen) {
properties.push('fullscreen');
}
if (properties.length) {
view.setAttribute('data-properties', properties.join(','));
}
allPages[pageIndex] = view;
return setControllerClass(view, options)
// Timeout for polyfilled CustomElements (webOS 1.2)
.then(() => new Promise((resolve) => setTimeout(resolve, 0)))
.then(() => {
if (onBeforeChange) {
onBeforeChange(view, false, options);
}
beforeAnimate(allPages, pageIndex, selected);
selectedPageIndex = pageIndex;
currentUrls[pageIndex] = options.url;
if (!options.cancel && previousAnimatable) {
afterAnimate(allPages, pageIndex);
}
if (window.$) {
$.mobile = $.mobile || {};
$.mobile.activePage = view;
}
return view;
});
}
}
function parseHtml(html, hasScript) {
if (hasScript) {
html = html
.replaceAll('\x3c!--<script', '<script')
.replaceAll('</script>--\x3e', '</script>');
}
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.querySelector('div[data-role="page"]');
}
function normalizeNewView(options, isPluginpage) {
const viewHtml = options.view;
if (viewHtml.indexOf('data-role="page"') === -1) {
return viewHtml;
}
let hasScript = viewHtml.indexOf('<script') !== -1;
const elem = parseHtml(viewHtml, hasScript);
if (hasScript) {
hasScript = elem.querySelector('script') != null;
}
let hasjQuery = false;
let hasjQuerySelect = false;
let hasjQueryChecked = false;
if (isPluginpage) {
hasjQuery = viewHtml.indexOf('jQuery') != -1 || viewHtml.indexOf('$(') != -1 || viewHtml.indexOf('$.') != -1;
hasjQueryChecked = viewHtml.indexOf('.checked(') != -1;
hasjQuerySelect = viewHtml.indexOf('.selectmenu(') != -1;
}
return {
elem: elem,
hasScript: hasScript,
hasjQuerySelect: hasjQuerySelect,
hasjQueryChecked: hasjQueryChecked,
hasjQuery: hasjQuery
};
}
function beforeAnimate(allPages, newPageIndex, oldPageIndex) {
for (let index = 0, length = allPages.length; index < length; index++) {
if (newPageIndex !== index && oldPageIndex !== index) {
allPages[index].classList.add('hide');
}
}
}
function afterAnimate(allPages, newPageIndex) {
for (let index = 0, length = allPages.length; index < length; index++) {
if (newPageIndex !== index) {
allPages[index].classList.add('hide');
}
}
}
export function setOnBeforeChange(fn) {
onBeforeChange = fn;
}
export function tryRestoreView(options) {
const url = options.url;
const index = currentUrls.indexOf(url);
if (index !== -1) {
const animatable = allPages[index];
const view = animatable;
if (view) {
if (options.cancel) {
return;
} }
const selected = selectedPageIndex; beforeAnimate(allPages, index, selected);
const previousAnimatable = selected === -1 ? null : allPages[selected]; animatable.classList.remove('hide');
return setControllerClass(view, options).then(() => { selectedPageIndex = index;
if (onBeforeChange) {
onBeforeChange(view, true, options);
}
beforeAnimate(allPages, index, selected); if (!options.cancel && previousAnimatable) {
animatable.classList.remove('hide'); afterAnimate(allPages, index);
selectedPageIndex = index; }
if (!options.cancel && previousAnimatable) { if (window.$) {
afterAnimate(allPages, index); $.mobile = $.mobile || {};
} $.mobile.activePage = view;
}
if (window.$) { return view;
$.mobile = $.mobile || {}; });
$.mobile.activePage = view;
}
return view;
});
}
} }
return Promise.reject();
} }
function triggerDestroy(view) { return Promise.reject();
view.dispatchEvent(new CustomEvent('viewdestroy', {})); }
}
export function reset() { function triggerDestroy(view) {
allPages = []; view.dispatchEvent(new CustomEvent('viewdestroy', {}));
currentUrls = []; }
mainAnimatedPages.innerHTML = '';
selectedPageIndex = -1;
}
let onBeforeChange; export function reset() {
const mainAnimatedPages = document.querySelector('.mainAnimatedPages'); allPages = [];
let allPages = []; currentUrls = [];
let currentUrls = []; if (mainAnimatedPages) mainAnimatedPages.innerHTML = '';
const pageContainerCount = 3; selectedPageIndex = -1;
let selectedPageIndex = -1; }
reset();
mainAnimatedPages.classList.remove('hide');
/* eslint-enable indent */ let onBeforeChange;
let mainAnimatedPages;
let allPages = [];
let currentUrls = [];
const pageContainerCount = 3;
let selectedPageIndex = -1;
reset();
getMainAnimatedPages()?.classList.remove('hide');
export default { export default {
loadView: loadView, loadView: loadView,
+1 -1
View File
@@ -97,7 +97,7 @@ function dispatchViewEvent(view, eventInfo, eventName, isCancellable) {
return eventResult; return eventResult;
} }
function getViewEventDetail(view, {state, url, options = {}}, isRestored) { function getViewEventDetail(view, { state, url, options = {} }, isRestored) {
const index = url.indexOf('?'); const index = url.indexOf('?');
// eslint-disable-next-line compat/compat // eslint-disable-next-line compat/compat
const searchParams = new URLSearchParams(url.substring(index + 1)); const searchParams = new URLSearchParams(url.substring(index + 1));
+71 -74
View File
@@ -6,85 +6,82 @@ import '../../elements/emby-button/emby-button';
import confirm from '../../components/confirm/confirm'; import confirm from '../../components/confirm/confirm';
import { pageIdOn } from '../../utils/dashboard'; import { pageIdOn } from '../../utils/dashboard';
/* eslint-disable indent */ function revoke(page, key) {
confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('Auth/Keys/' + key)
}).then(function () {
loadData(page);
});
});
}
function revoke(page, key) { function renderKeys(page, keys) {
confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () { const rows = keys.map(function (item) {
loading.show(); let html = '';
html += '<tr class="detailTableBodyRow detailTableBodyRow-shaded">';
html += '<td class="detailTableBodyCell">';
html += '<button type="button" is="emby-button" data-token="' + item.AccessToken + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AccessToken;
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AppName || '';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
const date = datetime.parseISO8601Date(item.DateCreated, true);
html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date);
html += '</td>';
html += '</tr>';
return html;
}).join('');
page.querySelector('.resultBody').innerHTML = rows;
loading.hide();
}
function loadData(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) {
renderKeys(page, result.Items);
});
}
function showNewKeyPrompt(page) {
import('../../components/prompt/prompt').then(({ default: prompt }) => {
prompt({
title: globalize.translate('HeaderNewApiKey'),
label: globalize.translate('LabelAppName'),
description: globalize.translate('LabelAppNameExample')
}).then(function (value) {
ApiClient.ajax({ ApiClient.ajax({
type: 'DELETE', type: 'POST',
url: ApiClient.getUrl('Auth/Keys/' + key) url: ApiClient.getUrl('Auth/Keys', {
App: value
})
}).then(function () { }).then(function () {
loadData(page); loadData(page);
}); });
}); });
}
function renderKeys(page, keys) {
const rows = keys.map(function (item) {
let html = '';
html += '<tr class="detailTableBodyRow detailTableBodyRow-shaded">';
html += '<td class="detailTableBodyCell">';
html += '<button type="button" is="emby-button" data-token="' + item.AccessToken + '" class="raised raised-mini btnRevoke" data-mini="true" title="' + globalize.translate('ButtonRevoke') + '" style="margin:0;">' + globalize.translate('ButtonRevoke') + '</button>';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AccessToken;
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
html += item.AppName || '';
html += '</td>';
html += '<td class="detailTableBodyCell" style="vertical-align:middle;">';
const date = datetime.parseISO8601Date(item.DateCreated, true);
html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date);
html += '</td>';
html += '</tr>';
return html;
}).join('');
page.querySelector('.resultBody').innerHTML = rows;
loading.hide();
}
function loadData(page) {
loading.show();
ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) {
renderKeys(page, result.Items);
});
}
function showNewKeyPrompt(page) {
import('../../components/prompt/prompt').then(({default: prompt}) => {
prompt({
title: globalize.translate('HeaderNewApiKey'),
label: globalize.translate('LabelAppName'),
description: globalize.translate('LabelAppNameExample')
}).then(function (value) {
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('Auth/Keys', {
App: value
})
}).then(function () {
loadData(page);
});
});
});
}
pageIdOn('pageinit', 'apiKeysPage', function () {
const page = this;
page.querySelector('.btnNewKey').addEventListener('click', function () {
showNewKeyPrompt(page);
});
page.querySelector('.tblApiKeys').addEventListener('click', function (e) {
const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke');
if (btnRevoke) {
revoke(page, btnRevoke.getAttribute('data-token'));
}
});
});
pageIdOn('pagebeforeshow', 'apiKeysPage', function () {
loadData(this);
}); });
}
pageIdOn('pageinit', 'apiKeysPage', function () {
const page = this;
page.querySelector('.btnNewKey').addEventListener('click', function () {
showNewKeyPrompt(page);
});
page.querySelector('.tblApiKeys').addEventListener('click', function (e) {
const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke');
if (btnRevoke) {
revoke(page, btnRevoke.getAttribute('data-token'));
}
});
});
pageIdOn('pagebeforeshow', 'apiKeysPage', function () {
loadData(this);
});
/* eslint-enable indent */
File diff suppressed because it is too large Load Diff
+41 -44
View File
@@ -5,53 +5,50 @@ import '../../../elements/emby-button/emby-button';
import Dashboard from '../../../utils/dashboard'; import Dashboard from '../../../utils/dashboard';
import { getParameterByName } from '../../../utils/url.ts'; import { getParameterByName } from '../../../utils/url.ts';
/* eslint-disable indent */ function load(page, device, deviceOptions) {
page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || '';
page.querySelector('.reportedName', page).innerText = device.Name || '';
}
function load(page, device, deviceOptions) { function loadData() {
page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; const page = this;
page.querySelector('.reportedName', page).innerText = device.Name || ''; loading.show();
} const id = getParameterByName('id');
const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', {
Id: id
}));
const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', {
Id: id
}));
Promise.all([promise1, promise2]).then(function (responses) {
load(page, responses[0], responses[1]);
loading.hide();
});
}
function loadData() { function save(page) {
const page = this; const id = getParameterByName('id');
loading.show(); ApiClient.ajax({
const id = getParameterByName('id'); url: ApiClient.getUrl('Devices/Options', {
const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', {
Id: id Id: id
})); }),
const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', { type: 'POST',
Id: id data: JSON.stringify({
})); CustomName: page.querySelector('#txtCustomName').value
Promise.all([promise1, promise2]).then(function (responses) { }),
load(page, responses[0], responses[1]); contentType: 'application/json'
loading.hide(); }).then(Dashboard.processServerConfigurationUpdateResult);
}); }
}
function save(page) { function onSubmit(e) {
const id = getParameterByName('id'); const form = this;
ApiClient.ajax({ save(dom.parentWithClass(form, 'page'));
url: ApiClient.getUrl('Devices/Options', { e.preventDefault();
Id: id return false;
}), }
type: 'POST',
data: JSON.stringify({
CustomName: page.querySelector('#txtCustomName').value
}),
contentType: 'application/json'
}).then(Dashboard.processServerConfigurationUpdateResult);
}
function onSubmit(e) { export default function (view) {
const form = this; view.querySelector('form').addEventListener('submit', onSubmit);
save(dom.parentWithClass(form, 'page')); view.addEventListener('viewshow', loadData);
e.preventDefault(); }
return false;
}
export default function (view) {
view.querySelector('form').addEventListener('submit', onSubmit);
view.addEventListener('viewshow', loadData);
}
/* eslint-enable indent */
+131 -133
View File
@@ -12,159 +12,157 @@ import '../../../components/cardbuilder/card.scss';
import Dashboard from '../../../utils/dashboard'; import Dashboard from '../../../utils/dashboard';
import confirm from '../../../components/confirm/confirm'; import confirm from '../../../components/confirm/confirm';
/* eslint-disable indent */ // Local cache of loaded
let deviceIds = [];
// Local cache of loaded function canDelete(deviceId) {
let deviceIds = []; return deviceId !== ApiClient.deviceId();
}
function canDelete(deviceId) { function deleteAllDevices(page) {
return deviceId !== ApiClient.deviceId(); const msg = globalize.translate('DeleteDevicesConfirmation');
}
function deleteAllDevices(page) { confirm({
const msg = globalize.translate('DeleteDevicesConfirmation'); text: msg,
title: globalize.translate('HeaderDeleteDevices'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(async () => {
loading.show();
await Promise.all(
deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id))
);
loadData(page);
});
}
confirm({ function deleteDevice(page, id) {
text: msg, const msg = globalize.translate('DeleteDeviceConfirmation');
title: globalize.translate('HeaderDeleteDevices'),
confirmText: globalize.translate('Delete'), confirm({
primary: 'delete' text: msg,
}).then(async () => { title: globalize.translate('HeaderDeleteDevice'),
loading.show(); confirmText: globalize.translate('Delete'),
await Promise.all( primary: 'delete'
deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) }).then(async () => {
); loading.show();
loadData(page); await ApiClient.deleteDevice(id);
loadData(page);
});
}
function showDeviceMenu(view, btn, deviceId) {
const menuItems = [{
name: globalize.translate('Edit'),
id: 'open',
icon: 'mode_edit'
}];
if (canDelete(deviceId)) {
menuItems.push({
name: globalize.translate('Delete'),
id: 'delete',
icon: 'delete'
}); });
} }
function deleteDevice(page, id) { import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
const msg = globalize.translate('DeleteDeviceConfirmation'); actionsheet.show({
items: menuItems,
positionTo: btn,
callback: function (id) {
switch (id) {
case 'open':
Dashboard.navigate('device.html?id=' + deviceId);
break;
confirm({ case 'delete':
text: msg, deleteDevice(view, deviceId);
title: globalize.translate('HeaderDeleteDevice'), }
confirmText: globalize.translate('Delete'), }
primary: 'delete'
}).then(async () => {
loading.show();
await ApiClient.deleteDevice(id);
loadData(page);
}); });
} });
}
function showDeviceMenu(view, btn, deviceId) { function load(page, devices) {
const menuItems = [{ const localeWithSuffix = getLocaleWithSuffix();
name: globalize.translate('Edit'),
id: 'open',
icon: 'mode_edit'
}];
if (canDelete(deviceId)) { let html = '';
menuItems.push({ html += devices.map(function (device) {
name: globalize.translate('Delete'), let deviceHtml = '';
id: 'delete', deviceHtml += "<div data-id='" + device.Id + "' class='card backdropCard'>";
icon: 'delete' deviceHtml += '<div class="cardBox visualCardBox">';
}); deviceHtml += '<div class="cardScalable">';
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
deviceHtml += `<a is="emby-linkbutton" href="#/device.html?id=${device.Id}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
const iconUrl = imageHelper.getDeviceIcon(device);
if (iconUrl) {
deviceHtml += '<div class="cardImage" style="background-image:url(\'' + iconUrl + "');background-size: auto 64%;background-position:center center;\">";
deviceHtml += '</div>';
} else {
deviceHtml += '<span class="cardImageIcon material-icons tablet_android" aria-hidden="true"></span>';
} }
import('../../../components/actionSheet/actionSheet').then(({default: actionsheet}) => { deviceHtml += '</a>';
actionsheet.show({ deviceHtml += '</div>';
items: menuItems, deviceHtml += '<div class="cardFooter">';
positionTo: btn,
callback: function (id) {
switch (id) {
case 'open':
Dashboard.navigate('device.html?id=' + deviceId);
break;
case 'delete': if (canDelete(device.Id)) {
deleteDevice(view, deviceId); if (globalize.getIsRTL())
} deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">';
} else
}); deviceHtml += '<div style="text-align:right; float:right;padding-top:5px;">';
}); deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + device.Id + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
}
function load(page, devices) {
const localeWithSuffix = getLocaleWithSuffix();
let html = '';
html += devices.map(function (device) {
let deviceHtml = '';
deviceHtml += "<div data-id='" + device.Id + "' class='card backdropCard'>";
deviceHtml += '<div class="cardBox visualCardBox">';
deviceHtml += '<div class="cardScalable">';
deviceHtml += '<div class="cardPadder cardPadder-backdrop"></div>';
deviceHtml += `<a is="emby-linkbutton" href="#/device.html?id=${device.Id}" class="cardContent cardImageContainer ${cardBuilder.getDefaultBackgroundClass()}">`;
const iconUrl = imageHelper.getDeviceIcon(device);
if (iconUrl) {
deviceHtml += '<div class="cardImage" style="background-image:url(\'' + iconUrl + "');background-size: auto 64%;background-position:center center;\">";
deviceHtml += '</div>';
} else {
deviceHtml += '<span class="cardImageIcon material-icons tablet_android" aria-hidden="true"></span>';
}
deviceHtml += '</a>';
deviceHtml += '</div>'; deviceHtml += '</div>';
deviceHtml += '<div class="cardFooter">'; }
if (canDelete(device.Id)) { deviceHtml += "<div class='cardText'>";
if (globalize.getIsRTL()) deviceHtml += escapeHtml(device.Name);
deviceHtml += '<div style="text-align:left; float:left;padding-top:5px;">'; deviceHtml += '</div>';
else deviceHtml += "<div class='cardText cardText-secondary'>";
deviceHtml += '<div style="text-align:right; float:right;padding-top:5px;">'; deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion);
deviceHtml += '<button type="button" is="paper-icon-button-light" data-id="' + device.Id + '" title="' + globalize.translate('Menu') + '" class="btnDeviceMenu"><span class="material-icons more_vert" aria-hidden="true"></span></button>'; deviceHtml += '</div>';
deviceHtml += '</div>'; deviceHtml += "<div class='cardText cardText-secondary'>";
}
deviceHtml += "<div class='cardText'>"; if (device.LastUserName) {
deviceHtml += escapeHtml(device.Name); deviceHtml += escapeHtml(device.LastUserName);
deviceHtml += '</div>'; deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix);
deviceHtml += "<div class='cardText cardText-secondary'>"; }
deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion);
deviceHtml += '</div>';
deviceHtml += "<div class='cardText cardText-secondary'>";
if (device.LastUserName) { deviceHtml += '&nbsp;';
deviceHtml += escapeHtml(device.LastUserName); deviceHtml += '</div>';
deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix); deviceHtml += '</div>';
} deviceHtml += '</div>';
deviceHtml += '</div>';
return deviceHtml;
}).join('');
page.querySelector('.devicesList').innerHTML = html;
}
deviceHtml += '&nbsp;'; function loadData(page) {
deviceHtml += '</div>'; loading.show();
deviceHtml += '</div>'; ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) {
deviceHtml += '</div>'; load(page, result.Items);
deviceHtml += '</div>'; deviceIds = result.Items.map((device) => device.Id);
return deviceHtml; loading.hide();
}).join(''); });
page.querySelector('.devicesList').innerHTML = html; }
}
function loadData(page) { export default function (view) {
loading.show(); view.querySelector('.devicesList').addEventListener('click', function (e) {
ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu');
load(page, result.Items);
deviceIds = result.Items.map((device) => device.Id);
loading.hide();
});
}
export default function (view) { if (btnDeviceMenu) {
view.querySelector('.devicesList').addEventListener('click', function (e) { showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id'));
const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); }
});
view.addEventListener('viewshow', function () {
loadData(this);
});
if (btnDeviceMenu) { view.querySelector('#deviceDeleteAll').addEventListener('click', function() {
showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); deleteAllDevices(view);
} });
}); }
view.addEventListener('viewshow', function () {
loadData(this);
});
view.querySelector('#deviceDeleteAll').addEventListener('click', function() {
deleteAllDevices(view);
});
}
/* eslint-enable indent */
File diff suppressed because it is too large Load Diff
+82 -85
View File
@@ -7,90 +7,87 @@ import '../../../components/listview/listview.scss';
import '../../../elements/emby-button/emby-button'; import '../../../elements/emby-button/emby-button';
import confirm from '../../../components/confirm/confirm'; import confirm from '../../../components/confirm/confirm';
/* eslint-disable indent */ function loadProfiles(page) {
loading.show();
function loadProfiles(page) { ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) {
loading.show(); renderUserProfiles(page, result);
ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { renderSystemProfiles(page, result);
renderUserProfiles(page, result); loading.hide();
renderSystemProfiles(page, result);
loading.hide();
});
}
function renderUserProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) {
return p.Type == 'User';
}));
}
function renderSystemProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) {
return p.Type == 'System';
}));
}
function renderProfiles(page, element, profiles) {
let html = '';
if (profiles.length) {
html += '<div class="paperList">';
}
for (let i = 0, length = profiles.length; i < length; i++) {
const profile = profiles[i];
html += '<div class="listItem listItem-border">';
html += '<span class="listItemIcon material-icons live_tv" aria-hidden="true"></span>';
html += '<div class="listItemBody two-line">';
html += "<a is='emby-linkbutton' style='padding:0;margin:0;' data-ripple='false' class='clearLink' href='#/dlnaprofile.html?id=" + profile.Id + "'>";
html += '<div>' + escapeHtml(profile.Name) + '</div>';
html += '</a>';
html += '</div>';
if (profile.Type == 'User') {
html += '<button type="button" is="paper-icon-button-light" class="btnDeleteProfile" data-profileid="' + profile.Id + '" title="' + globalize.translate('Delete') + '"><span class="material-icons delete" aria-hidden="true"></span></button>';
}
html += '</div>';
}
if (profiles.length) {
html += '</div>';
}
element.innerHTML = html;
$('.btnDeleteProfile', element).on('click', function () {
const id = this.getAttribute('data-profileid');
deleteProfile(page, id);
});
}
function deleteProfile(page, id) {
confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('Dlna/Profiles/' + id)
}).then(function () {
loading.hide();
loadProfiles(page);
});
});
}
function getTabs() {
return [{
href: '#/dlnasettings.html',
name: globalize.translate('Settings')
}, {
href: '#/dlnaprofiles.html',
name: globalize.translate('TabProfiles')
}];
}
$(document).on('pageshow', '#dlnaProfilesPage', function () {
libraryMenu.setTabs('dlna', 1, getTabs);
loadProfiles(this);
}); });
}
function renderUserProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) {
return p.Type == 'User';
}));
}
function renderSystemProfiles(page, profiles) {
renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) {
return p.Type == 'System';
}));
}
function renderProfiles(page, element, profiles) {
let html = '';
if (profiles.length) {
html += '<div class="paperList">';
}
for (let i = 0, length = profiles.length; i < length; i++) {
const profile = profiles[i];
html += '<div class="listItem listItem-border">';
html += '<span class="listItemIcon material-icons live_tv" aria-hidden="true"></span>';
html += '<div class="listItemBody two-line">';
html += "<a is='emby-linkbutton' style='padding:0;margin:0;' data-ripple='false' class='clearLink' href='#/dlnaprofile.html?id=" + profile.Id + "'>";
html += '<div>' + escapeHtml(profile.Name) + '</div>';
html += '</a>';
html += '</div>';
if (profile.Type == 'User') {
html += '<button type="button" is="paper-icon-button-light" class="btnDeleteProfile" data-profileid="' + profile.Id + '" title="' + globalize.translate('Delete') + '"><span class="material-icons delete" aria-hidden="true"></span></button>';
}
html += '</div>';
}
if (profiles.length) {
html += '</div>';
}
element.innerHTML = html;
$('.btnDeleteProfile', element).on('click', function () {
const id = this.getAttribute('data-profileid');
deleteProfile(page, id);
});
}
function deleteProfile(page, id) {
confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () {
loading.show();
ApiClient.ajax({
type: 'DELETE',
url: ApiClient.getUrl('Dlna/Profiles/' + id)
}).then(function () {
loading.hide();
loadProfiles(page);
});
});
}
function getTabs() {
return [{
href: '#/dlnasettings.html',
name: globalize.translate('Settings')
}, {
href: '#/dlnaprofiles.html',
name: globalize.translate('TabProfiles')
}];
}
$(document).on('pageshow', '#dlnaProfilesPage', function () {
libraryMenu.setTabs('dlna', 1, getTabs);
loadProfiles(this);
});
/* eslint-enable indent */
+50 -53
View File
@@ -5,59 +5,56 @@ import libraryMenu from '../../../scripts/libraryMenu';
import globalize from '../../../scripts/globalize'; import globalize from '../../../scripts/globalize';
import Dashboard from '../../../utils/dashboard'; import Dashboard from '../../../utils/dashboard';
/* eslint-disable indent */ function loadPage(page, config, users) {
page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo;
page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog;
$('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds);
$('#chkEnableServer', page).prop('checked', config.EnableServer);
$('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages);
$('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds);
const usersHtml = users.map(function (u) {
return '<option value="' + u.Id + '">' + escapeHtml(u.Name) + '</option>';
}).join('');
$('#selectUser', page).html(usersHtml).val(config.DefaultUserId || '');
loading.hide();
}
function loadPage(page, config, users) { function onSubmit() {
page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; loading.show();
page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; const form = this;
$('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); ApiClient.getNamedConfiguration('dlna').then(function (config) {
$('#chkEnableServer', page).prop('checked', config.EnableServer); config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked;
$('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked;
$('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val();
const usersHtml = users.map(function (u) { config.EnableServer = $('#chkEnableServer', form).is(':checked');
return '<option value="' + u.Id + '">' + escapeHtml(u.Name) + '</option>'; config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked');
}).join(''); config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val();
$('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); config.DefaultUserId = $('#selectUser', form).val();
loading.hide(); ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult);
}
function onSubmit() {
loading.show();
const form = this;
ApiClient.getNamedConfiguration('dlna').then(function (config) {
config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked;
config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked;
config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val();
config.EnableServer = $('#chkEnableServer', form).is(':checked');
config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked');
config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val();
config.DefaultUserId = $('#selectUser', form).val();
ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult);
});
return false;
}
function getTabs() {
return [{
href: '#/dlnasettings.html',
name: globalize.translate('Settings')
}, {
href: '#/dlnaprofiles.html',
name: globalize.translate('TabProfiles')
}];
}
$(document).on('pageinit', '#dlnaSettingsPage', function () {
$('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#dlnaSettingsPage', function () {
libraryMenu.setTabs('dlna', 0, getTabs);
loading.show();
const page = this;
const promise1 = ApiClient.getNamedConfiguration('dlna');
const promise2 = ApiClient.getUsers();
Promise.all([promise1, promise2]).then(function (responses) {
loadPage(page, responses[0], responses[1]);
});
}); });
return false;
}
function getTabs() {
return [{
href: '#/dlnasettings.html',
name: globalize.translate('Settings')
}, {
href: '#/dlnaprofiles.html',
name: globalize.translate('TabProfiles')
}];
}
$(document).on('pageinit', '#dlnaSettingsPage', function () {
$('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#dlnaSettingsPage', function () {
libraryMenu.setTabs('dlna', 0, getTabs);
loading.show();
const page = this;
const promise1 = ApiClient.getNamedConfiguration('dlna');
const promise2 = ApiClient.getUsers();
Promise.all([promise1, promise2]).then(function (responses) {
loadPage(page, responses[0], responses[1]);
});
});
/* eslint-enable indent */
@@ -111,7 +111,7 @@
<span>${EnableIntelLowPowerHevcHwEncoder}</span> <span>${EnableIntelLowPowerHevcHwEncoder}</span>
</label> </label>
<div class="fieldDescription"> <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> </div>
</div> </div>
@@ -253,6 +253,13 @@
</label> </label>
<div class="fieldDescription checkboxFieldDescription">${EnableFallbackFontHelp}</div> <div class="fieldDescription checkboxFieldDescription">${EnableFallbackFontHelp}</div>
</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"> <div class="inputContainer">
<input is="emby-input" type="number" id="txtDownMixAudioBoost" pattern="[0-9]*" required="required" min=".5" max="3" step=".1" label="${LabelDownMixAudioScale}" /> <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> <div class="fieldDescription">${LabelDownMixAudioScaleHelp}</div>
+281 -282
View File
@@ -6,295 +6,294 @@ import libraryMenu from '../../scripts/libraryMenu';
import Dashboard from '../../utils/dashboard'; import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert'; import alert from '../../components/alert';
/* eslint-disable indent */ function loadPage(page, config, systemInfo) {
Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) {
c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1;
});
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder;
page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder;
page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder;
page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder;
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
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 || '';
page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || '';
$('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || '');
page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || '';
page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont;
$('#txtVaapiDevice', page).val(config.VaapiDevice || '');
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat;
page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold;
page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak;
page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || '';
page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness;
page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast;
page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || '';
page.querySelector('#txtH264Crf').value = config.H264Crf || '';
page.querySelector('#txtH265Crf').value = config.H265Crf || '';
page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || '';
page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate;
page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false;
page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false;
page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
loading.hide();
}
function loadPage(page, config, systemInfo) { function onSaveEncodingPathFailure() {
Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { loading.hide();
c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1; alert(globalize.translate('FFmpegSavePathNotFound'));
}); }
page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc;
page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9;
page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder;
page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder;
page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder;
page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder;
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
$('#selectThreadCount', page).val(config.EncodingThreadCount);
$('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost);
$('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None');
page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || '';
page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || '';
$('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || '');
page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || '';
page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont;
$('#txtVaapiDevice', page).val(config.VaapiDevice || '');
page.querySelector('#chkTonemapping').checked = config.EnableTonemapping;
page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping;
page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm;
page.querySelector('#selectTonemappingRange').value = config.TonemappingRange;
page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat;
page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold;
page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak;
page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || '';
page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness;
page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast;
page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || '';
page.querySelector('#txtH264Crf').value = config.H264Crf || '';
page.querySelector('#txtH265Crf').value = config.H265Crf || '';
page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || '';
page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate;
page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false;
page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false;
page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', {
bubbles: true
}));
loading.hide();
}
function onSaveEncodingPathFailure() { function updateEncoder(form) {
loading.hide(); return ApiClient.getSystemInfo().then(function () {
alert(globalize.translate('FFmpegSavePathNotFound')); return ApiClient.ajax({
} url: ApiClient.getUrl('System/MediaEncoder/Path'),
type: 'POST',
data: JSON.stringify({
Path: form.querySelector('.txtEncoderPath').value,
PathType: 'Custom'
}),
contentType: 'application/json'
}).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure);
});
}
function updateEncoder(form) { function onSubmit() {
return ApiClient.getSystemInfo().then(function () { const form = this;
return ApiClient.ajax({
url: ApiClient.getUrl('System/MediaEncoder/Path'),
type: 'POST',
data: JSON.stringify({
Path: form.querySelector('.txtEncoderPath').value,
PathType: 'Custom'
}),
contentType: 'application/json'
}).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure);
});
}
function onSubmit() { const onDecoderConfirmed = function () {
const form = this;
const onDecoderConfirmed = function () {
loading.show();
ApiClient.getNamedConfiguration('encoding').then(function (config) {
config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val();
config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None';
config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value;
config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val();
config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value;
config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false;
config.EncodingThreadCount = $('#selectThreadCount', form).val();
config.HardwareAccelerationType = $('#selectVideoDecoder', form).val();
config.VaapiDevice = $('#txtVaapiDevice', form).val();
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
config.TonemappingRange = form.querySelector('#selectTonemappingRange').value;
config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value;
config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value;
config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value;
config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0';
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10);
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10);
config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value;
config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked;
config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked;
config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked;
config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) {
return c.checked;
}), function (c) {
return c.getAttribute('data-codec');
});
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked;
config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked;
config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked;
config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked;
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
updateEncoder(form);
}, function () {
alert(globalize.translate('ErrorDefault'));
Dashboard.processServerConfigurationUpdateResult();
});
});
};
if ($('#selectVideoDecoder', form).val()) {
alert({
title: globalize.translate('TitleHardwareAcceleration'),
text: globalize.translate('HardwareAccelerationWarning')
}).then(onDecoderConfirmed);
} else {
onDecoderConfirmed();
}
return false;
}
function setDecodingCodecsVisible(context, value) {
value = value || '';
let any;
Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) {
if (c.getAttribute('data-types').split(',').indexOf(value) === -1) {
dom.parentWithTag(c, 'LABEL').classList.add('hide');
} else {
dom.parentWithTag(c, 'LABEL').classList.remove('hide');
any = true;
}
});
if (any) {
context.querySelector('.decodingCodecsList').classList.remove('hide');
} else {
context.querySelector('.decodingCodecsList').classList.add('hide');
}
}
function getTabs() {
return [{
href: '#/encodingsettings.html',
name: globalize.translate('Transcoding')
}, {
href: '#/playbackconfiguration.html',
name: globalize.translate('ButtonResume')
}, {
href: '#/streamingsettings.html',
name: globalize.translate('TabStreaming')
}];
}
let systemInfo;
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
info => {
systemInfo = info;
return info;
}
);
}
$(document).on('pageinit', '#encodingSettingsPage', function () {
const page = this;
getSystemInfo();
page.querySelector('#selectVideoDecoder').addEventListener('change', function () {
if (this.value == 'vaapi') {
page.querySelector('.fldVaapiDevice').classList.remove('hide');
page.querySelector('#txtVaapiDevice').setAttribute('required', 'required');
} else {
page.querySelector('.fldVaapiDevice').classList.add('hide');
page.querySelector('#txtVaapiDevice').removeAttribute('required');
}
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') {
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide');
} else {
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide');
}
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') {
page.querySelector('.tonemappingOptions').classList.remove('hide');
} else {
page.querySelector('.tonemappingOptions').classList.add('hide');
}
if (this.value == 'qsv' || this.value == 'vaapi') {
page.querySelector('.fldIntelLp').classList.remove('hide');
} else {
page.querySelector('.fldIntelLp').classList.add('hide');
}
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) {
page.querySelector('.vppTonemappingOptions').classList.remove('hide');
} else {
page.querySelector('.vppTonemappingOptions').classList.add('hide');
}
if (this.value == 'qsv') {
page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide');
} else {
page.querySelector('.fldSysNativeHwDecoder').classList.add('hide');
}
if (this.value == 'nvenc') {
page.querySelector('.fldEnhancedNvdec').classList.remove('hide');
} else {
page.querySelector('.fldEnhancedNvdec').classList.add('hide');
}
if (this.value) {
page.querySelector('.hardwareAccelerationOptions').classList.remove('hide');
} else {
page.querySelector('.hardwareAccelerationOptions').classList.add('hide');
}
setDecodingCodecsVisible(page, this.value);
});
$('#btnSelectEncoderPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
$('.txtEncoderPath', page).val(path);
}
picker.close();
}
});
});
});
$('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
callback: function (path) {
if (path) {
$('#txtTranscodingTempPath', page).val(path);
}
picker.close();
},
validateWriteable: true,
header: globalize.translate('HeaderSelectTranscodingPath'),
instruction: globalize.translate('HeaderSelectTranscodingPathHelp')
});
});
});
$('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
includeDirectories: true,
callback: function (path) {
if (path) {
page.querySelector('#txtFallbackFontPath').value = path;
}
picker.close();
},
header: globalize.translate('HeaderSelectFallbackFontPath'),
instruction: globalize.translate('HeaderSelectFallbackFontPathHelp')
});
});
});
$('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#encodingSettingsPage', function () {
loading.show(); loading.show();
libraryMenu.setTabs('playback', 0, getTabs);
const page = this;
ApiClient.getNamedConfiguration('encoding').then(function (config) { ApiClient.getNamedConfiguration('encoding').then(function (config) {
ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked;
loadPage(page, config, fetchedSystemInfo); config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val();
config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None';
config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value;
config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val();
config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value;
config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false;
config.EncodingThreadCount = $('#selectThreadCount', form).val();
config.HardwareAccelerationType = $('#selectVideoDecoder', form).val();
config.VaapiDevice = $('#txtVaapiDevice', form).val();
config.EnableTonemapping = form.querySelector('#chkTonemapping').checked;
config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked;
config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value;
config.TonemappingRange = form.querySelector('#selectTonemappingRange').value;
config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value;
config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value;
config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value;
config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0';
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10);
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10);
config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value;
config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked;
config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked;
config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked;
config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) {
return c.checked;
}), function (c) {
return c.getAttribute('data-codec');
});
config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked;
config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked;
config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked;
config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked;
config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked;
config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked;
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
ApiClient.updateNamedConfiguration('encoding', config).then(function () {
updateEncoder(form);
}, function () {
alert(globalize.translate('ErrorDefault'));
Dashboard.processServerConfigurationUpdateResult();
});
});
};
if ($('#selectVideoDecoder', form).val()) {
alert({
title: globalize.translate('TitleHardwareAcceleration'),
text: globalize.translate('HardwareAccelerationWarning')
}).then(onDecoderConfirmed);
} else {
onDecoderConfirmed();
}
return false;
}
function setDecodingCodecsVisible(context, value) {
value = value || '';
let any;
Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) {
if (c.getAttribute('data-types').split(',').indexOf(value) === -1) {
dom.parentWithTag(c, 'LABEL').classList.add('hide');
} else {
dom.parentWithTag(c, 'LABEL').classList.remove('hide');
any = true;
}
});
if (any) {
context.querySelector('.decodingCodecsList').classList.remove('hide');
} else {
context.querySelector('.decodingCodecsList').classList.add('hide');
}
}
function getTabs() {
return [{
href: '#/encodingsettings.html',
name: globalize.translate('Transcoding')
}, {
href: '#/playbackconfiguration.html',
name: globalize.translate('ButtonResume')
}, {
href: '#/streamingsettings.html',
name: globalize.translate('TabStreaming')
}];
}
let systemInfo;
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
info => {
systemInfo = info;
return info;
}
);
}
$(document).on('pageinit', '#encodingSettingsPage', function () {
const page = this;
getSystemInfo();
page.querySelector('#selectVideoDecoder').addEventListener('change', function () {
if (this.value == 'vaapi') {
page.querySelector('.fldVaapiDevice').classList.remove('hide');
page.querySelector('#txtVaapiDevice').setAttribute('required', 'required');
} else {
page.querySelector('.fldVaapiDevice').classList.add('hide');
page.querySelector('#txtVaapiDevice').removeAttribute('required');
}
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') {
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide');
} else {
page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide');
}
if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') {
page.querySelector('.tonemappingOptions').classList.remove('hide');
} else {
page.querySelector('.tonemappingOptions').classList.add('hide');
}
if (this.value == 'qsv' || this.value == 'vaapi') {
page.querySelector('.fldIntelLp').classList.remove('hide');
} else {
page.querySelector('.fldIntelLp').classList.add('hide');
}
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) {
page.querySelector('.vppTonemappingOptions').classList.remove('hide');
} else {
page.querySelector('.vppTonemappingOptions').classList.add('hide');
}
if (this.value == 'qsv') {
page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide');
} else {
page.querySelector('.fldSysNativeHwDecoder').classList.add('hide');
}
if (this.value == 'nvenc') {
page.querySelector('.fldEnhancedNvdec').classList.remove('hide');
} else {
page.querySelector('.fldEnhancedNvdec').classList.add('hide');
}
if (this.value) {
page.querySelector('.hardwareAccelerationOptions').classList.remove('hide');
} else {
page.querySelector('.hardwareAccelerationOptions').classList.add('hide');
}
setDecodingCodecsVisible(page, this.value);
});
$('#btnSelectEncoderPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
$('.txtEncoderPath', page).val(path);
}
picker.close();
}
}); });
}); });
}); });
$('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
callback: function (path) {
if (path) {
$('#txtTranscodingTempPath', page).val(path);
}
picker.close();
},
validateWriteable: true,
header: globalize.translate('HeaderSelectTranscodingPath'),
instruction: globalize.translate('HeaderSelectTranscodingPathHelp')
});
});
});
$('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
includeDirectories: true,
callback: function (path) {
if (path) {
page.querySelector('#txtFallbackFontPath').value = path;
}
picker.close();
},
header: globalize.translate('HeaderSelectFallbackFontPath'),
instruction: globalize.translate('HeaderSelectFallbackFontPathHelp')
});
});
});
$('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit);
}).on('pageshow', '#encodingSettingsPage', function () {
loading.show();
libraryMenu.setTabs('playback', 0, getTabs);
const page = this;
ApiClient.getNamedConfiguration('encoding').then(function (config) {
ApiClient.getSystemInfo().then(function (fetchedSystemInfo) {
loadPage(page, config, fetchedSystemInfo);
});
});
});
/* eslint-enable indent */
+90 -93
View File
@@ -10,110 +10,107 @@ import '../../elements/emby-button/emby-button';
import Dashboard from '../../utils/dashboard'; import Dashboard from '../../utils/dashboard';
import alert from '../../components/alert'; import alert from '../../components/alert';
/* eslint-disable indent */ function loadPage(page, config, languageOptions, systemInfo) {
page.querySelector('#txtServerName').value = systemInfo.ServerName;
page.querySelector('#txtCachePath').value = systemInfo.CachePath || '';
page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true;
$('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || '');
$('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || '');
$('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) {
return '<option value="' + language.Value + '">' + language.Name + '</option>';
})).val(config.UICulture);
page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || '';
function loadPage(page, config, languageOptions, systemInfo) { loading.hide();
page.querySelector('#txtServerName').value = systemInfo.ServerName; }
page.querySelector('#txtCachePath').value = systemInfo.CachePath || '';
page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true;
$('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || '');
$('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || '');
$('#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(); function onSubmit() {
} loading.show();
const form = this;
$(form).parents('.page');
ApiClient.getServerConfiguration().then(function (config) {
config.ServerName = $('#txtServerName', form).val();
config.UICulture = $('#selectLocalizationLanguage', form).val();
config.CachePath = form.querySelector('#txtCachePath').value;
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);
function onSubmit() { ApiClient.updateServerConfiguration(config).then(function() {
loading.show(); ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) {
const form = this; brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value;
$(form).parents('.page'); brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value;
ApiClient.getServerConfiguration().then(function (config) { brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked;
config.ServerName = $('#txtServerName', form).val();
config.UICulture = $('#selectLocalizationLanguage', form).val();
config.CachePath = form.querySelector('#txtCachePath').value;
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.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () {
ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { Dashboard.processServerConfigurationUpdateResult();
brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value;
brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value;
brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked;
ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () {
Dashboard.processServerConfigurationUpdateResult();
});
});
}, function () {
alert(globalize.translate('ErrorDefault'));
Dashboard.processServerConfigurationUpdateResult();
});
});
return false;
}
const brandingConfigKey = 'branding';
export default function (view) {
$('#btnSelectCachePath', view).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => {
const picker = new DirectoryBrowser();
picker.show({
callback: function (path) {
if (path) {
view.querySelector('#txtCachePath').value = path;
}
picker.close();
},
validateWriteable: true,
header: globalize.translate('HeaderSelectServerCachePath'),
instruction: globalize.translate('HeaderSelectServerCachePathHelp')
}); });
}); });
}, function () {
alert(globalize.translate('ErrorDefault'));
Dashboard.processServerConfigurationUpdateResult();
}); });
$('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { });
import('../../components/directorybrowser/directorybrowser').then(({default: DirectoryBrowser}) => { return false;
const picker = new DirectoryBrowser(); }
picker.show({
path: $('#txtMetadataPath', view).val(),
networkSharePath: $('#txtMetadataNetworkPath', view).val(),
callback: function (path, networkPath) {
if (path) {
$('#txtMetadataPath', view).val(path);
}
if (networkPath) { const brandingConfigKey = 'branding';
$('#txtMetadataNetworkPath', view).val(networkPath); export default function (view) {
} $('#btnSelectCachePath', view).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
callback: function (path) {
if (path) {
view.querySelector('#txtCachePath').value = path;
}
picker.close(); picker.close();
}, },
validateWriteable: true, validateWriteable: true,
header: globalize.translate('HeaderSelectMetadataPath'), header: globalize.translate('HeaderSelectServerCachePath'),
instruction: globalize.translate('HeaderSelectMetadataPathHelp'), instruction: globalize.translate('HeaderSelectServerCachePathHelp')
enableNetworkSharePath: true
});
}); });
}); });
$('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); });
view.addEventListener('viewshow', function () { $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () {
const promiseConfig = ApiClient.getServerConfiguration(); import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); const picker = new DirectoryBrowser();
const promiseSystemInfo = ApiClient.getSystemInfo(); picker.show({
Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) { path: $('#txtMetadataPath', view).val(),
loadPage(view, responses[0], responses[1], responses[2]); networkSharePath: $('#txtMetadataNetworkPath', view).val(),
}); callback: function (path, networkPath) {
ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) { if (path) {
view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; $('#txtMetadataPath', view).val(path);
view.querySelector('#txtCustomCss').value = config.CustomCss || ''; }
view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true;
if (networkPath) {
$('#txtMetadataNetworkPath', view).val(networkPath);
}
picker.close();
},
validateWriteable: true,
header: globalize.translate('HeaderSelectMetadataPath'),
instruction: globalize.translate('HeaderSelectMetadataPathHelp'),
enableNetworkSharePath: true
}); });
}); });
} });
$('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit);
view.addEventListener('viewshow', function () {
const promiseConfig = ApiClient.getServerConfiguration();
const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options'));
const promiseSystemInfo = ApiClient.getSystemInfo();
Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) {
loadPage(view, responses[0], responses[1], responses[2]);
});
ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) {
view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || '';
view.querySelector('#txtCustomCss').value = config.CustomCss || '';
view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true;
});
});
}
/* eslint-enable indent */
+350 -353
View File
@@ -12,399 +12,396 @@ import Dashboard, { pageClassOn, pageIdOn } from '../../utils/dashboard';
import confirm from '../../components/confirm/confirm'; import confirm from '../../components/confirm/confirm';
import cardBuilder from '../../components/cardbuilder/cardBuilder'; import cardBuilder from '../../components/cardbuilder/cardBuilder';
/* eslint-disable indent */ function addVirtualFolder(page) {
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => {
function addVirtualFolder(page) { new medialibrarycreator({
import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({default: medialibrarycreator}) => { collectionTypeOptions: getCollectionTypeOptions().filter(function (f) {
new medialibrarycreator({ return !f.hidden;
collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { }),
return !f.hidden; refresh: shouldRefreshLibraryAfterChanges(page)
}), }).then(function (hasChanges) {
refresh: shouldRefreshLibraryAfterChanges(page) if (hasChanges) {
}).then(function (hasChanges) {
if (hasChanges) {
reloadLibrary(page);
}
});
});
}
function editVirtualFolder(page, virtualFolder) {
import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({default: medialibraryeditor}) => {
new medialibraryeditor({
refresh: shouldRefreshLibraryAfterChanges(page),
library: virtualFolder
}).then(function (hasChanges) {
if (hasChanges) {
reloadLibrary(page);
}
});
});
}
function deleteVirtualFolder(page, virtualFolder) {
let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder');
if (virtualFolder.Locations.length) {
msg += '<br/><br/>' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '<br/><br/>';
msg += virtualFolder.Locations.join('<br/>');
}
confirm({
text: msg,
title: globalize.translate('HeaderRemoveMediaFolder'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(function () {
const refreshAfterChange = shouldRefreshLibraryAfterChanges(page);
ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () {
reloadLibrary(page); reloadLibrary(page);
}); }
}); });
});
}
function editVirtualFolder(page, virtualFolder) {
import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => {
new medialibraryeditor({
refresh: shouldRefreshLibraryAfterChanges(page),
library: virtualFolder
}).then(function (hasChanges) {
if (hasChanges) {
reloadLibrary(page);
}
});
});
}
function deleteVirtualFolder(page, virtualFolder) {
let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder');
if (virtualFolder.Locations.length) {
msg += '<br/><br/>' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '<br/><br/>';
msg += virtualFolder.Locations.join('<br/>');
} }
function refreshVirtualFolder(page, virtualFolder) { confirm({
import('../../components/refreshdialog/refreshdialog').then(({default: refreshDialog}) => { text: msg,
new refreshDialog({ title: globalize.translate('HeaderRemoveMediaFolder'),
itemIds: [virtualFolder.ItemId], confirmText: globalize.translate('Delete'),
serverId: ApiClient.serverId(), primary: 'delete'
mode: 'scan' }).then(function () {
}).show(); const refreshAfterChange = shouldRefreshLibraryAfterChanges(page);
ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () {
reloadLibrary(page);
}); });
} });
}
function renameVirtualFolder(page, virtualFolder) { function refreshVirtualFolder(page, virtualFolder) {
import('../../components/prompt/prompt').then(({default: prompt}) => { import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => {
prompt({ new refreshDialog({
label: globalize.translate('LabelNewName'), itemIds: [virtualFolder.ItemId],
description: globalize.translate('MessageRenameMediaFolder'), serverId: ApiClient.serverId(),
confirmText: globalize.translate('ButtonRename') mode: 'scan'
}).then(function (newName) { }).show();
if (newName && newName != virtualFolder.Name) { });
const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); }
ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () {
reloadLibrary(page); function renameVirtualFolder(page, virtualFolder) {
}); import('../../components/prompt/prompt').then(({ default: prompt }) => {
prompt({
label: globalize.translate('LabelNewName'),
description: globalize.translate('MessageRenameMediaFolder'),
confirmText: globalize.translate('ButtonRename')
}).then(function (newName) {
if (newName && newName != virtualFolder.Name) {
const refreshAfterChange = shouldRefreshLibraryAfterChanges(page);
ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () {
reloadLibrary(page);
});
}
});
});
}
function showCardMenu(page, elem, virtualFolders) {
const card = dom.parentWithClass(elem, 'card');
const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index];
const menuItems = [];
menuItems.push({
name: globalize.translate('EditImages'),
id: 'editimages',
icon: 'photo'
});
menuItems.push({
name: globalize.translate('ManageLibrary'),
id: 'edit',
icon: 'folder'
});
menuItems.push({
name: globalize.translate('ButtonRename'),
id: 'rename',
icon: 'mode_edit'
});
menuItems.push({
name: globalize.translate('ScanLibrary'),
id: 'refresh',
icon: 'refresh'
});
menuItems.push({
name: globalize.translate('ButtonRemove'),
id: 'delete',
icon: 'delete'
});
import('../../components/actionSheet/actionSheet').then((actionsheet) => {
actionsheet.show({
items: menuItems,
positionTo: elem,
callback: function (resultId) {
switch (resultId) {
case 'edit':
editVirtualFolder(page, virtualFolder);
break;
case 'editimages':
editImages(page, virtualFolder);
break;
case 'rename':
renameVirtualFolder(page, virtualFolder);
break;
case 'delete':
deleteVirtualFolder(page, virtualFolder);
break;
case 'refresh':
refreshVirtualFolder(page, virtualFolder);
} }
}); }
}); });
});
}
function reloadLibrary(page) {
loading.show();
ApiClient.getVirtualFolders().then(function (result) {
reloadVirtualFolders(page, result);
});
}
function shouldRefreshLibraryAfterChanges(page) {
return page.id === 'mediaLibraryPage';
}
function reloadVirtualFolders(page, virtualFolders) {
let html = '';
virtualFolders.push({
Name: globalize.translate('ButtonAddMediaLibrary'),
icon: 'add_circle',
Locations: [],
showType: false,
showLocations: false,
showMenu: false,
showNameWithIcon: false,
elementId: 'addLibrary'
});
for (let i = 0; i < virtualFolders.length; i++) {
const virtualFolder = virtualFolders[i];
html += getVirtualFolderHtml(page, virtualFolder, i);
} }
function showCardMenu(page, elem, virtualFolders) { const divVirtualFolders = page.querySelector('#divVirtualFolders');
const card = dom.parentWithClass(elem, 'card'); divVirtualFolders.innerHTML = html;
divVirtualFolders.classList.add('itemsContainer');
divVirtualFolders.classList.add('vertical-wrap');
$('.btnCardMenu', divVirtualFolders).on('click', function () {
showCardMenu(page, this, virtualFolders);
});
divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () {
addVirtualFolder(page);
});
$('.editLibrary', divVirtualFolders).on('click', function () {
const card = $(this).parents('.card')[0];
const index = parseInt(card.getAttribute('data-index'), 10); const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index]; const virtualFolder = virtualFolders[index];
const menuItems = [];
menuItems.push({
name: globalize.translate('EditImages'),
id: 'editimages',
icon: 'photo'
});
menuItems.push({
name: globalize.translate('ManageLibrary'),
id: 'edit',
icon: 'folder'
});
menuItems.push({
name: globalize.translate('ButtonRename'),
id: 'rename',
icon: 'mode_edit'
});
menuItems.push({
name: globalize.translate('ScanLibrary'),
id: 'refresh',
icon: 'refresh'
});
menuItems.push({
name: globalize.translate('ButtonRemove'),
id: 'delete',
icon: 'delete'
});
import('../../components/actionSheet/actionSheet').then((actionsheet) => { if (virtualFolder.ItemId) {
actionsheet.show({ editVirtualFolder(page, virtualFolder);
items: menuItems,
positionTo: elem,
callback: function (resultId) {
switch (resultId) {
case 'edit':
editVirtualFolder(page, virtualFolder);
break;
case 'editimages':
editImages(page, virtualFolder);
break;
case 'rename':
renameVirtualFolder(page, virtualFolder);
break;
case 'delete':
deleteVirtualFolder(page, virtualFolder);
break;
case 'refresh':
refreshVirtualFolder(page, virtualFolder);
}
}
});
});
}
function reloadLibrary(page) {
loading.show();
ApiClient.getVirtualFolders().then(function (result) {
reloadVirtualFolders(page, result);
});
}
function shouldRefreshLibraryAfterChanges(page) {
return page.id === 'mediaLibraryPage';
}
function reloadVirtualFolders(page, virtualFolders) {
let html = '';
virtualFolders.push({
Name: globalize.translate('ButtonAddMediaLibrary'),
icon: 'add_circle',
Locations: [],
showType: false,
showLocations: false,
showMenu: false,
showNameWithIcon: false,
elementId: 'addLibrary'
});
for (let i = 0; i < virtualFolders.length; i++) {
const virtualFolder = virtualFolders[i];
html += getVirtualFolderHtml(page, virtualFolder, i);
} }
});
loading.hide();
}
const divVirtualFolders = page.querySelector('#divVirtualFolders'); function editImages(page, virtualFolder) {
divVirtualFolders.innerHTML = html; import('../../components/imageeditor/imageeditor').then((imageEditor) => {
divVirtualFolders.classList.add('itemsContainer'); imageEditor.show({
divVirtualFolders.classList.add('vertical-wrap'); itemId: virtualFolder.ItemId,
$('.btnCardMenu', divVirtualFolders).on('click', function () { serverId: ApiClient.serverId()
showCardMenu(page, this, virtualFolders); }).then(function () {
reloadLibrary(page);
}); });
divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { });
addVirtualFolder(page); }
});
$('.editLibrary', divVirtualFolders).on('click', function () {
const card = $(this).parents('.card')[0];
const index = parseInt(card.getAttribute('data-index'), 10);
const virtualFolder = virtualFolders[index];
if (virtualFolder.ItemId) { function getLink(text, url) {
editVirtualFolder(page, virtualFolder); return globalize.translate(text, '<a is="emby-linkbutton" class="button-link" href="' + url + '" target="_blank" data-autohide="true">', '</a>');
} }
});
loading.hide(); function getCollectionTypeOptions() {
return [{
name: '',
value: ''
}, {
name: globalize.translate('Movies'),
value: 'movies',
message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies')
}, {
name: globalize.translate('TabMusic'),
value: 'music',
message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music')
}, {
name: globalize.translate('Shows'),
value: 'tvshows',
message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows')
}, {
name: globalize.translate('Books'),
value: 'books',
message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books')
}, {
name: globalize.translate('HomeVideosPhotos'),
value: 'homevideos'
}, {
name: globalize.translate('MusicVideos'),
value: 'musicvideos'
}, {
name: globalize.translate('MixedMoviesShows'),
value: 'mixed',
message: globalize.translate('MessageUnsetContentHelp')
}];
}
function getVirtualFolderHtml(page, virtualFolder, index) {
let html = '';
let style = '';
if (page.classList.contains('wizardPage')) {
style += 'min-width:33.3%;';
} }
function editImages(page, virtualFolder) { const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : '';
import('../../components/imageeditor/imageeditor').then((imageEditor) => { html += '<div ' + elementId + 'class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
imageEditor.show({
itemId: virtualFolder.ItemId, html += '<div class="cardBox visualCardBox">';
serverId: ApiClient.serverId() html += '<div class="cardScalable visualCardBox-cardScalable">';
}).then(function () { html += '<div class="cardPadder cardPadder-backdrop"></div>';
reloadLibrary(page); html += '<div class="cardContent">';
}); let imgUrl = '';
if (virtualFolder.PrimaryImageItemId) {
imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.40),
type: 'Primary'
}); });
} }
function getLink(text, url) { let hasCardImageContainer;
return globalize.translate(text, '<a is="emby-linkbutton" class="button-link" href="' + url + '" target="_blank" data-autohide="true">', '</a>');
if (imgUrl) {
html += `<div class="cardImageContainer editLibrary ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer">`;
html += `<img src="${imgUrl}" style="width:100%" />`;
hasCardImageContainer = true;
} else if (!virtualFolder.showNameWithIcon) {
html += `<div class="cardImageContainer editLibrary ${cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer;">`;
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '" aria-hidden="true"></span>';
hasCardImageContainer = true;
} }
function getCollectionTypeOptions() { if (hasCardImageContainer) {
return [{ html += '<div class="cardIndicators backdropCardIndicators">';
name: '', html += '<div is="emby-itemrefreshindicator"' + (virtualFolder.RefreshProgress || virtualFolder.RefreshStatus && virtualFolder.RefreshStatus !== 'Idle' ? '' : ' class="hide"') + ' data-progress="' + (virtualFolder.RefreshProgress || 0) + '" data-status="' + virtualFolder.RefreshStatus + '"></div>';
value: ''
}, {
name: globalize.translate('Movies'),
value: 'movies',
message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies')
}, {
name: globalize.translate('TabMusic'),
value: 'music',
message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music')
}, {
name: globalize.translate('Shows'),
value: 'tvshows',
message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows')
}, {
name: globalize.translate('Books'),
value: 'books',
message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books')
}, {
name: globalize.translate('HomeVideosPhotos'),
value: 'homevideos'
}, {
name: globalize.translate('MusicVideos'),
value: 'musicvideos'
}, {
name: globalize.translate('MixedMoviesShows'),
value: 'mixed',
message: globalize.translate('MessageUnsetContentHelp')
}];
}
function getVirtualFolderHtml(page, virtualFolder, index) {
let html = '';
let style = '';
if (page.classList.contains('wizardPage')) {
style += 'min-width:33.3%;';
}
const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : '';
html += '<div ' + elementId + 'class="card backdropCard scalableCard backdropCard-scalable" style="' + style + '" data-index="' + index + '" data-id="' + virtualFolder.ItemId + '">';
html += '<div class="cardBox visualCardBox">';
html += '<div class="cardScalable visualCardBox-cardScalable">';
html += '<div class="cardPadder cardPadder-backdrop"></div>';
html += '<div class="cardContent">';
let imgUrl = '';
if (virtualFolder.PrimaryImageItemId) {
imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, {
maxWidth: Math.round(dom.getScreenWidth() * 0.40),
type: 'Primary'
});
}
let hasCardImageContainer;
if (imgUrl) {
html += `<div class="cardImageContainer editLibrary ${imgUrl ? '' : cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer">`;
html += `<img src="${imgUrl}" style="width:100%" />`;
hasCardImageContainer = true;
} else if (!virtualFolder.showNameWithIcon) {
html += `<div class="cardImageContainer editLibrary ${cardBuilder.getDefaultBackgroundClass()}" style="cursor:pointer;">`;
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '" aria-hidden="true"></span>';
hasCardImageContainer = true;
}
if (hasCardImageContainer) {
html += '<div class="cardIndicators backdropCardIndicators">';
html += '<div is="emby-itemrefreshindicator"' + (virtualFolder.RefreshProgress || virtualFolder.RefreshStatus && virtualFolder.RefreshStatus !== 'Idle' ? '' : ' class="hide"') + ' data-progress="' + (virtualFolder.RefreshProgress || 0) + '" data-status="' + virtualFolder.RefreshStatus + '"></div>';
html += '</div>';
html += '</div>';
}
if (!imgUrl && virtualFolder.showNameWithIcon) {
html += '<h3 class="cardImageContainer addLibrary" style="position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer;flex-direction:column;">';
html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '" aria-hidden="true"></span>';
if (virtualFolder.showNameWithIcon) {
html += '<div style="margin:1em 0;position:width:100%;">';
html += escapeHtml(virtualFolder.Name);
html += '</div>';
}
html += '</h3>';
}
html += '</div>'; html += '</div>';
html += '</div>'; html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">'; // always show menu unless explicitly hidden }
if (virtualFolder.showMenu !== false) { if (!imgUrl && virtualFolder.showNameWithIcon) {
let dirTextAlign = 'right'; html += '<h3 class="cardImageContainer addLibrary" style="position:absolute;top:0;left:0;right:0;bottom:0;cursor:pointer;flex-direction:column;">';
if (globalize.getIsRTL()) html += '<span class="cardImageIcon material-icons ' + (virtualFolder.icon || imageHelper.getLibraryIcon(virtualFolder.CollectionType)) + '" aria-hidden="true"></span>';
dirTextAlign = 'left';
html += '<div style="text-align:' + dirTextAlign + '; float:' + dirTextAlign + ';padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
html += '</div>';
}
html += "<div class='cardText'>";
if (virtualFolder.showNameWithIcon) { if (virtualFolder.showNameWithIcon) {
html += '&nbsp;'; html += '<div style="margin:1em 0;position:width:100%;">';
} else {
html += escapeHtml(virtualFolder.Name); html += escapeHtml(virtualFolder.Name);
html += '</div>';
} }
html += '</h3>';
}
html += '</div>';
html += '</div>';
html += '<div class="cardFooter visualCardBox-cardFooter">'; // always show menu unless explicitly hidden
if (virtualFolder.showMenu !== false) {
let dirTextAlign = 'right';
if (globalize.getIsRTL())
dirTextAlign = 'left';
html += '<div style="text-align:' + dirTextAlign + '; float:' + dirTextAlign + ';padding-top:5px;">';
html += '<button type="button" is="paper-icon-button-light" class="btnCardMenu autoSize"><span class="material-icons more_vert" aria-hidden="true"></span></button>';
html += '</div>'; html += '</div>';
let typeName = getCollectionTypeOptions().filter(function (t) { }
return t.value == virtualFolder.CollectionType;
})[0]; html += "<div class='cardText'>";
typeName = typeName ? typeName.name : globalize.translate('Other');
if (virtualFolder.showNameWithIcon) {
html += '&nbsp;';
} else {
html += escapeHtml(virtualFolder.Name);
}
html += '</div>';
let typeName = getCollectionTypeOptions().filter(function (t) {
return t.value == virtualFolder.CollectionType;
})[0];
typeName = typeName ? typeName.name : globalize.translate('Other');
html += "<div class='cardText cardText-secondary'>";
if (virtualFolder.showType === false) {
html += '&nbsp;';
} else {
html += typeName;
}
html += '</div>';
if (virtualFolder.showLocations === false) {
html += "<div class='cardText cardText-secondary'>"; html += "<div class='cardText cardText-secondary'>";
html += '&nbsp;';
if (virtualFolder.showType === false) {
html += '&nbsp;';
} else {
html += typeName;
}
html += '</div>'; html += '</div>';
} else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) {
if (virtualFolder.showLocations === false) { html += "<div class='cardText cardText-secondary' dir='ltr' style='text-align:left;'>";
html += "<div class='cardText cardText-secondary'>"; html += virtualFolder.Locations[0];
html += '&nbsp;';
html += '</div>';
} else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) {
html += "<div class='cardText cardText-secondary' dir='ltr' style='text-align:left;'>";
html += virtualFolder.Locations[0];
html += '</div>';
} else {
html += "<div class='cardText cardText-secondary'>";
html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length);
html += '</div>';
}
html += '</div>'; html += '</div>';
} else {
html += "<div class='cardText cardText-secondary'>";
html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length);
html += '</div>'; html += '</div>';
html += '</div>';
return html;
} }
function getTabs() { html += '</div>';
return [{ html += '</div>';
href: '#/library.html', html += '</div>';
name: globalize.translate('HeaderLibraries') return html;
}, { }
href: '#/librarydisplay.html',
name: globalize.translate('Display') function getTabs() {
}, { return [{
href: '#/metadataimages.html', href: '#/library.html',
name: globalize.translate('Metadata') name: globalize.translate('HeaderLibraries')
}, { }, {
href: '#/metadatanfo.html', href: '#/librarydisplay.html',
name: globalize.translate('TabNfoSettings') name: globalize.translate('Display')
}]; }, {
href: '#/metadataimages.html',
name: globalize.translate('Metadata')
}, {
href: '#/metadatanfo.html',
name: globalize.translate('TabNfoSettings')
}];
}
window.WizardLibraryPage = {
next: function () {
Dashboard.navigate('wizardsettings.html');
} }
};
pageClassOn('pageshow', 'mediaLibraryPage', function () {
reloadLibrary(this);
});
pageIdOn('pageshow', 'mediaLibraryPage', function () {
libraryMenu.setTabs('librarysetup', 0, getTabs);
window.WizardLibraryPage = { const page = this;
next: function () { taskButton({
Dashboard.navigate('wizardsettings.html'); mode: 'on',
} progressElem: page.querySelector('.refreshProgress'),
}; taskKey: 'RefreshLibrary',
pageClassOn('pageshow', 'mediaLibraryPage', function () { button: page.querySelector('.btnRefresh')
reloadLibrary(this);
}); });
pageIdOn('pageshow', 'mediaLibraryPage', function () { });
libraryMenu.setTabs('librarysetup', 0, getTabs); pageIdOn('pagebeforehide', 'mediaLibraryPage', function () {
const page = this;
taskButton({
mode: 'off',
progressElem: page.querySelector('.refreshProgress'),
taskKey: 'RefreshLibrary',
button: page.querySelector('.btnRefresh')
});
});
const page = this;
taskButton({
mode: 'on',
progressElem: page.querySelector('.refreshProgress'),
taskKey: 'RefreshLibrary',
button: page.querySelector('.btnRefresh')
});
});
pageIdOn('pagebeforehide', 'mediaLibraryPage', function () {
const page = this;
taskButton({
mode: 'off',
progressElem: page.querySelector('.refreshProgress'),
taskKey: 'RefreshLibrary',
button: page.querySelector('.btnRefresh')
});
});
/* eslint-enable indent */

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