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