Merge branch 'master' into audio-normalization
This commit is contained in:
@@ -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
@@ -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
@@ -1,6 +1,2 @@
|
|||||||
.ci @dkanada @EraYaN
|
* @jellyfin/web
|
||||||
.github @jellyfin/core
|
.github @jellyfin/core
|
||||||
fedora @joshuaboniface
|
|
||||||
debian @joshuaboniface
|
|
||||||
.copr @joshuaboniface
|
|
||||||
deployment @joshuaboniface
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Generated
+3574
-2171
File diff suppressed because it is too large
Load Diff
+36
-36
@@ -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
@@ -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
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 */
|
|
||||||
|
|||||||
@@ -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
@@ -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 {};
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
+1445
-1448
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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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 */
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
|
|||||||
@@ -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
@@ -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 */
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 += ' ';
|
||||||
|
}
|
||||||
|
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 += ' ';
|
|
||||||
}
|
|
||||||
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 */
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+565
-568
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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] || '') || ' ';
|
||||||
|
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] || '') || ' ';
|
|
||||||
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
@@ -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 || ' ');
|
|
||||||
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 || ' ');
|
||||||
|
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
@@ -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('&', '&');
|
|
||||||
$('#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('&', '&');
|
||||||
|
$('#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
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { clearBackdrop } from '../backdrop/backdrop';
|
||||||
|
import * as mainTabsManager from '../maintabsmanager';
|
||||||
|
import layoutManager from '../layoutManager';
|
||||||
|
import '../../elements/emby-tabs/emby-tabs';
|
||||||
|
import LibraryMenu from '../../scripts/libraryMenu';
|
||||||
|
|
||||||
|
function onViewDestroy() {
|
||||||
|
const tabControllers = this.tabControllers;
|
||||||
|
|
||||||
|
if (tabControllers) {
|
||||||
|
tabControllers.forEach(function (t) {
|
||||||
|
if (t.destroy) {
|
||||||
|
t.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tabControllers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.view = null;
|
||||||
|
this.params = null;
|
||||||
|
this.currentTabController = null;
|
||||||
|
this.initialTabIndex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabbedView {
|
||||||
|
constructor(view, params) {
|
||||||
|
this.tabControllers = [];
|
||||||
|
this.view = view;
|
||||||
|
this.params = params;
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
let currentTabIndex = parseInt(params.tab || this.getDefaultTabIndex(params.parentId), 10);
|
||||||
|
this.initialTabIndex = currentTabIndex;
|
||||||
|
|
||||||
|
function validateTabLoad(index) {
|
||||||
|
return self.validateTabLoad ? self.validateTabLoad(index) : Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadTab(index, previousIndex) {
|
||||||
|
validateTabLoad(index).then(function () {
|
||||||
|
self.getTabController(index).then(function (controller) {
|
||||||
|
const refresh = !controller.refreshed;
|
||||||
|
|
||||||
|
controller.onResume({
|
||||||
|
autoFocus: previousIndex == null && layoutManager.tv,
|
||||||
|
refresh: refresh
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.refreshed = true;
|
||||||
|
|
||||||
|
currentTabIndex = index;
|
||||||
|
self.currentTabController = controller;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTabContainers() {
|
||||||
|
return view.querySelectorAll('.tabContent');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTabChange(e) {
|
||||||
|
const newIndex = parseInt(e.detail.selectedTabIndex, 10);
|
||||||
|
const previousIndex = e.detail.previousIndex;
|
||||||
|
|
||||||
|
const previousTabController = previousIndex == null ? null : self.tabControllers[previousIndex];
|
||||||
|
if (previousTabController && previousTabController.onPause) {
|
||||||
|
previousTabController.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTab(newIndex, previousIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
view.addEventListener('viewbeforehide', this.onPause.bind(this));
|
||||||
|
|
||||||
|
view.addEventListener('viewbeforeshow', function () {
|
||||||
|
mainTabsManager.setTabs(view, currentTabIndex, self.getTabs, getTabContainers, null, onTabChange, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.addEventListener('viewshow', function (e) {
|
||||||
|
self.onResume(e.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
view.addEventListener('viewdestroy', onViewDestroy.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onResume() {
|
||||||
|
this.setTitle();
|
||||||
|
clearBackdrop();
|
||||||
|
|
||||||
|
const currentTabController = this.currentTabController;
|
||||||
|
|
||||||
|
if (!currentTabController) {
|
||||||
|
mainTabsManager.selectedTabIndex(this.initialTabIndex);
|
||||||
|
} else if (currentTabController && currentTabController.onResume) {
|
||||||
|
currentTabController.onResume({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPause() {
|
||||||
|
const currentTabController = this.currentTabController;
|
||||||
|
|
||||||
|
if (currentTabController && currentTabController.onPause) {
|
||||||
|
currentTabController.onPause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTitle() {
|
||||||
|
LibraryMenu.setTitle('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TabbedView;
|
||||||
@@ -48,7 +48,7 @@ function refreshTunerDevices(page, providerInfo, devices) {
|
|||||||
function onSelectPathClick(e) {
|
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,
|
||||||
|
|||||||
@@ -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;"> </h2>';
|
||||||
|
|
||||||
html += '<h2 class="upNextDialog-nextVideoText" style="margin:.25em 0;"> </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
@@ -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,
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
@@ -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 */
|
|
||||||
|
|||||||
@@ -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 += ' ';
|
||||||
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 += ' ';
|
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
@@ -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 */
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
|
|||||||
@@ -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 += ' ';
|
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 += ' ';
|
||||||
|
} 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 += ' ';
|
||||||
|
} else {
|
||||||
|
html += typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if (virtualFolder.showLocations === false) {
|
||||||
html += "<div class='cardText cardText-secondary'>";
|
html += "<div class='cardText cardText-secondary'>";
|
||||||
|
html += ' ';
|
||||||
if (virtualFolder.showType === false) {
|
|
||||||
html += ' ';
|
|
||||||
} 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 += ' ';
|
|
||||||
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
Reference in New Issue
Block a user