Compare commits
38 Commits
release-10
...
release-10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc461a830d | ||
|
|
e6e5f38a0d | ||
|
|
756e322e1c | ||
|
|
c9ed8c750e | ||
|
|
5f1af65c2e | ||
|
|
ff88922f19 | ||
|
|
47700dc29c | ||
|
|
c2ebc35080 | ||
|
|
d040177525 | ||
|
|
cac6b42dab | ||
|
|
15e9b645f2 | ||
|
|
1833b348e9 | ||
|
|
ccb74610aa | ||
|
|
5c37ee6276 | ||
|
|
e25721cb34 | ||
|
|
f11b27fc14 | ||
|
|
26d29dc2cb | ||
|
|
537b3a3351 | ||
|
|
ab58d3c3a1 | ||
|
|
e7be594b8f | ||
|
|
229294aecc | ||
|
|
e41c1854d0 | ||
|
|
e355fcdac0 | ||
|
|
453d225c84 | ||
|
|
ec9ab39d26 | ||
|
|
c2d9ca2af9 | ||
|
|
eba30bda75 | ||
|
|
5c9847468f | ||
|
|
027cb4f744 | ||
|
|
37cde45d12 | ||
|
|
875c4e0882 | ||
|
|
ee9d651246 | ||
|
|
7f133ff8cd | ||
|
|
76f9b2c741 | ||
|
|
51768eb119 | ||
|
|
cdaf03a769 | ||
|
|
f1acfda7d2 | ||
|
|
fef30a0be3 |
@@ -8,6 +8,8 @@ jobs:
|
||||
BuildConfiguration: development
|
||||
Production:
|
||||
BuildConfiguration: production
|
||||
Standalone:
|
||||
BuildConfiguration: standalone
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
@@ -19,15 +21,15 @@ jobs:
|
||||
versionSpec: '12.x'
|
||||
|
||||
- task: Cache@2
|
||||
displayName: 'Cache node_modules'
|
||||
displayName: 'Check Cache'
|
||||
inputs:
|
||||
key: 'yarn | yarn.lock'
|
||||
path: 'node_modules'
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
|
||||
- script: 'yarn install --frozen-lockfile'
|
||||
displayName: 'Install Dependencies'
|
||||
env:
|
||||
SKIP_PREPARE: 'true'
|
||||
condition: ne(variables.CACHE_RESTORED, 'true')
|
||||
|
||||
- script: 'yarn build:development'
|
||||
displayName: 'Build Development'
|
||||
@@ -37,6 +39,10 @@ jobs:
|
||||
displayName: 'Build Production'
|
||||
condition: eq(variables['BuildConfiguration'], 'production')
|
||||
|
||||
- script: 'yarn build:standalone'
|
||||
displayName: 'Build Standalone'
|
||||
condition: eq(variables['BuildConfiguration'], 'standalone')
|
||||
|
||||
- script: 'test -d dist'
|
||||
displayName: 'Check Build'
|
||||
|
||||
|
||||
29
.ci/azure-pipelines-lint.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
jobs:
|
||||
- job: Lint
|
||||
displayName: 'Lint'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
displayName: 'Install Node'
|
||||
inputs:
|
||||
versionSpec: '12.x'
|
||||
|
||||
- task: Cache@2
|
||||
displayName: 'Check Cache'
|
||||
inputs:
|
||||
key: 'yarn | yarn.lock'
|
||||
path: 'node_modules'
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
|
||||
- script: 'yarn install --frozen-lockfile'
|
||||
displayName: 'Install Dependencies'
|
||||
condition: ne(variables.CACHE_RESTORED, 'true')
|
||||
|
||||
- script: 'yarn run lint --quiet'
|
||||
displayName: 'Run ESLint'
|
||||
|
||||
- script: 'yarn run stylelint'
|
||||
displayName: 'Run Stylelint'
|
||||
@@ -13,4 +13,5 @@ pr:
|
||||
|
||||
jobs:
|
||||
- template: azure-pipelines-build.yml
|
||||
- template: azure-pipelines-lint.yml
|
||||
- template: azure-pipelines-package.yml
|
||||
|
||||
5
.dependabot/config.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
version: 1
|
||||
update_configs:
|
||||
- package_manager: "javascript"
|
||||
directory: "/"
|
||||
update_schedule: "weekly"
|
||||
@@ -8,5 +8,5 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.json]
|
||||
[json]
|
||||
indent_size = 2
|
||||
|
||||
@@ -2,3 +2,4 @@ node_modules
|
||||
dist
|
||||
.idea
|
||||
.vscode
|
||||
src/libraries
|
||||
|
||||
32
.eslintrc.js
@@ -1,9 +1,6 @@
|
||||
const restrictedGlobals = require('confusing-browser-globals');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
plugins: [
|
||||
'@babel',
|
||||
'promise',
|
||||
'import',
|
||||
'eslint-comments'
|
||||
@@ -25,12 +22,13 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
// 'plugin:promise/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:eslint-comments/recommended',
|
||||
'plugin:compat/recommended'
|
||||
],
|
||||
rules: {
|
||||
'block-spacing': ['error'],
|
||||
'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
|
||||
'brace-style': ['error'],
|
||||
'comma-dangle': ['error', 'never'],
|
||||
'comma-spacing': ['error'],
|
||||
'eol-last': ['error'],
|
||||
@@ -40,25 +38,19 @@ module.exports = {
|
||||
'no-floating-decimal': ['error'],
|
||||
'no-multi-spaces': ['error'],
|
||||
'no-multiple-empty-lines': ['error', { 'max': 1 }],
|
||||
'no-restricted-globals': ['error'].concat(restrictedGlobals),
|
||||
'no-trailing-spaces': ['error'],
|
||||
'@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
|
||||
'one-var': ['error', 'never'],
|
||||
'padded-blocks': ['error', 'never'],
|
||||
'prefer-const': ['error', {'destructuring': 'all'}],
|
||||
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
|
||||
'@babel/semi': ['error'],
|
||||
'no-var': ['error'],
|
||||
'semi': ['error'],
|
||||
'space-before-blocks': ['error'],
|
||||
'space-infix-ops': 'error',
|
||||
'yoda': 'error'
|
||||
'space-infix-ops': 'error'
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'./src/**/*.js'
|
||||
],
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: 'babel-eslint',
|
||||
env: {
|
||||
node: false,
|
||||
amd: true,
|
||||
@@ -78,12 +70,17 @@ module.exports = {
|
||||
// Dependency globals
|
||||
'$': 'readonly',
|
||||
'jQuery': 'readonly',
|
||||
'requirejs': 'readonly',
|
||||
// Jellyfin globals
|
||||
'ApiClient': 'writable',
|
||||
'AppInfo': 'writable',
|
||||
'chrome': 'writable',
|
||||
'ConnectionManager': 'writable',
|
||||
'DlnaProfilePage': 'writable',
|
||||
'Dashboard': 'writable',
|
||||
'DashboardPage': 'writable',
|
||||
'Emby': 'readonly',
|
||||
'Events': 'writable',
|
||||
'getParameterByName': 'writable',
|
||||
'getWindowLocationSearch': 'writable',
|
||||
'Globalize': 'writable',
|
||||
@@ -92,8 +89,9 @@ module.exports = {
|
||||
'LibraryMenu': 'writable',
|
||||
'LinkParser': 'writable',
|
||||
'LiveTvHelpers': 'writable',
|
||||
'Loading': 'writable',
|
||||
'MetadataEditor': 'writable',
|
||||
'pageClassOn': 'writable',
|
||||
'pageIdOn': 'writable',
|
||||
'PlaylistViewer': 'writable',
|
||||
'UserParentalControlPage': 'writable',
|
||||
'Windows': 'readonly'
|
||||
@@ -101,8 +99,10 @@ module.exports = {
|
||||
rules: {
|
||||
// TODO: Fix warnings and remove these rules
|
||||
'no-redeclare': ['warn'],
|
||||
'no-unused-vars': ['warn'],
|
||||
'no-useless-escape': ['warn'],
|
||||
'no-unused-vars': ['warn']
|
||||
// TODO: Remove after ES6 migration is complete
|
||||
'import/no-unresolved': ['off']
|
||||
},
|
||||
settings: {
|
||||
polyfills: [
|
||||
@@ -193,4 +193,4 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
4
.github/CODEOWNERS
vendored
@@ -1,6 +1,4 @@
|
||||
.ci @dkanada @EraYaN
|
||||
.github @jellyfin/core
|
||||
fedora @joshuaboniface
|
||||
debian @joshuaboniface
|
||||
.copr @joshuaboniface
|
||||
build.sh @joshuaboniface
|
||||
deployment @joshuaboniface
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/2-playback-issue.md
vendored
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Playback Issue
|
||||
about: You have playback issues with some files
|
||||
labels: playback
|
||||
---
|
||||
|
||||
**Describe The Bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Media Information**
|
||||
<!-- Please paste any ffprobe or MediaInfo logs. -->
|
||||
|
||||
**Screenshots**
|
||||
<!-- Add screenshots from the Playback Data and Media Info. -->
|
||||
|
||||
**System (please complete the following information):**
|
||||
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
|
||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||
- Jellyfin Version: [e.g. 10.6.0]
|
||||
|
||||
**Additional Context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
13
.github/ISSUE_TEMPLATE/3-technical-discussion.md
vendored
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Technical Discussion
|
||||
about: You want to discuss technical aspects of changes you intend to make
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
<!-- Explain the change and the motivations behind it.
|
||||
|
||||
For example, if you plan to rely on a new dependency, explain why and what
|
||||
it brings to the project.
|
||||
|
||||
If you plan to make significant changes, go roughly over the steps you intend
|
||||
to take and how you would divide the change in PRs of a manageable size. -->
|
||||
9
.github/ISSUE_TEMPLATE/4-meta-issue.md
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Meta Issue
|
||||
about: You want to track a number of other issues as part of a larger project
|
||||
labels: meta
|
||||
---
|
||||
|
||||
* [ ] Issue 1 [#123]
|
||||
* [ ] Issue 2 [#456]
|
||||
* [ ] ...
|
||||
@@ -1,20 +1,23 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: You have noticed a general issue or regression, and would like to report it
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe The Bug**
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Steps To Reproduce**
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected Behavior**
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Logs**
|
||||
@@ -24,9 +27,9 @@ labels: bug
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**System (please complete the following information):**
|
||||
- Platform: [e.g. Linux, Windows, iPhone, Tizen]
|
||||
- OS: [e.g. Docker, Debian, Windows]
|
||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||
- Jellyfin Version: [e.g. 10.6.0]
|
||||
- Jellyfin Version: [e.g. 10.0.1]
|
||||
|
||||
**Additional Context**
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature Request
|
||||
url: https://features.jellyfin.org/
|
||||
about: Please head over to our feature request hub to vote on or submit a feature.
|
||||
- name: Help Or Question
|
||||
url: https://matrix.to/#/#jellyfin-troubleshooting:matrix.org
|
||||
about: Please join the troubleshooting Matrix channel to get some help.
|
||||
24
.github/SUPPORT.md
vendored
@@ -1,24 +0,0 @@
|
||||
# Support
|
||||
|
||||
Jellyfin contributors have limited availability to address general support
|
||||
questions. Please make sure you are using the latest version of Jellyfin.
|
||||
|
||||
When looking for support or information, please first search for your
|
||||
question in these venues:
|
||||
|
||||
* [Jellyfin Forum](https://forum.jellyfin.org)
|
||||
* [Jellyfin Documentation](https://docs.jellyfin.org)
|
||||
* [Open or **closed** issues in the organization](https://github.com/issues?q=sort%3Aupdated-desc+org%3Ajellyfin+is%3Aissue+)
|
||||
|
||||
If you didn't find an answer in the resources above, contributors and other
|
||||
users are reachable through the following channels:
|
||||
|
||||
* #jellyfin on [Matrix](https://matrix.to/#/#jellyfin:matrix.org%22) or [IRC](https://webchat.freenode.net/#jellyfin)
|
||||
* #jellyfin-troubleshooting on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [IRC](https://webchat.freenode.net/#jellyfin-troubleshooting)
|
||||
* [/r/jellyfin on Reddit](https://www.reddit.com/r/jellyfin)
|
||||
|
||||
GitHub issues are for tracking enhancements and bugs, not general support.
|
||||
|
||||
The open source license grants you the freedom to use Jellyfin.
|
||||
It does not guarantee commitments of other people's time.
|
||||
Please be respectful and manage your expectations.
|
||||
7
.github/dependabot.yaml
vendored
@@ -1,7 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
31
.github/workflows/codeql-analysis.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '30 7 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
95
.github/workflows/lint.yml
vendored
@@ -1,95 +0,0 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
run-eslint:
|
||||
name: Run eslint
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run eslint
|
||||
run: yarn lint
|
||||
|
||||
run-stylelint-css:
|
||||
name: Run stylelint (css)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Set up stylelint matcher
|
||||
uses: xt0rted/stylelint-problem-matcher@v1
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run stylelint
|
||||
run: yarn stylelint:css
|
||||
|
||||
run-stylelint-scss:
|
||||
name: Run stylelint (scss)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Set up stylelint matcher
|
||||
uses: xt0rted/stylelint-problem-matcher@v1
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
env:
|
||||
SKIP_PREPARE: true
|
||||
|
||||
- name: Run stylelint
|
||||
run: yarn stylelint:scss
|
||||
15
.github/workflows/merge-conflicts.yml
vendored
@@ -1,15 +0,0 @@
|
||||
name: "Merge Conflicts"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'jellyfin/jellyfin-web'
|
||||
steps:
|
||||
- uses: mschilde/auto-label-merge-conflicts@master
|
||||
with:
|
||||
CONFLICT_LABEL_NAME: "merge conflict"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
3
.gitignore
vendored
@@ -3,9 +3,6 @@ dist
|
||||
web
|
||||
node_modules
|
||||
|
||||
# config
|
||||
config.json
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"plugins": [
|
||||
"stylelint-no-browser-hacks/lib"
|
||||
"stylelint-no-browser-hacks/lib",
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-empty-line-before": [ "always", {
|
||||
"except": [
|
||||
except: [
|
||||
"blockless-after-same-name-blockless",
|
||||
"first-nested"
|
||||
"first-nested",
|
||||
],
|
||||
"ignore": ["after-comment"]
|
||||
ignore: ["after-comment"],
|
||||
} ],
|
||||
"at-rule-name-case": "lower",
|
||||
"at-rule-name-space-after": "always-single-line",
|
||||
@@ -26,27 +26,27 @@
|
||||
"color-hex-length": "short",
|
||||
"color-no-invalid-hex": true,
|
||||
"comment-empty-line-before": [ "always", {
|
||||
"except": ["first-nested"],
|
||||
"ignore": ["stylelint-commands"]
|
||||
except: ["first-nested"],
|
||||
ignore: ["stylelint-commands"],
|
||||
} ],
|
||||
"comment-no-empty": true,
|
||||
"comment-whitespace-inside": "always",
|
||||
"custom-property-empty-line-before": [ "always", {
|
||||
"except": [
|
||||
except: [
|
||||
"after-custom-property",
|
||||
"first-nested"
|
||||
"first-nested",
|
||||
],
|
||||
"ignore": [
|
||||
ignore: [
|
||||
"after-comment",
|
||||
"inside-single-line-block"
|
||||
]
|
||||
"inside-single-line-block",
|
||||
],
|
||||
} ],
|
||||
"declaration-bang-space-after": "never",
|
||||
"declaration-bang-space-before": "always",
|
||||
"declaration-block-no-duplicate-properties": [
|
||||
true,
|
||||
{
|
||||
"ignore": ["consecutive-duplicates-with-different-values"]
|
||||
ignore: ["consecutive-duplicates-with-different-values"]
|
||||
}
|
||||
],
|
||||
"declaration-block-no-shorthand-property-overrides": true,
|
||||
@@ -105,8 +105,8 @@
|
||||
}
|
||||
],
|
||||
"rule-empty-line-before": [ "always-multi-line", {
|
||||
"except": ["first-nested"],
|
||||
"ignore": ["after-comment"]
|
||||
except: ["first-nested"],
|
||||
ignore: ["after-comment"],
|
||||
} ],
|
||||
"selector-attribute-brackets-space-inside": "never",
|
||||
"selector-attribute-operator-space-after": "never",
|
||||
@@ -138,6 +138,6 @@
|
||||
"value-list-comma-newline-after": "always-multi-line",
|
||||
"value-list-comma-space-after": "always-single-line",
|
||||
"value-list-comma-space-before": "never",
|
||||
"value-list-max-empty-lines": 0
|
||||
"value-list-max-empty-lines": 0,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": [ "./.stylelintrc.json" ],
|
||||
"plugins": [ "stylelint-scss" ],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"plugin/no-browser-hacks": null
|
||||
}
|
||||
}
|
||||
@@ -34,17 +34,8 @@
|
||||
- [Ryan Hartzell](https://github.com/ryan-hartzell)
|
||||
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [artiume](https://github.com/Artiume)
|
||||
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||
- [Sarab Singh](https://github.com/sarab97)
|
||||
- [DesertCookie](https://github.com/desertcookie)
|
||||
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
|
||||
- [Andrei Oanca](https://github.com/OancaAndrei)
|
||||
- [Cromefire_](https://github.com/cromefire)
|
||||
- [Orry Verducci](https://github.com/orryverducci)
|
||||
- [Camc314](https://github.com/camc314)
|
||||
- [danieladov](https://github.com/danieladov)
|
||||
- [Stephane Senart](https://github.com/ssenart)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
|
||||
- Gulp-cli
|
||||
|
||||
@@ -69,8 +68,14 @@ Jellyfin Web is the frontend used for most of the clients available for end user
|
||||
yarn serve
|
||||
```
|
||||
|
||||
4. Build the client with sourcemaps available.
|
||||
4. Build the client with sourcemaps.
|
||||
|
||||
```sh
|
||||
yarn build:development
|
||||
```
|
||||
|
||||
You can build a nginx compatible version as well.
|
||||
|
||||
```sh
|
||||
yarn build:standalone
|
||||
```
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
babelrcRoots: [
|
||||
// Keep the root as a root
|
||||
'.'
|
||||
],
|
||||
sourceType: 'unambiguous',
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 3
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-private-methods',
|
||||
'babel-plugin-dynamic-import-polyfill'
|
||||
]
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# We just wrap `build` so this is really it
|
||||
name: "jellyfin-web"
|
||||
version: "10.7.7"
|
||||
version: "10.6.4"
|
||||
packages:
|
||||
- debian.all
|
||||
- fedora.all
|
||||
|
||||
46
debian/changelog
vendored
@@ -1,45 +1,29 @@
|
||||
jellyfin-web (10.7.7-1) unstable; urgency=medium
|
||||
jellyfin-web (10.6.4-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
|
||||
* New upstream version 10.6.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.4
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 05 Sep 2021 22:32:59 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 30 Aug 2020 16:45:08 -0400
|
||||
|
||||
jellyfin-web (10.7.6-1) unstable; urgency=medium
|
||||
jellyfin-web (10.6.3-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
|
||||
* New upstream version 10.6.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.3
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Thu, 20 May 2021 22:06:52 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 16 Aug 2020 19:48:03 -0400
|
||||
|
||||
jellyfin-web (10.7.5-1) unstable; urgency=medium
|
||||
jellyfin-web (10.6.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
|
||||
* New upstream version 10.6.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.2
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 22:08:30 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 02 Aug 2020 20:25:58 -0400
|
||||
|
||||
jellyfin-web (10.7.4-1) unstable; urgency=medium
|
||||
jellyfin-web (10.6.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
|
||||
* New upstream version 10.6.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.1
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 21:16:07 -0400
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 18:29:54 -0400
|
||||
|
||||
jellyfin-web (10.7.3-1) unstable; urgency=medium
|
||||
jellyfin-web (10.6.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
|
||||
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 04 May 2021 20:00:22 -0400
|
||||
|
||||
jellyfin-web (10.7.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:19:38 -0400
|
||||
|
||||
jellyfin-web (10.7.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
|
||||
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 21 Mar 2021 19:23:29 -0400
|
||||
|
||||
jellyfin-web (10.7.0-1) unstable; urgency=medium
|
||||
|
||||
* New upstream version 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
|
||||
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 16 Mar 2020 11:15:00 -0400
|
||||
|
||||
1
debian/conffiles
vendored
@@ -1 +0,0 @@
|
||||
/usr/share/jellyfin/web/config.json
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM node:lts-alpine
|
||||
FROM node:alpine
|
||||
|
||||
ARG SOURCE_DIR=/src
|
||||
ARG ARTIFACT_DIR=/jellyfin-web
|
||||
|
||||
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3
|
||||
RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python
|
||||
|
||||
WORKDIR ${SOURCE_DIR}
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM fedora:33
|
||||
FROM fedora:31
|
||||
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
@@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Fedora environment
|
||||
RUN dnf update -y \
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs nodejs-yarn autoconf automake glibc-devel
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs-yarn autoconf automake glibc-devel
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
%global debug_package %{nil}
|
||||
|
||||
Name: jellyfin-web
|
||||
Version: 10.7.7
|
||||
Version: 10.6.4
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media System web client
|
||||
License: GPLv3
|
||||
URL: https://jellyfin.org
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
|
||||
Source0: jellyfin-web-%{version}.tar.gz
|
||||
|
||||
%if 0%{?centos}
|
||||
BuildRequires: yarn
|
||||
# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it
|
||||
BuildRequires: git
|
||||
%else
|
||||
BuildRequires: nodejs-yarn
|
||||
%endif
|
||||
# sadly the yarn RPM at https://dl.yarnpkg.com/rpm/ uses git but doesn't Requires: it
|
||||
# ditto for Fedora's yarn RPM
|
||||
BuildRequires: git
|
||||
BuildArch: noarch
|
||||
%if 0%{?fedora} >= 33
|
||||
BuildRequires: nodejs
|
||||
%endif
|
||||
|
||||
# Disable Automatic Dependency Processing
|
||||
AutoReqProv: no
|
||||
@@ -41,28 +37,17 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%files
|
||||
%defattr(644,root,root,755)
|
||||
%{_datadir}/jellyfin-web
|
||||
%attr(755,root,root) %{_datadir}/jellyfin-web
|
||||
%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
%changelog
|
||||
* Sun Sep 05 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.7; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.7
|
||||
* Thu May 20 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.6; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.6
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.5; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.5
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.4
|
||||
* Tue May 04 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.3
|
||||
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.2
|
||||
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.1
|
||||
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New stable release 10.7.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.7.0
|
||||
* Sun Aug 30 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.6.4; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.4
|
||||
* Sun Aug 16 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.6.3; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.3
|
||||
* Sun Aug 02 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- New upstream version 10.6.2; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.2
|
||||
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- Forthcoming stable release
|
||||
- New upstream version 10.6.1; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.1
|
||||
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
- Forthcoming stable release
|
||||
|
||||
204
gulpfile.js
Normal file
@@ -0,0 +1,204 @@
|
||||
const { src, dest, series, parallel, watch } = require('gulp');
|
||||
const browserSync = require('browser-sync').create();
|
||||
const del = require('del');
|
||||
const babel = require('gulp-babel');
|
||||
const concat = require('gulp-concat');
|
||||
const terser = require('gulp-terser');
|
||||
const htmlmin = require('gulp-htmlmin');
|
||||
const imagemin = require('gulp-imagemin');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const mode = require('gulp-mode')({
|
||||
modes: ['development', 'production'],
|
||||
default: 'development',
|
||||
verbose: false
|
||||
});
|
||||
const stream = require('webpack-stream');
|
||||
const inject = require('gulp-inject');
|
||||
const postcss = require('gulp-postcss');
|
||||
const sass = require('gulp-sass');
|
||||
const gulpif = require('gulp-if');
|
||||
const lazypipe = require('lazypipe');
|
||||
|
||||
sass.compiler = require('node-sass');
|
||||
|
||||
let config;
|
||||
if (mode.production()) {
|
||||
config = require('./webpack.prod.js');
|
||||
} else {
|
||||
config = require('./webpack.dev.js');
|
||||
}
|
||||
|
||||
const options = {
|
||||
javascript: {
|
||||
query: ['src/**/*.js', '!src/bundle.js', '!src/standalone.js', '!src/scripts/apploader.js']
|
||||
},
|
||||
apploader: {
|
||||
query: ['src/standalone.js', 'src/scripts/apploader.js']
|
||||
},
|
||||
css: {
|
||||
query: ['src/**/*.css', 'src/**/*.scss']
|
||||
},
|
||||
html: {
|
||||
query: ['src/**/*.html', '!src/index.html']
|
||||
},
|
||||
images: {
|
||||
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
|
||||
},
|
||||
copy: {
|
||||
query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3']
|
||||
},
|
||||
injectBundle: {
|
||||
query: 'src/index.html'
|
||||
}
|
||||
};
|
||||
|
||||
function serve() {
|
||||
browserSync.init({
|
||||
server: {
|
||||
baseDir: './dist'
|
||||
},
|
||||
port: 8080
|
||||
});
|
||||
|
||||
const events = ['add', 'change'];
|
||||
|
||||
watch(options.javascript.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
javascript(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.apploader.query, apploader(true));
|
||||
|
||||
watch('src/bundle.js', webpack);
|
||||
|
||||
watch(options.css.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
css(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.html.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
html(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.images.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
images(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.copy.query).on('all', function (event, path) {
|
||||
if (events.includes(event)) {
|
||||
copy(path);
|
||||
}
|
||||
});
|
||||
|
||||
watch(options.injectBundle.query, injectBundle);
|
||||
}
|
||||
|
||||
function clean() {
|
||||
return del(['dist/']);
|
||||
}
|
||||
|
||||
const pipelineJavascript = lazypipe()
|
||||
.pipe(function () {
|
||||
return mode.development(sourcemaps.init({ loadMaps: true }));
|
||||
})
|
||||
.pipe(function () {
|
||||
return babel({
|
||||
presets: [
|
||||
['@babel/preset-env']
|
||||
]
|
||||
});
|
||||
})
|
||||
.pipe(function () {
|
||||
return terser({
|
||||
keep_fnames: true,
|
||||
mangle: false
|
||||
});
|
||||
})
|
||||
.pipe(function () {
|
||||
return mode.development(sourcemaps.write('.'));
|
||||
});
|
||||
|
||||
function javascript(query) {
|
||||
return src(typeof query !== 'function' ? query : options.javascript.query, { base: './src/' })
|
||||
.pipe(pipelineJavascript())
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function apploader(standalone) {
|
||||
function task() {
|
||||
return src(options.apploader.query, { base: './src/' })
|
||||
.pipe(gulpif(standalone, concat('scripts/apploader.js')))
|
||||
.pipe(pipelineJavascript())
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
task.displayName = 'apploader';
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
function webpack() {
|
||||
return stream(config)
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function css(query) {
|
||||
return src(typeof query !== 'function' ? query : options.css.query, { base: './src/' })
|
||||
.pipe(mode.development(sourcemaps.init({ loadMaps: true })))
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(postcss())
|
||||
.pipe(mode.development(sourcemaps.write('.')))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function html(query) {
|
||||
return src(typeof query !== 'function' ? query : options.html.query, { base: './src/' })
|
||||
.pipe(mode.production(htmlmin({ collapseWhitespace: true })))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function images(query) {
|
||||
return src(typeof query !== 'function' ? query : options.images.query, { base: './src/' })
|
||||
.pipe(mode.production(imagemin()))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function copy(query) {
|
||||
return src(typeof query !== 'function' ? query : options.copy.query, { base: './src/' })
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function injectBundle() {
|
||||
return src(options.injectBundle.query, { base: './src/' })
|
||||
.pipe(inject(
|
||||
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), {
|
||||
relative: true,
|
||||
transform: function (filepath) {
|
||||
return `<script src="${filepath}" defer></script>`;
|
||||
}
|
||||
}
|
||||
))
|
||||
.pipe(dest('dist/'))
|
||||
.pipe(browserSync.stream());
|
||||
}
|
||||
|
||||
function build(standalone) {
|
||||
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy));
|
||||
}
|
||||
|
||||
exports.default = series(build(false), injectBundle);
|
||||
exports.standalone = series(build(true), injectBundle);
|
||||
exports.serve = series(exports.standalone, serve);
|
||||
180
package.json
@@ -5,82 +5,146 @@
|
||||
"repository": "https://github.com/jellyfin/jellyfin-web",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
"@babel/eslint-parser": "^7.12.1",
|
||||
"@babel/eslint-plugin": "^7.12.1",
|
||||
"@babel/core": "^7.10.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.1",
|
||||
"@babel/plugin-proposal-private-methods": "^7.12.1",
|
||||
"@babel/plugin-transform-modules-umd": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.7",
|
||||
"@uupaa/dynamic-import-polyfill": "^1.0.2",
|
||||
"autoprefixer": "^9.8.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-dynamic-import-polyfill": "^1.0.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"confusing-browser-globals": "^1.0.10",
|
||||
"copy-webpack-plugin": "^6.3.2",
|
||||
"css-loader": "^5.0.1",
|
||||
"@babel/plugin-proposal-private-methods": "^7.10.1",
|
||||
"@babel/plugin-transform-modules-amd": "^7.9.6",
|
||||
"@babel/polyfill": "^7.8.7",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"autoprefixer": "^9.8.5",
|
||||
"babel-eslint": "^11.0.0-beta.2",
|
||||
"babel-loader": "^8.0.6",
|
||||
"browser-sync": "^2.26.7",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"css-loader": "^3.6.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"eslint": "^7.14.0",
|
||||
"del": "^5.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-compat": "^3.5.1",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-import": "^2.21.2",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"expose-loader": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^1.1.0",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"file-loader": "^6.0.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-cli": "^2.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-if": "^3.0.0",
|
||||
"gulp-imagemin": "^7.1.0",
|
||||
"gulp-inject": "^5.0.5",
|
||||
"gulp-mode": "^1.0.2",
|
||||
"gulp-postcss": "^8.0.0",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-terser": "^1.2.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"lazypipe": "^1.0.2",
|
||||
"node-sass": "^4.13.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"sass": "^1.29.0",
|
||||
"sass-loader": "^10.1.0",
|
||||
"source-map-loader": "^1.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"stylelint": "^13.8.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"stylelint": "^13.6.1",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-no-browser-hacks": "^1.2.1",
|
||||
"stylelint-order": "^4.1.0",
|
||||
"stylelint-scss": "^3.18.0",
|
||||
"webpack": "^5.9.0",
|
||||
"webpack-cli": "^4.0.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-merge": "^4.2.2",
|
||||
"workbox-webpack-plugin": "^6.1.5",
|
||||
"worker-plugin": "^5.0.0"
|
||||
"webpack-stream": "^5.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"alameda": "^1.4.0",
|
||||
"blurhash": "^1.1.3",
|
||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||
"core-js": "^3.8.0",
|
||||
"date-fns": "^2.16.1",
|
||||
"core-js": "^3.6.5",
|
||||
"date-fns": "^2.14.0",
|
||||
"epubjs": "^0.3.85",
|
||||
"fast-text-encoding": "^1.0.3",
|
||||
"flv.js": "^1.5.0",
|
||||
"fontsource-noto-sans": "^3.1.5",
|
||||
"fontsource-noto-sans-hk": "^3.1.5",
|
||||
"fontsource-noto-sans-jp": "^3.1.5",
|
||||
"fontsource-noto-sans-kr": "^3.1.5",
|
||||
"fontsource-noto-sans-sc": "^3.1.5",
|
||||
"headroom.js": "^0.12.0",
|
||||
"hls.js": "^0.14.17",
|
||||
"intersection-observer": "^0.12.0",
|
||||
"jellyfin-apiclient": "^1.8.0",
|
||||
"headroom.js": "^0.11.0",
|
||||
"hls.js": "^0.14.0",
|
||||
"howler": "^2.2.0",
|
||||
"intersection-observer": "^0.11.0",
|
||||
"jellyfin-apiclient": "^1.4.1",
|
||||
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
|
||||
"jquery": "^3.5.1",
|
||||
"jstree": "^3.3.10",
|
||||
"libarchive.js": "^1.3.0",
|
||||
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
|
||||
"material-design-icons-iconfont": "^6.1.0",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"native-promise-only": "^0.8.0-a",
|
||||
"page": "^1.11.6",
|
||||
"pdfjs-dist": "2.5.207",
|
||||
"query-string": "^6.13.1",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"screenfull": "^5.0.2",
|
||||
"sortablejs": "^1.12.0",
|
||||
"swiper": "^6.3.5",
|
||||
"shaka-player": "^2.5.13",
|
||||
"sortablejs": "^1.10.2",
|
||||
"swiper": "^5.4.5",
|
||||
"webcomponents.js": "^0.7.24",
|
||||
"whatwg-fetch": "^3.5.0",
|
||||
"workbox-core": "^5.1.4",
|
||||
"workbox-precaching": "^5.1.4"
|
||||
"whatwg-fetch": "^3.2.0"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@babel/preset-env"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"test": [
|
||||
"src/components/accessSchedule/accessSchedule.js",
|
||||
"src/components/actionSheet/actionSheet.js",
|
||||
"src/components/autoFocuser.js",
|
||||
"src/components/cardbuilder/cardBuilder.js",
|
||||
"src/components/cardbuilder/chaptercardbuilder.js",
|
||||
"src/components/cardbuilder/peoplecardbuilder.js",
|
||||
"src/components/images/imageLoader.js",
|
||||
"src/components/indicators/indicators.js",
|
||||
"src/components/lazyLoader/lazyLoaderIntersectionObserver.js",
|
||||
"src/components/playback/brightnessosd.js",
|
||||
"src/components/playback/mediasession.js",
|
||||
"src/components/playback/nowplayinghelper.js",
|
||||
"src/components/playback/playbackorientation.js",
|
||||
"src/components/playback/playerSelectionMenu.js",
|
||||
"src/components/playback/playersettingsmenu.js",
|
||||
"src/components/playback/playmethodhelper.js",
|
||||
"src/components/playback/remotecontrolautoplay.js",
|
||||
"src/components/playback/volumeosd.js",
|
||||
"src/components/playmenu.js",
|
||||
"src/components/sanatizefilename.js",
|
||||
"src/components/scrollManager.js",
|
||||
"src/components/syncPlay/groupSelectionMenu.js",
|
||||
"src/components/syncPlay/playbackPermissionManager.js",
|
||||
"src/components/syncPlay/syncPlayManager.js",
|
||||
"src/components/syncPlay/timeSyncManager.js",
|
||||
"src/controllers/dashboard/logs.js",
|
||||
"src/controllers/dashboard/plugins/repositories.js",
|
||||
"src/controllers/user/display.js",
|
||||
"src/controllers/user/home.js",
|
||||
"src/controllers/user/playback.js",
|
||||
"src/controllers/user/subtitles.js",
|
||||
"src/plugins/bookPlayer/plugin.js",
|
||||
"src/plugins/bookPlayer/tableOfContents.js",
|
||||
"src/plugins/photoPlayer/plugin.js",
|
||||
"src/scripts/deleteHelper.js",
|
||||
"src/scripts/dfnshelper.js",
|
||||
"src/scripts/dom.js",
|
||||
"src/scripts/fileDownloader.js",
|
||||
"src/scripts/filesystem.js",
|
||||
"src/scripts/imagehelper.js",
|
||||
"src/scripts/inputManager.js",
|
||||
"src/plugins/backdropScreensaver/plugin.js",
|
||||
"src/components/filterdialog/filterdialog.js",
|
||||
"src/components/fetchhelper.js",
|
||||
"src/scripts/keyboardNavigation.js",
|
||||
"src/scripts/settings/appSettings.js",
|
||||
"src/scripts/settings/userSettings.js",
|
||||
"src/scripts/settings/webSettings.js"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-modules-amd",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-private-methods"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"last 2 Firefox versions",
|
||||
@@ -99,14 +163,12 @@
|
||||
"Firefox ESR"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "yarn serve",
|
||||
"serve": "webpack serve --config webpack.dev.js",
|
||||
"prepare": "node ./scripts/prepare.js",
|
||||
"build:development": "webpack --config webpack.dev.js",
|
||||
"build:production": "webpack --config webpack.prod.js",
|
||||
"lint": "eslint \"src/\"",
|
||||
"stylelint": "yarn stylelint:css && yarn stylelint:scss",
|
||||
"stylelint:css": "stylelint \"src/**/*.css\"",
|
||||
"stylelint:scss": "stylelint --config=\".stylelintrc.scss.json\" \"src/**/*.scss\""
|
||||
"serve": "gulp serve --development",
|
||||
"prepare": "gulp --production",
|
||||
"build:development": "gulp --development",
|
||||
"build:production": "gulp --production",
|
||||
"build:standalone": "gulp standalone --development",
|
||||
"lint": "eslint \".\"",
|
||||
"stylelint": "stylelint \"src/**/*.css\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# load every string in the source language
|
||||
# print all duplicate values to a file
|
||||
|
||||
cwd = os.getcwd()
|
||||
source = cwd + '/../src/strings/en-us.json'
|
||||
|
||||
reverse = {}
|
||||
duplicates = {}
|
||||
|
||||
with open(source) as en:
|
||||
strings = json.load(en)
|
||||
for key, value in strings.items():
|
||||
if value not in reverse:
|
||||
reverse[value] = [key]
|
||||
else:
|
||||
reverse[value].append(key)
|
||||
|
||||
for key, value in reverse.items():
|
||||
if len(value) > 1:
|
||||
duplicates[key] = value
|
||||
|
||||
print('LENGTH: ' + str(len(duplicates)))
|
||||
with open('duplicates.txt', 'w') as out:
|
||||
for item in duplicates:
|
||||
out.write(json.dumps(item) + ': ')
|
||||
out.write(json.dumps(duplicates[item]) + '\n')
|
||||
out.close()
|
||||
|
||||
print('DONE')
|
||||
@@ -1,12 +0,0 @@
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* The npm `prepare` script needs to run a build to support installing
|
||||
* a package from git repositories (this is dumb but a limitation of how
|
||||
* npm behaves). We don't want to run these in CI though because
|
||||
* building is slow so this script will skip the build when the
|
||||
* `SKIP_PREPARE` environment variable has been set.
|
||||
*/
|
||||
if (!process.env.SKIP_PREPARE) {
|
||||
execSync('webpack --config webpack.prod.js', { stdio: 'inherit' });
|
||||
}
|
||||
@@ -16,7 +16,7 @@ langlst.append('en-us.json')
|
||||
dep = []
|
||||
|
||||
def grep(key):
|
||||
command = 'grep -r -E "(\\\"|\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key
|
||||
command = 'grep -r -E "(\(\\\"|\(\'|\{)%s(\\\"|\'|\})" --include=\*.{js,html} --exclude-dir=../src/strings ../src' % key
|
||||
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
output = p.stdout.readlines()
|
||||
if output:
|
||||
@@ -11,7 +11,7 @@ langlst = os.listdir(langdir)
|
||||
|
||||
keys = []
|
||||
|
||||
with open('unused.txt', 'r') as f:
|
||||
with open('scout.txt', 'r') as f:
|
||||
for line in f:
|
||||
keys.append(line.strip('\n'))
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<br />
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Connect}</span>
|
||||
<span>${ButtonConnect}</span>
|
||||
</button>
|
||||
<button is="emby-button" type="button" class="raised button-cancel block btnCancel">
|
||||
<span>${ButtonCancel}</span>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="content-primary">
|
||||
<div class="detailSectionHeader">
|
||||
<h2 style="margin:.6em 0;vertical-align:middle;display:inline-block;">${HeaderApiKeys}</h2>
|
||||
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${Add}">
|
||||
<button is="emby-button" type="button" class="fab btnNewKey submit" style="margin-left:1em;" title="${ButtonAdd}">
|
||||
<span class="material-icons add" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -127,8 +127,8 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.sessionAppInfo img {
|
||||
max-width: 2.5em;
|
||||
max-height: 2.5em;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@@ -204,10 +204,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.dashboardActionsContainer {
|
||||
margin: 1em -0.3em 0;
|
||||
}
|
||||
|
||||
.sessionNowPlayingContent {
|
||||
-webkit-background-size: cover;
|
||||
background-size: cover;
|
||||
@@ -235,13 +231,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.dashboardSection .sectionTitleTextButton > .material-icons.material-icons {
|
||||
font-size: 1.17em;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.activeRecordingItems > .card {
|
||||
width: 50%;
|
||||
}
|
||||
@@ -257,12 +246,20 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
|
||||
@media all and (min-width: 70em) {
|
||||
.dashboardSections {
|
||||
-webkit-flex-wrap: wrap;
|
||||
flex-wrap: wrap;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dashboardColumn-2-60 {
|
||||
flex-grow: 2;
|
||||
width: 46%;
|
||||
}
|
||||
|
||||
.dashboardColumn-2-40 {
|
||||
width: 27%;
|
||||
}
|
||||
|
||||
.dashboardSection {
|
||||
@@ -294,7 +291,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.activeSession {
|
||||
min-width: 20rem;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@@ -308,24 +304,27 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
.activeSession {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 50em) {
|
||||
.activeSession {
|
||||
max-width: 25rem;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
flex-basis: 50%;
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sessionCardFooter {
|
||||
padding-top: 0.5em !important;
|
||||
padding-bottom: 1em !important;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sessionAppInfo {
|
||||
flex-grow: 1;
|
||||
padding: 0.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -345,9 +344,11 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sessionNowPlayingContent-withbackground + .sessionNowPlayingInnerContent {
|
||||
color: #fff !important;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sessionAppName {
|
||||
@@ -357,6 +358,9 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
|
||||
.sessionNowPlayingDetails {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sessionNowPlayingInfo {
|
||||
@@ -372,6 +376,10 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
padding: 0.8em 0.5em;
|
||||
}
|
||||
|
||||
.sessionNowPlayingStreamInfo {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.playbackProgress,
|
||||
.transcodingProgress {
|
||||
margin: 0;
|
||||
@@ -379,12 +387,6 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.activeDevices.itemsContainer {
|
||||
/* offset for cardBox margin */
|
||||
margin: -0.6em;
|
||||
}
|
||||
|
||||
.activeSession .backgroundProgress,
|
||||
.activeSession .playbackProgress,
|
||||
.activeSession .transcodingProgress {
|
||||
position: absolute;
|
||||
@@ -401,14 +403,9 @@ div[data-role=controlgroup] a.ui-btn-active {
|
||||
}
|
||||
|
||||
.transcodingProgress > div {
|
||||
z-index: 10;
|
||||
background-color: #dd4919;
|
||||
}
|
||||
|
||||
.backgroundProgress > div {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
@media all and (max-width: 34.375em) {
|
||||
.sessionAppName {
|
||||
max-width: 160px;
|
||||
|
||||
37
src/assets/css/fonts.css
Normal file
@@ -0,0 +1,37 @@
|
||||
html {
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
font-size: 93%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
.layout-tv {
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
.layout-mobile {
|
||||
font-size: 90%;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
@import "../../styles/noto-sans/index.scss";
|
||||
|
||||
@mixin font($weight: null, $size: null) {
|
||||
font-family: "Noto Sans", "Noto Sans HK", "Noto Sans JP", "Noto Sans KR", "Noto Sans SC", sans-serif;
|
||||
font-weight: $weight;
|
||||
font-size: $size;
|
||||
}
|
||||
|
||||
html {
|
||||
@include font($size: 93%);
|
||||
text-size-adjust: 100%;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@include font(400, 1.8em);
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include font(400, 1.5em);
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include font(400, 1.17em);
|
||||
}
|
||||
|
||||
.layout-tv {
|
||||
/* Per WebOS and Tizen guidelines, fonts must be 20px minimum.
|
||||
This takes the 16px baseline and multiplies it by 1.25 to get 20px. */
|
||||
font-size: 125%;
|
||||
}
|
||||
|
||||
.layout-mobile {
|
||||
font-size: 90%;
|
||||
}
|
||||
31
src/assets/css/fonts.sized.css
Normal file
@@ -0,0 +1,31 @@
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.layout-desktop h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
@media all and (min-height: 720px) {
|
||||
html {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
|
||||
@media all and (min-height: 1000px) {
|
||||
html {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
@mixin header-font($size: null) {
|
||||
font-weight: 400;
|
||||
font-size: $size;
|
||||
}
|
||||
|
||||
html {
|
||||
@media all and (min-height: 720px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
|
||||
@media all and (min-height: 1000px) {
|
||||
font-size: 27px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@include header-font(1.8em);
|
||||
|
||||
.layout-desktop & {
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
@include header-font(1.8em);
|
||||
}
|
||||
|
||||
h3 {
|
||||
@include header-font(1.17em);
|
||||
}
|
||||
@@ -28,10 +28,6 @@
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.layout-tv .itemDetailPage {
|
||||
padding-top: 4.2em !important;
|
||||
}
|
||||
|
||||
.standalonePage {
|
||||
padding-top: 4.5em !important;
|
||||
}
|
||||
@@ -167,12 +163,6 @@
|
||||
transition: background ease-in-out 0.5s;
|
||||
}
|
||||
|
||||
.layout-tv .skinHeader {
|
||||
/* In TV layout, it makes more sense to keep the top bar at the top of the page
|
||||
Having it follow the view only makes us lose vertical space, while not being focusable */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hiddenViewMenuBar .skinHeader {
|
||||
display: none;
|
||||
}
|
||||
@@ -246,30 +236,16 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layout-desktop .searchTabButton,
|
||||
.layout-mobile .searchTabButton,
|
||||
.layout-tv .headerSearchButton {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mainDrawer-scrollContainer {
|
||||
padding-bottom: 10vh;
|
||||
}
|
||||
|
||||
.primaryImageWrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.primaryImageWrapper > img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 80vw;
|
||||
max-height: 50vh;
|
||||
}
|
||||
|
||||
.primaryImageWrapper > img.aspect-square {
|
||||
max-height: 45vh;
|
||||
}
|
||||
|
||||
.layout-mobile .primaryImageWrapper {
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
@media all and (min-width: 40em) {
|
||||
.dashboardDocument .adminDrawerLogo,
|
||||
.dashboardDocument .mainDrawerButton {
|
||||
@@ -473,10 +449,12 @@
|
||||
}
|
||||
|
||||
.layout-mobile .itemBackdrop {
|
||||
display: none;
|
||||
background-attachment: scroll;
|
||||
height: 26.5vh;
|
||||
}
|
||||
|
||||
.layout-desktop .itemBackdrop::after {
|
||||
.layout-desktop .itemBackdrop::after,
|
||||
.layout-tv .itemBackdrop::after {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -484,8 +462,8 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-tv .itemBackdrop,
|
||||
.layout-desktop .noBackdrop .itemBackdrop {
|
||||
.layout-desktop .noBackdrop .itemBackdrop,
|
||||
.layout-tv .noBackdrop .itemBackdrop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -633,8 +611,7 @@
|
||||
}
|
||||
|
||||
.layout-mobile .mainDetailButtons {
|
||||
flex: 2 0 70%;
|
||||
margin-top: 0.5em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
@@ -653,14 +630,10 @@
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.layout-tv .detailPagePrimaryContainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-mobile .detailPagePrimaryContainer {
|
||||
flex-wrap: wrap;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 4.5rem 3.3% 0.5rem;
|
||||
padding: 0.5em 3.3% 0.5em;
|
||||
}
|
||||
|
||||
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
|
||||
@@ -670,16 +643,12 @@
|
||||
padding-left: 32.45vw;
|
||||
}
|
||||
|
||||
.layout-desktop .detailRibbon {
|
||||
.layout-desktop .detailRibbon,
|
||||
.layout-tv .detailRibbon {
|
||||
margin-top: -7.2em;
|
||||
height: 7.2em;
|
||||
}
|
||||
|
||||
.layout-tv .detailRibbon {
|
||||
margin-top: 0;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.layout-desktop .noBackdrop .detailRibbon,
|
||||
.layout-tv .noBackdrop .detailRibbon {
|
||||
margin-top: 0;
|
||||
@@ -689,10 +658,6 @@
|
||||
flex: 1 0 0;
|
||||
}
|
||||
|
||||
.layout-mobile .infoWrapper {
|
||||
flex: 2 0 70%;
|
||||
}
|
||||
|
||||
.infoText {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@@ -753,8 +718,7 @@
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.noBackdrop .detailLogo,
|
||||
.layout-mobile .detailLogo {
|
||||
.noBackdrop .detailLogo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -779,17 +743,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
height: 23vw;
|
||||
}
|
||||
|
||||
.sectionTitleTextButton > .material-icons {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 0.35em;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.layout-mobile .sectionTitleTextButton > .material-icons {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
.itemDetailGalleryLink.defaultCardBackground > .material-icons {
|
||||
font-size: 15vw;
|
||||
margin-top: 50%;
|
||||
@@ -801,7 +754,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-desktop .itemBackdrop {
|
||||
.layout-desktop .itemBackdrop,
|
||||
.layout-tv .itemBackdrop {
|
||||
height: 40vh;
|
||||
}
|
||||
|
||||
@@ -827,8 +781,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
}
|
||||
|
||||
.emby-button.detailFloatingButton {
|
||||
font-size: 1.4em;
|
||||
margin-right: 0.5em !important;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 3;
|
||||
top: 100%;
|
||||
left: 90%;
|
||||
margin: -2.2em 0 0 -2.2em;
|
||||
padding: 0.4em;
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
@@ -891,7 +850,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin: 0 !important;
|
||||
padding: 0.7em 0.7em !important;
|
||||
padding: 0.5em 0.7em !important;
|
||||
}
|
||||
|
||||
@media all and (min-width: 29em) {
|
||||
@@ -960,6 +919,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
}
|
||||
|
||||
@media all and (min-width: 100em) {
|
||||
.detailFloatingButton {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.personBackdrop {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -968,11 +931,6 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
font-size: 108%;
|
||||
margin: 1.25em 0;
|
||||
}
|
||||
|
||||
.layout-tv .mainDetailButtons {
|
||||
font-size: 108%;
|
||||
margin: 1em 0 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 50em) {
|
||||
@@ -1093,7 +1051,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||
.sectionTitleButton,
|
||||
.sectionTitleIconButton {
|
||||
margin-right: 0 !important;
|
||||
display: inline-flex;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -1188,13 +1146,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
|
||||
}
|
||||
|
||||
.layout-tv .padded-top-focusscale {
|
||||
padding-top: 1.5em;
|
||||
margin-top: -1.5em;
|
||||
padding-top: 1em;
|
||||
margin-top: -1em;
|
||||
}
|
||||
|
||||
.layout-tv .padded-bottom-focusscale {
|
||||
padding-bottom: 1.5em;
|
||||
margin-bottom: -1.5em;
|
||||
padding-bottom: 1em;
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
|
||||
@media all and (min-height: 31.25em) {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
padding-bottom: 15em;
|
||||
}
|
||||
|
||||
#guideTab {
|
||||
@media all and (min-width: 62.5em) {
|
||||
@media all and (min-width: 62.5em) {
|
||||
#guideTab {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,14 @@
|
||||
@mixin fullpage {
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
@include fullpage;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
body {
|
||||
@include fullpage;
|
||||
overflow-x: hidden;
|
||||
background-color: transparent !important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
.layout-mobile,
|
||||
.layout-tv {
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clipForScreenReader {
|
||||
@@ -41,10 +36,14 @@ body {
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.layout-mobile,
|
||||
.layout-tv {
|
||||
-webkit-touch-callout: none;
|
||||
user-select: none;
|
||||
html {
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
background-color: transparent !important;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.mainAnimatedPage {
|
||||
@@ -59,7 +58,7 @@ body {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
div[data-role="page"] {
|
||||
div[data-role=page] {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
@@ -72,10 +71,10 @@ div[data-role="page"] {
|
||||
padding-left: 0.15em;
|
||||
font-weight: 400;
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
+ .fieldDescription {
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
.fieldDescription + .fieldDescription {
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.content-primary,
|
||||
@@ -86,14 +85,9 @@ div[data-role="page"] {
|
||||
padding-bottom: 5em !important;
|
||||
}
|
||||
|
||||
.readOnlyContent {
|
||||
@media all and (min-width: 50em) {
|
||||
max-width: 54em;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
@media all and (min-width: 50em) {
|
||||
@media all and (min-width: 50em) {
|
||||
.readOnlyContent,
|
||||
form {
|
||||
max-width: 54em;
|
||||
}
|
||||
}
|
||||
@@ -113,14 +107,14 @@ form {
|
||||
.headroom {
|
||||
will-change: transform;
|
||||
transition: transform 200ms linear;
|
||||
}
|
||||
|
||||
&--pinned {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
.headroom--pinned {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
&--unpinned {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
.headroom--unpinned {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.drawerContent {
|
||||
@@ -135,17 +129,3 @@ form {
|
||||
.hide-scroll {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.margin-auto-x {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.margin-auto-y {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
@@ -6,45 +6,29 @@
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.osdPoster img,
|
||||
.videoOsdBottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: fixed;
|
||||
background: linear-gradient(0deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
|
||||
padding-top: 7.5em;
|
||||
padding-bottom: 1.75em;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
will-change: opacity;
|
||||
transition: opacity 0.3s ease-out;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.skinHeader-withBackground.osdHeader {
|
||||
.osdHeader {
|
||||
-webkit-transition: opacity 0.3s ease-out;
|
||||
-o-transition: opacity 0.3s ease-out;
|
||||
transition: opacity 0.3s ease-out;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
background: linear-gradient(180deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
|
||||
backdrop-filter: none;
|
||||
color: #eee;
|
||||
height: 7.5em;
|
||||
pointer-events: none;
|
||||
background: rgba(0, 0, 0, 0.7) !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
color: #eee !important;
|
||||
}
|
||||
|
||||
.osdHeader-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.osdHeader .headerTop {
|
||||
pointer-events: all;
|
||||
max-height: 3.5em;
|
||||
}
|
||||
|
||||
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) {
|
||||
display: none;
|
||||
}
|
||||
@@ -102,18 +86,34 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.videoOsdBottom {
|
||||
position: fixed;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 1%;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
will-change: opacity;
|
||||
-webkit-transition: opacity 0.3s ease-out;
|
||||
-o-transition: opacity 0.3s ease-out;
|
||||
transition: opacity 0.3s ease-out;
|
||||
color: #fff;
|
||||
user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
.videoOsdBottom-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.osdControls {
|
||||
pointer-events: all;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
padding: 0 0.8em;
|
||||
}
|
||||
|
||||
.layout-desktop .osdControls {
|
||||
max-width: calc(100vh * 1.77 - 2vh);
|
||||
}
|
||||
|
||||
.videoOsdBottom .buttons {
|
||||
@@ -145,7 +145,7 @@
|
||||
}
|
||||
|
||||
.volumeButtons {
|
||||
margin: 0 1em 0 0.29em;
|
||||
margin: 0 0.5em 0 auto;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
@@ -153,13 +153,33 @@
|
||||
|
||||
.osdTimeText {
|
||||
margin-left: 1em;
|
||||
margin-right: auto;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.osdPoster {
|
||||
width: 10%;
|
||||
position: relative;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.osdPoster img {
|
||||
position: absolute;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
-webkit-box-shadow: 0 0 1.9vh #000;
|
||||
box-shadow: 0 0 1.9vh #000;
|
||||
border: 0.08em solid #222;
|
||||
user-drag: none;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.osdTitle,
|
||||
.osdTitleSmall {
|
||||
margin: 0 1em 0 0;
|
||||
@@ -228,6 +248,8 @@
|
||||
}
|
||||
|
||||
@media all and (max-width: 30em) {
|
||||
.btnFastForward,
|
||||
.btnRewind,
|
||||
.osdMediaInfo,
|
||||
.osdPoster {
|
||||
display: none !important;
|
||||
@@ -259,119 +281,3 @@
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.syncPlayContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.primary-icon {
|
||||
position: absolute;
|
||||
font-size: 64px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.primary-icon.spin {
|
||||
font-size: 76px !important;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.secondary-icon {
|
||||
position: absolute;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.secondary-icon.centered {
|
||||
font-size: 28px !important;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.secondary-icon.shifted {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
font-size: 52px;
|
||||
}
|
||||
|
||||
.syncPlayIconCircle {
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
border-radius: 50%;
|
||||
margin: 60px;
|
||||
height: 96px;
|
||||
width: 96px;
|
||||
|
||||
color: rgba(0, 164, 220, 0);
|
||||
background: rgba(0, 164, 220, 0);
|
||||
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.syncPlayIconCircle.oneShotPulse {
|
||||
animation: pulse 1.5s 1;
|
||||
}
|
||||
|
||||
.syncPlayIconCircle.infinitePulse {
|
||||
animation: infinite-pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
color: rgba(0, 164, 220, 0.7);
|
||||
background: rgba(0, 164, 220, 0.3);
|
||||
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0.3);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1);
|
||||
color: rgba(0, 164, 220, 0);
|
||||
background: rgba(0, 164, 220, 0);
|
||||
box-shadow: 0 0 0 60px rgba(0, 164, 220, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
color: rgba(0, 164, 220, 0);
|
||||
background: rgba(0, 164, 220, 0);
|
||||
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes infinite-pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
color: rgba(0, 164, 220, 0.7);
|
||||
background: rgba(0, 164, 220, 0.3);
|
||||
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0.3);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1);
|
||||
color: rgba(0, 164, 220, 0.6);
|
||||
background: rgba(0, 164, 220, 0);
|
||||
box-shadow: 0 0 0 60px rgba(0, 164, 220, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
color: rgba(0, 164, 220, 0.7);
|
||||
background: rgba(0, 164, 220, 0.3);
|
||||
box-shadow: 0 0 0 0 rgba(0, 164, 220, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 43 KiB |
183
src/bundle.js
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* require.js module definitions bundled by webpack
|
||||
*/
|
||||
// Use define from require.js not webpack's define
|
||||
var _define = window.define;
|
||||
|
||||
// fetch
|
||||
var fetch = require('whatwg-fetch');
|
||||
_define('fetch', function() {
|
||||
return fetch;
|
||||
});
|
||||
|
||||
// Blurhash
|
||||
var blurhash = require('blurhash');
|
||||
_define('blurhash', function() {
|
||||
return blurhash;
|
||||
});
|
||||
|
||||
// query-string
|
||||
var query = require('query-string');
|
||||
_define('queryString', function() {
|
||||
return query;
|
||||
});
|
||||
|
||||
// flvjs
|
||||
var flvjs = require('flv.js/dist/flv').default;
|
||||
_define('flvjs', function() {
|
||||
return flvjs;
|
||||
});
|
||||
|
||||
// jstree
|
||||
var jstree = require('jstree');
|
||||
require('jstree/dist/themes/default/style.css');
|
||||
_define('jstree', function() {
|
||||
return jstree;
|
||||
});
|
||||
|
||||
// jquery
|
||||
var jquery = require('jquery');
|
||||
_define('jQuery', function() {
|
||||
return jquery;
|
||||
});
|
||||
|
||||
// hlsjs
|
||||
var hlsjs = require('hls.js');
|
||||
_define('hlsjs', function() {
|
||||
return hlsjs;
|
||||
});
|
||||
|
||||
// howler
|
||||
var howler = require('howler');
|
||||
_define('howler', function() {
|
||||
return howler;
|
||||
});
|
||||
|
||||
// resize-observer-polyfill
|
||||
var resize = require('resize-observer-polyfill').default;
|
||||
_define('resize-observer-polyfill', function() {
|
||||
return resize;
|
||||
});
|
||||
|
||||
// swiper
|
||||
var swiper = require('swiper/js/swiper');
|
||||
require('swiper/css/swiper.min.css');
|
||||
_define('swiper', function() {
|
||||
return swiper;
|
||||
});
|
||||
|
||||
// sortable
|
||||
var sortable = require('sortablejs').default;
|
||||
_define('sortable', function() {
|
||||
return sortable;
|
||||
});
|
||||
|
||||
// webcomponents
|
||||
var webcomponents = require('webcomponents.js/webcomponents-lite');
|
||||
_define('webcomponents', function() {
|
||||
return webcomponents;
|
||||
});
|
||||
|
||||
// shaka
|
||||
var shaka = require('shaka-player');
|
||||
_define('shaka', function() {
|
||||
return shaka;
|
||||
});
|
||||
|
||||
// libass-wasm
|
||||
var libassWasm = require('libass-wasm');
|
||||
_define('JavascriptSubtitlesOctopus', function() {
|
||||
return libassWasm;
|
||||
});
|
||||
|
||||
// material-icons
|
||||
var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css');
|
||||
_define('material-icons', function() {
|
||||
return materialIcons;
|
||||
});
|
||||
|
||||
// noto font
|
||||
var noto = require('jellyfin-noto');
|
||||
_define('jellyfin-noto', function () {
|
||||
return noto;
|
||||
});
|
||||
|
||||
var epubjs = require('epubjs');
|
||||
_define('epubjs', function () {
|
||||
return epubjs;
|
||||
});
|
||||
|
||||
// page.js
|
||||
var page = require('page');
|
||||
_define('page', function() {
|
||||
return page;
|
||||
});
|
||||
|
||||
// core-js
|
||||
var polyfill = require('@babel/polyfill/dist/polyfill');
|
||||
_define('polyfill', function () {
|
||||
return polyfill;
|
||||
});
|
||||
|
||||
// domtokenlist-shim
|
||||
var classlist = require('classlist.js');
|
||||
_define('classlist-polyfill', function () {
|
||||
return classlist;
|
||||
});
|
||||
|
||||
// Date-FNS
|
||||
var dateFns = require('date-fns');
|
||||
_define('date-fns', function () {
|
||||
return dateFns;
|
||||
});
|
||||
|
||||
var dateFnsLocale = require('date-fns/locale');
|
||||
_define('date-fns/locale', function () {
|
||||
return dateFnsLocale;
|
||||
});
|
||||
|
||||
var fast_text_encoding = require('fast-text-encoding');
|
||||
_define('fast-text-encoding', function () {
|
||||
return fast_text_encoding;
|
||||
});
|
||||
|
||||
// intersection-observer
|
||||
var intersection_observer = require('intersection-observer');
|
||||
_define('intersection-observer', function () {
|
||||
return intersection_observer;
|
||||
});
|
||||
|
||||
// screenfull
|
||||
var screenfull = require('screenfull');
|
||||
_define('screenfull', function () {
|
||||
return screenfull;
|
||||
});
|
||||
|
||||
// headroom.js
|
||||
var headroom = require('headroom.js/dist/headroom');
|
||||
_define('headroom', function () {
|
||||
return headroom;
|
||||
});
|
||||
|
||||
// apiclient
|
||||
var apiclient = require('jellyfin-apiclient');
|
||||
|
||||
_define('apiclient', function () {
|
||||
return apiclient.ApiClient;
|
||||
});
|
||||
|
||||
_define('events', function () {
|
||||
return apiclient.Events;
|
||||
});
|
||||
|
||||
_define('credentialprovider', function () {
|
||||
return apiclient.Credentials;
|
||||
});
|
||||
|
||||
_define('connectionManagerFactory', function () {
|
||||
return apiclient.ConnectionManager;
|
||||
});
|
||||
|
||||
_define('appStorage', function () {
|
||||
return apiclient.AppStorage;
|
||||
});
|
||||
@@ -1,79 +0,0 @@
|
||||
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
|
||||
import { appHost } from './apphost';
|
||||
import Dashboard from '../scripts/clientUtils';
|
||||
import { setUserInfo } from '../scripts/settings/userSettings';
|
||||
|
||||
class ServerConnections extends ConnectionManager {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.localApiClient = null;
|
||||
|
||||
Events.on(this, 'localusersignedout', function () {
|
||||
setUserInfo(null, null);
|
||||
});
|
||||
}
|
||||
|
||||
initApiClient(server) {
|
||||
console.debug('creating ApiClient singleton');
|
||||
|
||||
const apiClient = new ApiClient(
|
||||
server,
|
||||
appHost.appName(),
|
||||
appHost.appVersion(),
|
||||
appHost.deviceName(),
|
||||
appHost.deviceId()
|
||||
);
|
||||
|
||||
apiClient.enableAutomaticNetworking = false;
|
||||
apiClient.manualAddressOnly = true;
|
||||
|
||||
this.addApiClient(apiClient);
|
||||
|
||||
this.setLocalApiClient(apiClient);
|
||||
|
||||
console.debug('loaded ApiClient singleton');
|
||||
}
|
||||
|
||||
setLocalApiClient(apiClient) {
|
||||
if (apiClient) {
|
||||
this.localApiClient = apiClient;
|
||||
window.ApiClient = apiClient;
|
||||
}
|
||||
}
|
||||
|
||||
getLocalApiClient() {
|
||||
return this.localApiClient;
|
||||
}
|
||||
|
||||
currentApiClient() {
|
||||
let apiClient = this.getLocalApiClient();
|
||||
|
||||
if (!apiClient) {
|
||||
const server = this.getLastUsedServer();
|
||||
|
||||
if (server) {
|
||||
apiClient = this.getApiClient(server.Id);
|
||||
}
|
||||
}
|
||||
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
onLocalUserSignedIn(user) {
|
||||
const apiClient = this.getApiClient(user.ServerId);
|
||||
this.setLocalApiClient(apiClient);
|
||||
return setUserInfo(user.Id, apiClient);
|
||||
}
|
||||
}
|
||||
|
||||
const credentials = new Credentials();
|
||||
|
||||
const capabilities = Dashboard.capabilities(appHost);
|
||||
|
||||
export default new ServerConnections(
|
||||
credentials,
|
||||
appHost.appName(),
|
||||
appHost.appVersion(),
|
||||
appHost.deviceName(),
|
||||
appHost.deviceId(),
|
||||
capabilities);
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
/**
|
||||
@@ -6,13 +5,12 @@
|
||||
* @module components/accessSchedule/accessSchedule
|
||||
*/
|
||||
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import datetime from '../../scripts/datetime';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../formdialog.css';
|
||||
import template from './accessSchedule.template.html';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
import datetime from 'datetime';
|
||||
import globalize from 'globalize';
|
||||
import 'emby-select';
|
||||
import 'paper-icon-button-light';
|
||||
import 'formDialogStyle';
|
||||
|
||||
function getDisplayTime(hours) {
|
||||
let minutes = 0;
|
||||
@@ -51,7 +49,7 @@ import template from './accessSchedule.template.html';
|
||||
};
|
||||
|
||||
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
|
||||
return void alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
|
||||
return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd'));
|
||||
}
|
||||
|
||||
context.submitted = true;
|
||||
@@ -61,31 +59,34 @@ import template from './accessSchedule.template.html';
|
||||
|
||||
export function show(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const dlg = dialogHelper.createDialog({
|
||||
removeOnClose: true,
|
||||
size: 'small'
|
||||
});
|
||||
dlg.classList.add('formDialog');
|
||||
let html = '';
|
||||
html += globalize.translateHtml(template);
|
||||
dlg.innerHTML = html;
|
||||
populateHours(dlg);
|
||||
loadSchedule(dlg, options.schedule);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.addEventListener('close', () => {
|
||||
if (dlg.submitted) {
|
||||
resolve(options.schedule);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
dlg.querySelector('form').addEventListener('submit', event => {
|
||||
submitSchedule(dlg, options);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
// TODO: remove require
|
||||
require(['text!./components/accessSchedule/accessSchedule.template.html'], template => {
|
||||
const dlg = dialogHelper.createDialog({
|
||||
removeOnClose: true,
|
||||
size: 'small'
|
||||
});
|
||||
dlg.classList.add('formDialog');
|
||||
let html = '';
|
||||
html += globalize.translateDocument(template);
|
||||
dlg.innerHTML = html;
|
||||
populateHours(dlg);
|
||||
loadSchedule(dlg, options.schedule);
|
||||
dialogHelper.open(dlg);
|
||||
dlg.addEventListener('close', () => {
|
||||
if (dlg.submitted) {
|
||||
resolve(options.schedule);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
dlg.querySelector('form').addEventListener('submit', event => {
|
||||
submitSchedule(dlg, options);
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${Previous}" tabindex="-1">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${LabelPrevious}" tabindex="-1">
|
||||
<span class="material-icons arrow_back" aria-hidden="true"></span>
|
||||
</button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
@@ -12,13 +12,13 @@
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
|
||||
<option value="Sunday">${Sunday}</option>
|
||||
<option value="Monday">${Monday}</option>
|
||||
<option value="Tuesday">${Tuesday}</option>
|
||||
<option value="Wednesday">${Wednesday}</option>
|
||||
<option value="Thursday">${Thursday}</option>
|
||||
<option value="Friday">${Friday}</option>
|
||||
<option value="Saturday">${Saturday}</option>
|
||||
<option value="Sunday">${OptionSunday}</option>
|
||||
<option value="Monday">${OptionMonday}</option>
|
||||
<option value="Tuesday">${OptionTuesday}</option>
|
||||
<option value="Wednesday">${OptionWednesday}</option>
|
||||
<option value="Thursday">${OptionThursday}</option>
|
||||
<option value="Friday">${OptionFriday}</option>
|
||||
<option value="Saturday">${OptionSaturday}</option>
|
||||
<option value="Everyday">${OptionEveryday}</option>
|
||||
<option value="Weekday">${OptionWeekdays}</option>
|
||||
<option value="Weekend">${OptionWeekends}</option>
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<div class="formDialogFooter">
|
||||
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
|
||||
<span>${Add}</span>
|
||||
<span>${ButtonAdd}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import layoutManager from '../layoutManager';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import dom from '../../scripts/dom';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import './actionSheet.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../../assets/css/scrollstyles.css';
|
||||
import '../../components/listview/listview.css';
|
||||
import dialogHelper from 'dialogHelper';
|
||||
import layoutManager from 'layoutManager';
|
||||
import globalize from 'globalize';
|
||||
import dom from 'dom';
|
||||
import 'emby-button';
|
||||
import 'css!./actionSheet';
|
||||
import 'material-icons';
|
||||
import 'scrollStyles';
|
||||
import 'listViewStyle';
|
||||
|
||||
function getOffsets(elems) {
|
||||
const results = [];
|
||||
|
||||
let results = [];
|
||||
|
||||
if (!document) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (const elem of elems) {
|
||||
const box = elem.getBoundingClientRect();
|
||||
let box = elem.getBoundingClientRect();
|
||||
|
||||
results.push({
|
||||
top: box.top,
|
||||
@@ -30,11 +31,12 @@ function getOffsets(elems) {
|
||||
}
|
||||
|
||||
function getPosition(options, dlg) {
|
||||
|
||||
const windowSize = dom.getWindowSize();
|
||||
const windowHeight = windowSize.innerHeight;
|
||||
const windowWidth = windowSize.innerWidth;
|
||||
|
||||
const pos = getOffsets([options.positionTo])[0];
|
||||
let pos = getOffsets([options.positionTo])[0];
|
||||
|
||||
if (options.positionY !== 'top') {
|
||||
pos.top += (pos.height || 0) / 2;
|
||||
@@ -71,18 +73,19 @@ function getPosition(options, dlg) {
|
||||
}
|
||||
|
||||
function centerFocus(elem, horiz, on) {
|
||||
import('../../scripts/scrollHelper').then((scrollHelper) => {
|
||||
require(['scrollHelper'], function (scrollHelper) {
|
||||
const fn = on ? 'on' : 'off';
|
||||
scrollHelper.centerFocus[fn](elem, horiz);
|
||||
});
|
||||
}
|
||||
|
||||
export function show(options) {
|
||||
|
||||
// items
|
||||
// positionTo
|
||||
// showCancel
|
||||
// title
|
||||
const dialogOptions = {
|
||||
let dialogOptions = {
|
||||
removeOnClose: true,
|
||||
enableHistory: options.enableHistory,
|
||||
scrollY: false
|
||||
@@ -95,6 +98,7 @@ export function show(options) {
|
||||
isFullscreen = true;
|
||||
dialogOptions.autoFocus = true;
|
||||
} else {
|
||||
|
||||
dialogOptions.modal = false;
|
||||
dialogOptions.entryAnimation = options.entryAnimation;
|
||||
dialogOptions.exitAnimation = options.exitAnimation;
|
||||
@@ -103,7 +107,7 @@ export function show(options) {
|
||||
dialogOptions.autoFocus = false;
|
||||
}
|
||||
|
||||
const dlg = dialogHelper.createDialog(dialogOptions);
|
||||
let dlg = dialogHelper.createDialog(dialogOptions);
|
||||
|
||||
if (isFullscreen) {
|
||||
dlg.classList.add('actionsheet-fullscreen');
|
||||
@@ -129,9 +133,10 @@ export function show(options) {
|
||||
}
|
||||
|
||||
let renderIcon = false;
|
||||
const icons = [];
|
||||
let icons = [];
|
||||
let itemIcon;
|
||||
for (const item of options.items) {
|
||||
|
||||
itemIcon = item.icon || (item.selected ? 'check' : null);
|
||||
|
||||
if (itemIcon) {
|
||||
@@ -156,6 +161,7 @@ export function show(options) {
|
||||
}
|
||||
|
||||
if (options.title) {
|
||||
|
||||
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
|
||||
}
|
||||
if (options.text) {
|
||||
@@ -191,6 +197,7 @@ export function show(options) {
|
||||
const item = options.items[i];
|
||||
|
||||
if (item.divider) {
|
||||
|
||||
html += '<div class="actionsheetDivider"></div>';
|
||||
continue;
|
||||
}
|
||||
@@ -241,13 +248,15 @@ export function show(options) {
|
||||
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
|
||||
}
|
||||
|
||||
const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
|
||||
let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
|
||||
if (btnCloseActionSheet) {
|
||||
btnCloseActionSheet.addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
}
|
||||
|
||||
// Seeing an issue in some non-chrome browsers where this is requiring a double click
|
||||
//var eventName = browser.firefox ? 'mousedown' : 'click';
|
||||
let selectedId;
|
||||
|
||||
let timeout;
|
||||
@@ -258,20 +267,26 @@ export function show(options) {
|
||||
}
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
let isResolved;
|
||||
|
||||
dlg.addEventListener('click', function (e) {
|
||||
|
||||
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
|
||||
|
||||
if (actionSheetMenuItem) {
|
||||
selectedId = actionSheetMenuItem.getAttribute('data-id');
|
||||
|
||||
if (options.resolveOnClick) {
|
||||
|
||||
if (options.resolveOnClick.indexOf) {
|
||||
|
||||
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
|
||||
|
||||
resolve(selectedId);
|
||||
isResolved = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
resolve(selectedId);
|
||||
isResolved = true;
|
||||
@@ -280,9 +295,11 @@ export function show(options) {
|
||||
|
||||
dialogHelper.close(dlg);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
dlg.addEventListener('close', function () {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import globalize from '../scripts/globalize';
|
||||
import dom from '../scripts/dom';
|
||||
import * as datefns from 'date-fns';
|
||||
import dfnshelper from '../scripts/dfnshelper';
|
||||
import serverNotifications from '../scripts/serverNotifications';
|
||||
import '../elements/emby-button/emby-button';
|
||||
import './listview/listview.css';
|
||||
import ServerConnections from './ServerConnections';
|
||||
import alert from './alert';
|
||||
|
||||
/* eslint-disable indent */
|
||||
define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', 'serverNotifications', 'connectionManager', 'emby-button', 'listViewStyle'], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) {
|
||||
'use strict';
|
||||
|
||||
function getEntryHtml(entry, apiClient) {
|
||||
let html = '';
|
||||
var html = '';
|
||||
html += '<div class="listItem listItem-border">';
|
||||
let color = '#00a4dc';
|
||||
let icon = 'notifications';
|
||||
var color = '#00a4dc';
|
||||
var icon = 'notifications';
|
||||
|
||||
if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') {
|
||||
if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) {
|
||||
color = '#cc0000';
|
||||
icon = 'notification_important';
|
||||
}
|
||||
@@ -61,15 +51,14 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
function reloadData(instance, elem, apiClient, startIndex, limit) {
|
||||
if (startIndex == null) {
|
||||
if (null == startIndex) {
|
||||
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0');
|
||||
}
|
||||
|
||||
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7');
|
||||
const minDate = new Date();
|
||||
const hasUserId = elem.getAttribute('data-useractivity') !== 'false';
|
||||
var minDate = new Date();
|
||||
var hasUserId = 'false' !== elem.getAttribute('data-useractivity');
|
||||
|
||||
// TODO: Use date-fns
|
||||
if (hasUserId) {
|
||||
minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
|
||||
} else {
|
||||
@@ -85,7 +74,7 @@ import alert from './alert';
|
||||
elem.setAttribute('data-activitystartindex', startIndex);
|
||||
elem.setAttribute('data-activitylimit', limit);
|
||||
if (!startIndex) {
|
||||
const activityContainer = dom.parentWithClass(elem, 'activityContainer');
|
||||
var activityContainer = dom.parentWithClass(elem, 'activityContainer');
|
||||
|
||||
if (activityContainer) {
|
||||
if (result.Items.length) {
|
||||
@@ -102,7 +91,7 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
function onActivityLogUpdate(e, apiClient, data) {
|
||||
const options = this.options;
|
||||
var options = this.options;
|
||||
|
||||
if (options && options.serverId === apiClient.serverId()) {
|
||||
reloadData(this, options.element, apiClient);
|
||||
@@ -110,14 +99,14 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
function onListClick(e) {
|
||||
const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
|
||||
var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
|
||||
|
||||
if (btnEntryInfo) {
|
||||
const id = btnEntryInfo.getAttribute('data-id');
|
||||
const items = this.items;
|
||||
var id = btnEntryInfo.getAttribute('data-id');
|
||||
var items = this.items;
|
||||
|
||||
if (items) {
|
||||
const item = items.filter(function (i) {
|
||||
var item = items.filter(function (i) {
|
||||
return i.Id.toString() === id;
|
||||
})[0];
|
||||
|
||||
@@ -129,43 +118,43 @@ import alert from './alert';
|
||||
}
|
||||
|
||||
function showItemOverview(item) {
|
||||
alert({
|
||||
text: item.Overview
|
||||
require(['alert'], function (alert) {
|
||||
alert({
|
||||
text: item.Overview
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class ActivityLog {
|
||||
constructor(options) {
|
||||
function ActivityLog(options) {
|
||||
this.options = options;
|
||||
const element = options.element;
|
||||
var element = options.element;
|
||||
element.classList.add('activityLogListWidget');
|
||||
element.addEventListener('click', onListClick.bind(this));
|
||||
const apiClient = ServerConnections.getApiClient(options.serverId);
|
||||
var apiClient = connectionManager.getApiClient(options.serverId);
|
||||
reloadData(this, element, apiClient);
|
||||
const onUpdate = onActivityLogUpdate.bind(this);
|
||||
var onUpdate = onActivityLogUpdate.bind(this);
|
||||
this.updateFn = onUpdate;
|
||||
Events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
|
||||
events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
|
||||
apiClient.sendMessage('ActivityLogEntryStart', '0,1500');
|
||||
}
|
||||
destroy() {
|
||||
const options = this.options;
|
||||
|
||||
ActivityLog.prototype.destroy = function () {
|
||||
var options = this.options;
|
||||
|
||||
if (options) {
|
||||
options.element.classList.remove('activityLogListWidget');
|
||||
ServerConnections.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
|
||||
connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
|
||||
}
|
||||
|
||||
const onUpdate = this.updateFn;
|
||||
var onUpdate = this.updateFn;
|
||||
|
||||
if (onUpdate) {
|
||||
Events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
|
||||
events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
|
||||
}
|
||||
|
||||
this.items = null;
|
||||
this.options = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default ActivityLog;
|
||||
|
||||
/* eslint-enable indent */
|
||||
return ActivityLog;
|
||||
});
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
|
||||
import browser from '../scripts/browser';
|
||||
import dialog from './dialog/dialog';
|
||||
import globalize from '../scripts/globalize';
|
||||
|
||||
/* eslint-disable indent */
|
||||
define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize) {
|
||||
'use strict';
|
||||
|
||||
function replaceAll(originalString, strReplace, strWith) {
|
||||
const reg = new RegExp(strReplace, 'ig');
|
||||
var reg = new RegExp(strReplace, 'ig');
|
||||
return originalString.replace(reg, strWith);
|
||||
}
|
||||
|
||||
export default function (text, title) {
|
||||
let options;
|
||||
return function (text, title) {
|
||||
|
||||
var options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
@@ -24,7 +21,7 @@ import globalize from '../scripts/globalize';
|
||||
if (browser.tv && window.alert) {
|
||||
alert(replaceAll(options.text || '', '<br/>', '\n'));
|
||||
} else {
|
||||
const items = [];
|
||||
var items = [];
|
||||
|
||||
items.push({
|
||||
name: globalize.translate('ButtonGotIt'),
|
||||
@@ -34,7 +31,7 @@ import globalize from '../scripts/globalize';
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
return dialog.show(options).then(function (result) {
|
||||
return dialog(options).then(function (result) {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -44,6 +41,5 @@ import globalize from '../scripts/globalize';
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
/* eslint-disable indent */
|
||||
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module alphaPicker.
|
||||
* @module components/alphaPicker/alphaPicker
|
||||
*/
|
||||
|
||||
import focusManager from '../focusManager';
|
||||
import layoutManager from '../layoutManager';
|
||||
import dom from '../../scripts/dom';
|
||||
import './style.css';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import 'material-design-icons-iconfont';
|
||||
|
||||
const selectedButtonClass = 'alphaPickerButton-selected';
|
||||
var selectedButtonClass = 'alphaPickerButton-selected';
|
||||
|
||||
function focus() {
|
||||
const scope = this;
|
||||
const selected = scope.querySelector(`.${selectedButtonClass}`);
|
||||
var scope = this;
|
||||
var selected = scope.querySelector('.' + selectedButtonClass);
|
||||
|
||||
if (selected) {
|
||||
focusManager.focus(selected);
|
||||
@@ -26,7 +15,8 @@ import 'material-design-icons-iconfont';
|
||||
}
|
||||
|
||||
function getAlphaPickerButtonClassName(vertical) {
|
||||
let alphaPickerButtonClassName = 'alphaPickerButton';
|
||||
|
||||
var alphaPickerButtonClassName = 'alphaPickerButton';
|
||||
|
||||
if (layoutManager.tv) {
|
||||
alphaPickerButtonClassName += ' alphaPickerButton-tv';
|
||||
@@ -40,42 +30,44 @@ import 'material-design-icons-iconfont';
|
||||
}
|
||||
|
||||
function getLetterButton(l, vertical) {
|
||||
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
|
||||
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + '</button>';
|
||||
}
|
||||
|
||||
function mapLetters(letters, vertical) {
|
||||
return letters.map(l => {
|
||||
|
||||
return letters.map(function (l) {
|
||||
return getLetterButton(l, vertical);
|
||||
});
|
||||
}
|
||||
|
||||
function render(element, options) {
|
||||
|
||||
element.classList.add('alphaPicker');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
element.classList.add('alphaPicker-tv');
|
||||
}
|
||||
|
||||
const vertical = element.classList.contains('alphaPicker-vertical');
|
||||
var vertical = element.classList.contains('alphaPicker-vertical');
|
||||
|
||||
if (!vertical) {
|
||||
element.classList.add('focuscontainer-x');
|
||||
}
|
||||
|
||||
let html = '';
|
||||
let letters;
|
||||
var html = '';
|
||||
var letters;
|
||||
|
||||
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
|
||||
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
|
||||
|
||||
let rowClassName = 'alphaPickerRow';
|
||||
var rowClassName = 'alphaPickerRow';
|
||||
|
||||
if (vertical) {
|
||||
rowClassName += ' alphaPickerRow-vertical';
|
||||
}
|
||||
|
||||
html += `<div class="${rowClassName}">`;
|
||||
html += '<div class="' + rowClassName + '">';
|
||||
if (options.mode === 'keyboard') {
|
||||
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
|
||||
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>';
|
||||
} else {
|
||||
letters = ['#'];
|
||||
html += mapLetters(letters, vertical).join('');
|
||||
@@ -85,11 +77,11 @@ import 'material-design-icons-iconfont';
|
||||
html += mapLetters(letters, vertical).join('');
|
||||
|
||||
if (options.mode === 'keyboard') {
|
||||
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>`;
|
||||
html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>';
|
||||
html += '</div>';
|
||||
|
||||
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
html += `<div class="${rowClassName}">`;
|
||||
html += '<div class="' + rowClassName + '">';
|
||||
html += '<br/>';
|
||||
html += mapLetters(letters, vertical).join('');
|
||||
html += '</div>';
|
||||
@@ -103,211 +95,227 @@ import 'material-design-icons-iconfont';
|
||||
element.focus = focus;
|
||||
}
|
||||
|
||||
export class AlphaPicker {
|
||||
constructor(options) {
|
||||
const self = this;
|
||||
function AlphaPicker(options) {
|
||||
|
||||
this.options = options;
|
||||
var self = this;
|
||||
this.options = options;
|
||||
|
||||
const element = options.element;
|
||||
const itemsContainer = options.itemsContainer;
|
||||
const itemClass = options.itemClass;
|
||||
var element = options.element;
|
||||
var itemsContainer = options.itemsContainer;
|
||||
var itemClass = options.itemClass;
|
||||
|
||||
let itemFocusValue;
|
||||
let itemFocusTimeout;
|
||||
var itemFocusValue;
|
||||
var itemFocusTimeout;
|
||||
|
||||
function onItemFocusTimeout() {
|
||||
itemFocusTimeout = null;
|
||||
self.value(itemFocusValue);
|
||||
}
|
||||
|
||||
let alphaFocusedElement;
|
||||
let alphaFocusTimeout;
|
||||
|
||||
function onAlphaFocusTimeout() {
|
||||
alphaFocusTimeout = null;
|
||||
|
||||
if (document.activeElement === alphaFocusedElement) {
|
||||
const value = alphaFocusedElement.getAttribute('data-value');
|
||||
self.value(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
function onAlphaPickerInKeyboardModeClick(e) {
|
||||
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
const value = alphaPickerButton.getAttribute('data-value');
|
||||
|
||||
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
|
||||
cancelable: false,
|
||||
detail: {
|
||||
value
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function onAlphaPickerClick(e) {
|
||||
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
const value = alphaPickerButton.getAttribute('data-value');
|
||||
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
|
||||
this.value(null, true);
|
||||
} else {
|
||||
this.value(value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onAlphaPickerFocusIn(e) {
|
||||
if (alphaFocusTimeout) {
|
||||
clearTimeout(alphaFocusTimeout);
|
||||
alphaFocusTimeout = null;
|
||||
}
|
||||
|
||||
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
alphaFocusedElement = alphaPickerButton;
|
||||
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
|
||||
}
|
||||
}
|
||||
|
||||
function onItemsFocusIn(e) {
|
||||
const item = dom.parentWithClass(e.target, itemClass);
|
||||
|
||||
if (item) {
|
||||
const prefix = item.getAttribute('data-prefix');
|
||||
if (prefix && prefix.length) {
|
||||
itemFocusValue = prefix[0];
|
||||
if (itemFocusTimeout) {
|
||||
clearTimeout(itemFocusTimeout);
|
||||
}
|
||||
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.enabled = function (enabled) {
|
||||
if (enabled) {
|
||||
if (itemsContainer) {
|
||||
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
|
||||
}
|
||||
|
||||
if (options.mode === 'keyboard') {
|
||||
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||
}
|
||||
|
||||
if (options.valueChangeEvent !== 'click') {
|
||||
element.addEventListener('focus', onAlphaPickerFocusIn, true);
|
||||
} else {
|
||||
element.addEventListener('click', onAlphaPickerClick.bind(this));
|
||||
}
|
||||
} else {
|
||||
if (itemsContainer) {
|
||||
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
|
||||
}
|
||||
|
||||
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
|
||||
element.removeEventListener('click', onAlphaPickerClick.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
render(element, options);
|
||||
|
||||
this.enabled(true);
|
||||
this.visible(true);
|
||||
function onItemFocusTimeout() {
|
||||
itemFocusTimeout = null;
|
||||
self.value(itemFocusValue);
|
||||
}
|
||||
|
||||
value(value, applyValue) {
|
||||
const element = this.options.element;
|
||||
let btn;
|
||||
let selected;
|
||||
var alphaFocusedElement;
|
||||
var alphaFocusTimeout;
|
||||
|
||||
if (value !== undefined) {
|
||||
if (value != null) {
|
||||
value = value.toUpperCase();
|
||||
this._currentValue = value;
|
||||
function onAlphaFocusTimeout() {
|
||||
|
||||
if (this.options.mode !== 'keyboard') {
|
||||
selected = element.querySelector(`.${selectedButtonClass}`);
|
||||
alphaFocusTimeout = null;
|
||||
|
||||
try {
|
||||
btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`);
|
||||
} catch (err) {
|
||||
console.error('error in querySelector:', err);
|
||||
}
|
||||
|
||||
if (btn && btn !== selected) {
|
||||
btn.classList.add(selectedButtonClass);
|
||||
}
|
||||
if (selected && selected !== btn) {
|
||||
selected.classList.remove(selectedButtonClass);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._currentValue = value;
|
||||
|
||||
selected = element.querySelector(`.${selectedButtonClass}`);
|
||||
if (selected) {
|
||||
selected.classList.remove(selectedButtonClass);
|
||||
}
|
||||
}
|
||||
if (document.activeElement === alphaFocusedElement) {
|
||||
var value = alphaFocusedElement.getAttribute('data-value');
|
||||
self.value(value, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (applyValue) {
|
||||
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
|
||||
function onAlphaPickerInKeyboardModeClick(e) {
|
||||
|
||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
var value = alphaPickerButton.getAttribute('data-value');
|
||||
|
||||
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
|
||||
cancelable: false,
|
||||
detail: {
|
||||
value
|
||||
value: value
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return this._currentValue;
|
||||
}
|
||||
|
||||
on(name, fn) {
|
||||
const element = this.options.element;
|
||||
element.addEventListener(name, fn);
|
||||
function onAlphaPickerClick(e) {
|
||||
|
||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
var value = alphaPickerButton.getAttribute('data-value');
|
||||
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
|
||||
self.value(null, true);
|
||||
} else {
|
||||
self.value(value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
off(name, fn) {
|
||||
const element = this.options.element;
|
||||
element.removeEventListener(name, fn);
|
||||
}
|
||||
function onAlphaPickerFocusIn(e) {
|
||||
|
||||
visible(visible) {
|
||||
const element = this.options.element;
|
||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||
}
|
||||
|
||||
values() {
|
||||
const element = this.options.element;
|
||||
const elems = element.querySelectorAll('.alphaPickerButton');
|
||||
const values = [];
|
||||
for (let i = 0, length = elems.length; i < length; i++) {
|
||||
values.push(elems[i].getAttribute('data-value'));
|
||||
if (alphaFocusTimeout) {
|
||||
clearTimeout(alphaFocusTimeout);
|
||||
alphaFocusTimeout = null;
|
||||
}
|
||||
|
||||
return values;
|
||||
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
|
||||
|
||||
if (alphaPickerButton) {
|
||||
alphaFocusedElement = alphaPickerButton;
|
||||
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
|
||||
}
|
||||
}
|
||||
|
||||
focus() {
|
||||
const element = this.options.element;
|
||||
focusManager.autoFocus(element, true);
|
||||
function onItemsFocusIn(e) {
|
||||
|
||||
var item = dom.parentWithClass(e.target, itemClass);
|
||||
|
||||
if (item) {
|
||||
var prefix = item.getAttribute('data-prefix');
|
||||
if (prefix && prefix.length) {
|
||||
|
||||
itemFocusValue = prefix[0];
|
||||
if (itemFocusTimeout) {
|
||||
clearTimeout(itemFocusTimeout);
|
||||
}
|
||||
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
const element = this.options.element;
|
||||
this.enabled(false);
|
||||
element.classList.remove('focuscontainer-x');
|
||||
this.options = null;
|
||||
}
|
||||
self.enabled = function (enabled) {
|
||||
|
||||
if (enabled) {
|
||||
|
||||
if (itemsContainer) {
|
||||
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
|
||||
}
|
||||
|
||||
if (options.mode === 'keyboard') {
|
||||
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||
}
|
||||
|
||||
if (options.valueChangeEvent !== 'click') {
|
||||
element.addEventListener('focus', onAlphaPickerFocusIn, true);
|
||||
} else {
|
||||
element.addEventListener('click', onAlphaPickerClick.bind(this));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (itemsContainer) {
|
||||
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
|
||||
}
|
||||
|
||||
element.removeEventListener('click', onAlphaPickerInKeyboardModeClick);
|
||||
element.removeEventListener('focus', onAlphaPickerFocusIn, true);
|
||||
element.removeEventListener('click', onAlphaPickerClick.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
render(element, options);
|
||||
|
||||
this.enabled(true);
|
||||
this.visible(true);
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
export default AlphaPicker;
|
||||
AlphaPicker.prototype.value = function (value, applyValue) {
|
||||
|
||||
var element = this.options.element;
|
||||
var btn;
|
||||
var selected;
|
||||
|
||||
if (value !== undefined) {
|
||||
if (value != null) {
|
||||
|
||||
value = value.toUpperCase();
|
||||
this._currentValue = value;
|
||||
|
||||
if (this.options.mode !== 'keyboard') {
|
||||
selected = element.querySelector('.' + selectedButtonClass);
|
||||
|
||||
try {
|
||||
btn = element.querySelector('.alphaPickerButton[data-value=\'' + value + '\']');
|
||||
} catch (err) {
|
||||
console.error('error in querySelector: ' + err);
|
||||
}
|
||||
|
||||
if (btn && btn !== selected) {
|
||||
btn.classList.add(selectedButtonClass);
|
||||
}
|
||||
if (selected && selected !== btn) {
|
||||
selected.classList.remove(selectedButtonClass);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._currentValue = value;
|
||||
|
||||
selected = element.querySelector('.' + selectedButtonClass);
|
||||
if (selected) {
|
||||
selected.classList.remove(selectedButtonClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (applyValue) {
|
||||
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
|
||||
cancelable: false,
|
||||
detail: {
|
||||
value: value
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return this._currentValue;
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.on = function (name, fn) {
|
||||
var element = this.options.element;
|
||||
element.addEventListener(name, fn);
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.off = function (name, fn) {
|
||||
var element = this.options.element;
|
||||
element.removeEventListener(name, fn);
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.visible = function (visible) {
|
||||
|
||||
var element = this.options.element;
|
||||
element.style.visibility = visible ? 'visible' : 'hidden';
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.values = function () {
|
||||
|
||||
var element = this.options.element;
|
||||
var elems = element.querySelectorAll('.alphaPickerButton');
|
||||
var values = [];
|
||||
for (var i = 0, length = elems.length; i < length; i++) {
|
||||
|
||||
values.push(elems[i].getAttribute('data-value'));
|
||||
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.focus = function () {
|
||||
|
||||
var element = this.options.element;
|
||||
focusManager.autoFocus(element, true);
|
||||
};
|
||||
|
||||
AlphaPicker.prototype.destroy = function () {
|
||||
|
||||
var element = this.options.element;
|
||||
this.enabled(false);
|
||||
element.classList.remove('focuscontainer-x');
|
||||
this.options = null;
|
||||
};
|
||||
|
||||
return AlphaPicker;
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
.alphaPicker-fixed {
|
||||
position: fixed;
|
||||
bottom: 5.5em;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
.alphaPickerRow {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import './appFooter.css';
|
||||
define(['browser', 'css!./appFooter'], function (browser) {
|
||||
'use strict';
|
||||
|
||||
function render(options) {
|
||||
const elem = document.createElement('div');
|
||||
elem.classList.add('appfooter');
|
||||
function render(options) {
|
||||
var elem = document.createElement('div');
|
||||
elem.classList.add('appfooter');
|
||||
|
||||
document.body.appendChild(elem);
|
||||
document.body.appendChild(elem);
|
||||
|
||||
return elem;
|
||||
}
|
||||
return elem;
|
||||
}
|
||||
|
||||
class appFooter {
|
||||
constructor(options) {
|
||||
const self = this;
|
||||
function appFooter(options) {
|
||||
var self = this;
|
||||
|
||||
self.element = render(options);
|
||||
self.add = function (elem) {
|
||||
@@ -26,11 +26,12 @@ class appFooter {
|
||||
}
|
||||
};
|
||||
}
|
||||
destroy() {
|
||||
const self = this;
|
||||
|
||||
appFooter.prototype.destroy = function () {
|
||||
var self = this;
|
||||
|
||||
self.element = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default new appFooter({});
|
||||
return appFooter;
|
||||
});
|
||||
|
||||
@@ -1,87 +1,98 @@
|
||||
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
|
||||
'use strict';
|
||||
|
||||
import appSettings from '../scripts/settings/appSettings';
|
||||
import browser from '../scripts/browser';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import * as htmlMediaHelper from '../components/htmlMediaHelper';
|
||||
import * as webSettings from '../scripts/settings/webSettings';
|
||||
import globalize from '../scripts/globalize';
|
||||
import profileBuilder from '../scripts/browserDeviceProfile';
|
||||
function getBaseProfileOptions(item) {
|
||||
var disableHlsVideoAudioCodecs = [];
|
||||
|
||||
const appName = 'Jellyfin Web';
|
||||
const appVersion = '10.7.6';
|
||||
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
|
||||
if (browser.edge) {
|
||||
disableHlsVideoAudioCodecs.push('mp3');
|
||||
}
|
||||
|
||||
function getBaseProfileOptions(item) {
|
||||
const disableHlsVideoAudioCodecs = [];
|
||||
|
||||
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
|
||||
if (browser.edge) {
|
||||
disableHlsVideoAudioCodecs.push('mp3');
|
||||
disableHlsVideoAudioCodecs.push('ac3');
|
||||
disableHlsVideoAudioCodecs.push('eac3');
|
||||
disableHlsVideoAudioCodecs.push('opus');
|
||||
}
|
||||
|
||||
disableHlsVideoAudioCodecs.push('ac3');
|
||||
disableHlsVideoAudioCodecs.push('eac3');
|
||||
disableHlsVideoAudioCodecs.push('opus');
|
||||
return {
|
||||
enableMkvProgressive: false,
|
||||
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
enableMkvProgressive: false,
|
||||
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
|
||||
};
|
||||
}
|
||||
function getDeviceProfileForWindowsUwp(item) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) {
|
||||
var profileOptions = getBaseProfileOptions(item);
|
||||
profileOptions.supportsDts = uwpMediaCaps.supportsDTS();
|
||||
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
|
||||
profileOptions.audioChannels = uwpMediaCaps.getAudioChannels();
|
||||
resolve(profileBuilder(profileOptions));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getDeviceProfile(item, options = {}) {
|
||||
return new Promise(function (resolve) {
|
||||
let profile;
|
||||
function getDeviceProfile(item, options) {
|
||||
options = options || {};
|
||||
|
||||
if (window.NativeShell) {
|
||||
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder, appVersion);
|
||||
} else {
|
||||
const builderOpts = getBaseProfileOptions(item);
|
||||
profile = profileBuilder(builderOpts);
|
||||
if (self.Windows) {
|
||||
return getDeviceProfileForWindowsUwp(item);
|
||||
}
|
||||
|
||||
resolve(profile);
|
||||
});
|
||||
}
|
||||
return new Promise(function (resolve) {
|
||||
require(['browserdeviceprofile'], function (profileBuilder) {
|
||||
var profile;
|
||||
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
|
||||
}
|
||||
if (window.NativeShell) {
|
||||
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
|
||||
} else {
|
||||
var builderOpts = getBaseProfileOptions(item);
|
||||
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
|
||||
profile = profileBuilder(builderOpts);
|
||||
}
|
||||
|
||||
function replaceAll(originalString, strReplace, strWith) {
|
||||
const strReplace2 = escapeRegExp(strReplace);
|
||||
const reg = new RegExp(strReplace2, 'ig');
|
||||
return originalString.replace(reg, strWith);
|
||||
}
|
||||
|
||||
function generateDeviceId() {
|
||||
const keys = [];
|
||||
|
||||
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), window.btoa) {
|
||||
const result = replaceAll(btoa(keys.join('|')), '=', '1');
|
||||
return result;
|
||||
resolve(profile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return new Date().getTime();
|
||||
}
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
|
||||
}
|
||||
|
||||
function getDeviceId() {
|
||||
if (!deviceId) {
|
||||
const key = '_deviceId2';
|
||||
function replaceAll(originalString, strReplace, strWith) {
|
||||
var strReplace2 = escapeRegExp(strReplace);
|
||||
var reg = new RegExp(strReplace2, 'ig');
|
||||
return originalString.replace(reg, strWith);
|
||||
}
|
||||
|
||||
deviceId = appSettings.get(key);
|
||||
function generateDeviceId() {
|
||||
var keys = [];
|
||||
|
||||
if (!deviceId) {
|
||||
deviceId = generateDeviceId();
|
||||
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) {
|
||||
var result = replaceAll(btoa(keys.join('|')), '=', '1');
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
return Promise.resolve(new Date().getTime());
|
||||
}
|
||||
|
||||
function getDeviceId() {
|
||||
var key = '_deviceId2';
|
||||
var deviceId = appSettings.get(key);
|
||||
|
||||
if (deviceId) {
|
||||
return Promise.resolve(deviceId);
|
||||
}
|
||||
|
||||
return generateDeviceId().then(function (deviceId) {
|
||||
appSettings.set(key, deviceId);
|
||||
}
|
||||
return deviceId;
|
||||
});
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
function getDeviceName() {
|
||||
if (!deviceName) {
|
||||
function getDeviceName() {
|
||||
var deviceName;
|
||||
if (browser.tizen) {
|
||||
deviceName = 'Samsung Smart TV';
|
||||
} else if (browser.web0s) {
|
||||
@@ -115,299 +126,333 @@ function getDeviceName() {
|
||||
} else if (browser.android) {
|
||||
deviceName += ' Android';
|
||||
}
|
||||
|
||||
return deviceName;
|
||||
}
|
||||
|
||||
return deviceName;
|
||||
}
|
||||
function supportsVoiceInput() {
|
||||
if (!browser.tv) {
|
||||
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
||||
}
|
||||
|
||||
function supportsVoiceInput() {
|
||||
if (!browser.tv) {
|
||||
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function supportsFullscreen() {
|
||||
if (browser.tv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const element = document.documentElement;
|
||||
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
|
||||
}
|
||||
function supportsFullscreen() {
|
||||
if (browser.tv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDefaultLayout() {
|
||||
return 'desktop';
|
||||
}
|
||||
var element = document.documentElement;
|
||||
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
|
||||
}
|
||||
|
||||
function getSyncProfile() {
|
||||
return new Promise(function (resolve) {
|
||||
require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) {
|
||||
var profile;
|
||||
|
||||
if (window.NativeShell) {
|
||||
profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings);
|
||||
} else {
|
||||
profile = profileBuilder();
|
||||
profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate();
|
||||
}
|
||||
|
||||
resolve(profile);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getDefaultLayout() {
|
||||
return 'desktop';
|
||||
}
|
||||
|
||||
function supportsHtmlMediaAutoplay() {
|
||||
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (browser.mobile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function supportsHtmlMediaAutoplay() {
|
||||
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (browser.mobile) {
|
||||
return false;
|
||||
}
|
||||
function supportsCue() {
|
||||
try {
|
||||
var video = document.createElement('video');
|
||||
var style = document.createElement('style');
|
||||
|
||||
return true;
|
||||
}
|
||||
style.textContent = 'video::cue {background: inherit}';
|
||||
document.body.appendChild(style);
|
||||
document.body.appendChild(video);
|
||||
|
||||
function supportsCue() {
|
||||
try {
|
||||
const video = document.createElement('video');
|
||||
const style = document.createElement('style');
|
||||
var cue = window.getComputedStyle(video, '::cue').background;
|
||||
document.body.removeChild(style);
|
||||
document.body.removeChild(video);
|
||||
|
||||
style.textContent = 'video::cue {background: inherit}';
|
||||
document.body.appendChild(style);
|
||||
document.body.appendChild(video);
|
||||
|
||||
const cue = window.getComputedStyle(video, '::cue').background;
|
||||
document.body.removeChild(style);
|
||||
document.body.removeChild(video);
|
||||
|
||||
return !!cue.length;
|
||||
} catch (err) {
|
||||
console.error('error detecting cue support: ' + err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function onAppVisible() {
|
||||
if (isHidden) {
|
||||
isHidden = false;
|
||||
Events.trigger(appHost, 'resume');
|
||||
}
|
||||
}
|
||||
|
||||
function onAppHidden() {
|
||||
if (!isHidden) {
|
||||
isHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
const supportedFeatures = function () {
|
||||
const features = [];
|
||||
|
||||
if (navigator.share) {
|
||||
features.push('sharing');
|
||||
}
|
||||
|
||||
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('filedownload');
|
||||
}
|
||||
|
||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||
features.push('exit');
|
||||
} else {
|
||||
features.push('exitmenu');
|
||||
features.push('plugins');
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
||||
features.push('externallinks');
|
||||
features.push('externalpremium');
|
||||
}
|
||||
|
||||
if (!browser.operaTv) {
|
||||
features.push('externallinkdisplay');
|
||||
}
|
||||
|
||||
if (supportsVoiceInput()) {
|
||||
features.push('voiceinput');
|
||||
}
|
||||
|
||||
if (supportsHtmlMediaAutoplay()) {
|
||||
features.push('htmlaudioautoplay');
|
||||
features.push('htmlvideoautoplay');
|
||||
}
|
||||
|
||||
if (browser.edgeUwp) {
|
||||
features.push('sync');
|
||||
}
|
||||
|
||||
if (supportsFullscreen()) {
|
||||
features.push('fullscreenchange');
|
||||
}
|
||||
|
||||
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
|
||||
features.push('physicalvolumecontrol');
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('remotecontrol');
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
||||
features.push('remotevideo');
|
||||
}
|
||||
|
||||
features.push('displaylanguage');
|
||||
features.push('otherapppromotions');
|
||||
features.push('displaymode');
|
||||
features.push('targetblank');
|
||||
features.push('screensaver');
|
||||
|
||||
webSettings.getMultiServer().then(enabled => {
|
||||
if (enabled) features.push('multiserver');
|
||||
});
|
||||
|
||||
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
features.push('subtitleappearancesettings');
|
||||
}
|
||||
|
||||
if (!browser.orsay) {
|
||||
features.push('subtitleburnsettings');
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
||||
features.push('fileinput');
|
||||
}
|
||||
|
||||
if (browser.chrome || browser.edgeChromium) {
|
||||
features.push('chromecast');
|
||||
}
|
||||
|
||||
return features;
|
||||
}();
|
||||
|
||||
/**
|
||||
* Do exit according to platform
|
||||
*/
|
||||
function doExit() {
|
||||
try {
|
||||
if (window.NativeShell) {
|
||||
window.NativeShell.AppHost.exit();
|
||||
} else if (browser.tizen) {
|
||||
tizen.application.getCurrentApplication().exit();
|
||||
} else if (browser.web0s) {
|
||||
webOS.platformBack();
|
||||
} else {
|
||||
window.close();
|
||||
return !!cue.length;
|
||||
} catch (err) {
|
||||
console.error('error detecting cue support: ' + err);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('error closing application: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
let exitPromise;
|
||||
|
||||
/**
|
||||
* Ask user for exit
|
||||
*/
|
||||
function askForExit() {
|
||||
if (exitPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
import('../components/actionSheet/actionSheet').then((actionsheet) => {
|
||||
exitPromise = actionsheet.show({
|
||||
title: globalize.translate('MessageConfirmAppExit'),
|
||||
items: [
|
||||
{id: 'yes', name: globalize.translate('Yes')},
|
||||
{id: 'no', name: globalize.translate('No')}
|
||||
]
|
||||
}).then(function (value) {
|
||||
if (value === 'yes') {
|
||||
function onAppVisible() {
|
||||
if (isHidden) {
|
||||
isHidden = false;
|
||||
console.debug('triggering app resume event');
|
||||
events.trigger(appHost, 'resume');
|
||||
}
|
||||
}
|
||||
|
||||
function onAppHidden() {
|
||||
if (!isHidden) {
|
||||
isHidden = true;
|
||||
console.debug('app is hidden');
|
||||
}
|
||||
}
|
||||
|
||||
var supportedFeatures = function () {
|
||||
var features = [];
|
||||
|
||||
if (navigator.share) {
|
||||
features.push('sharing');
|
||||
}
|
||||
|
||||
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('filedownload');
|
||||
}
|
||||
|
||||
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
|
||||
features.push('exit');
|
||||
} else {
|
||||
features.push('exitmenu');
|
||||
features.push('plugins');
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
|
||||
features.push('externallinks');
|
||||
features.push('externalpremium');
|
||||
}
|
||||
|
||||
if (!browser.operaTv) {
|
||||
features.push('externallinkdisplay');
|
||||
}
|
||||
|
||||
if (supportsVoiceInput()) {
|
||||
features.push('voiceinput');
|
||||
}
|
||||
|
||||
if (supportsHtmlMediaAutoplay()) {
|
||||
features.push('htmlaudioautoplay');
|
||||
features.push('htmlvideoautoplay');
|
||||
}
|
||||
|
||||
if (browser.edgeUwp) {
|
||||
features.push('sync');
|
||||
}
|
||||
|
||||
if (supportsFullscreen()) {
|
||||
features.push('fullscreenchange');
|
||||
}
|
||||
|
||||
if (browser.chrome || browser.edge && !browser.slow) {
|
||||
if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) {
|
||||
features.push('imageanalysis');
|
||||
}
|
||||
}
|
||||
|
||||
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
|
||||
features.push('physicalvolumecontrol');
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
features.push('remotecontrol');
|
||||
}
|
||||
|
||||
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
|
||||
features.push('remotevideo');
|
||||
}
|
||||
|
||||
features.push('displaylanguage');
|
||||
features.push('otherapppromotions');
|
||||
features.push('displaymode');
|
||||
features.push('targetblank');
|
||||
features.push('screensaver');
|
||||
|
||||
webSettings.enableMultiServer().then(enabled => {
|
||||
if (enabled) features.push('multiserver');
|
||||
});
|
||||
|
||||
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
|
||||
features.push('subtitleappearancesettings');
|
||||
}
|
||||
|
||||
if (!browser.orsay) {
|
||||
features.push('subtitleburnsettings');
|
||||
}
|
||||
|
||||
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
|
||||
features.push('fileinput');
|
||||
}
|
||||
|
||||
if (browser.chrome || browser.edgeChromium) {
|
||||
features.push('chromecast');
|
||||
}
|
||||
|
||||
return features;
|
||||
}();
|
||||
|
||||
/**
|
||||
* Do exit according to platform
|
||||
*/
|
||||
function doExit() {
|
||||
try {
|
||||
if (window.NativeShell) {
|
||||
window.NativeShell.AppHost.exit();
|
||||
} else if (browser.tizen) {
|
||||
tizen.application.getCurrentApplication().exit();
|
||||
} else if (browser.web0s) {
|
||||
webOS.platformBack();
|
||||
} else {
|
||||
window.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('error closing application: ' + err);
|
||||
}
|
||||
}
|
||||
|
||||
var exitPromise;
|
||||
|
||||
/**
|
||||
* Ask user for exit
|
||||
*/
|
||||
function askForExit() {
|
||||
if (exitPromise) {
|
||||
return;
|
||||
}
|
||||
|
||||
require(['actionsheet'], function (actionsheet) {
|
||||
exitPromise = actionsheet.show({
|
||||
title: globalize.translate('MessageConfirmAppExit'),
|
||||
items: [
|
||||
{id: 'yes', name: globalize.translate('Yes')},
|
||||
{id: 'no', name: globalize.translate('No')}
|
||||
]
|
||||
}).then(function (value) {
|
||||
if (value === 'yes') {
|
||||
doExit();
|
||||
}
|
||||
}).finally(function () {
|
||||
exitPromise = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var deviceId;
|
||||
var deviceName;
|
||||
var appName = 'Jellyfin Web';
|
||||
var appVersion = '10.6.4';
|
||||
|
||||
var appHost = {
|
||||
getWindowState: function () {
|
||||
return document.windowState || 'Normal';
|
||||
},
|
||||
setWindowState: function (state) {
|
||||
alert('setWindowState is not supported and should not be called');
|
||||
},
|
||||
exit: function () {
|
||||
if (!!window.appMode && browser.tizen) {
|
||||
askForExit();
|
||||
} else {
|
||||
doExit();
|
||||
}
|
||||
}).finally(function () {
|
||||
exitPromise = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
supports: function (command) {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.supports(command);
|
||||
}
|
||||
|
||||
let deviceId;
|
||||
let deviceName;
|
||||
return -1 !== supportedFeatures.indexOf(command.toLowerCase());
|
||||
},
|
||||
preferVisualCards: browser.android || browser.chrome,
|
||||
getSyncProfile: getSyncProfile,
|
||||
getDefaultLayout: function () {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.getDefaultLayout();
|
||||
}
|
||||
|
||||
export const appHost = {
|
||||
getWindowState: function () {
|
||||
return document.windowState || 'Normal';
|
||||
},
|
||||
setWindowState: function () {
|
||||
alert('setWindowState is not supported and should not be called');
|
||||
},
|
||||
exit: function () {
|
||||
if (!!window.appMode && browser.tizen) {
|
||||
askForExit();
|
||||
return getDefaultLayout();
|
||||
},
|
||||
getDeviceProfile: getDeviceProfile,
|
||||
init: function () {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.init();
|
||||
}
|
||||
|
||||
deviceName = getDeviceName();
|
||||
getDeviceId().then(function (id) {
|
||||
deviceId = id;
|
||||
});
|
||||
},
|
||||
deviceName: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName;
|
||||
},
|
||||
deviceId: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId;
|
||||
},
|
||||
appName: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
||||
},
|
||||
appVersion: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
||||
},
|
||||
getPushTokenInfo: function () {
|
||||
return {};
|
||||
},
|
||||
setThemeColor: function (color) {
|
||||
var metaThemeColor = document.querySelector('meta[name=theme-color]');
|
||||
|
||||
if (metaThemeColor) {
|
||||
metaThemeColor.setAttribute('content', color);
|
||||
}
|
||||
},
|
||||
setUserScalable: function (scalable) {
|
||||
if (!browser.tv) {
|
||||
var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
|
||||
document.querySelector('meta[name=viewport]').setAttribute('content', att);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var isHidden = false;
|
||||
var hidden;
|
||||
var visibilityChange;
|
||||
|
||||
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
|
||||
hidden = 'hidden';
|
||||
visibilityChange = 'visibilitychange';
|
||||
} else if (typeof document.webkitHidden !== 'undefined') {
|
||||
hidden = 'webkitHidden';
|
||||
visibilityChange = 'webkitvisibilitychange';
|
||||
}
|
||||
|
||||
document.addEventListener(visibilityChange, function () {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (document[hidden]) {
|
||||
onAppHidden();
|
||||
} else {
|
||||
doExit();
|
||||
}
|
||||
},
|
||||
supports: function (command) {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.supports(command);
|
||||
onAppVisible();
|
||||
}
|
||||
}, false);
|
||||
|
||||
return supportedFeatures.indexOf(command.toLowerCase()) !== -1;
|
||||
},
|
||||
preferVisualCards: browser.android || browser.chrome,
|
||||
getDefaultLayout: function () {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.getDefaultLayout();
|
||||
}
|
||||
|
||||
return getDefaultLayout();
|
||||
},
|
||||
getDeviceProfile: getDeviceProfile,
|
||||
init: function () {
|
||||
if (window.NativeShell) {
|
||||
return window.NativeShell.AppHost.init();
|
||||
}
|
||||
|
||||
return {
|
||||
deviceId: getDeviceId(),
|
||||
deviceName: getDeviceName()
|
||||
};
|
||||
},
|
||||
deviceName: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
|
||||
},
|
||||
deviceId: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
|
||||
},
|
||||
appName: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
|
||||
},
|
||||
appVersion: function () {
|
||||
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
|
||||
},
|
||||
getPushTokenInfo: function () {
|
||||
return {};
|
||||
},
|
||||
setUserScalable: function (scalable) {
|
||||
if (!browser.tv) {
|
||||
const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
|
||||
document.querySelector('meta[name=viewport]').setAttribute('content', att);
|
||||
}
|
||||
if (self.addEventListener) {
|
||||
self.addEventListener('focus', onAppVisible);
|
||||
self.addEventListener('blur', onAppHidden);
|
||||
}
|
||||
};
|
||||
|
||||
let isHidden = false;
|
||||
let hidden;
|
||||
let visibilityChange;
|
||||
|
||||
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
|
||||
hidden = 'hidden';
|
||||
visibilityChange = 'visibilitychange';
|
||||
} else if (typeof document.webkitHidden !== 'undefined') {
|
||||
hidden = 'webkitHidden';
|
||||
visibilityChange = 'webkitvisibilitychange';
|
||||
}
|
||||
|
||||
document.addEventListener(visibilityChange, function () {
|
||||
/* eslint-disable-next-line compat/compat */
|
||||
if (document[hidden]) {
|
||||
onAppHidden();
|
||||
} else {
|
||||
onAppVisible();
|
||||
}
|
||||
}, false);
|
||||
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('focus', onAppVisible);
|
||||
window.addEventListener('blur', onAppHidden);
|
||||
}
|
||||
|
||||
// load app host on module load
|
||||
appHost.init();
|
||||
return appHost;
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* @module components/autoFocuser
|
||||
*/
|
||||
|
||||
import focusManager from './focusManager';
|
||||
import layoutManager from './layoutManager';
|
||||
import focusManager from 'focusManager';
|
||||
import layoutManager from 'layoutManager';
|
||||
|
||||
/**
|
||||
* Previously selected element.
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import browser from '../../scripts/browser';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import dom from '../../scripts/dom';
|
||||
import * as userSettings from '../../scripts/settings/userSettings';
|
||||
import './backdrop.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
|
||||
/* eslint-disable indent */
|
||||
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) {
|
||||
'use strict';
|
||||
|
||||
function enableAnimation(elem) {
|
||||
if (browser.slow) {
|
||||
@@ -28,70 +22,71 @@ import ServerConnections from '../ServerConnections';
|
||||
return true;
|
||||
}
|
||||
|
||||
class Backdrop {
|
||||
load(url, parent, existingBackdropImage) {
|
||||
const img = new Image();
|
||||
const self = this;
|
||||
|
||||
img.onload = () => {
|
||||
if (self.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const backdropImage = document.createElement('div');
|
||||
backdropImage.classList.add('backdropImage');
|
||||
backdropImage.classList.add('displayingBackdropImage');
|
||||
backdropImage.style.backgroundImage = `url('${url}')`;
|
||||
backdropImage.setAttribute('data-url', url);
|
||||
|
||||
backdropImage.classList.add('backdropImageFadeIn');
|
||||
parent.appendChild(backdropImage);
|
||||
|
||||
if (!enableAnimation(backdropImage)) {
|
||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||
}
|
||||
internalBackdrop(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const onAnimationComplete = () => {
|
||||
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||
once: true
|
||||
});
|
||||
if (backdropImage === self.currentAnimatingElement) {
|
||||
self.currentAnimatingElement = null;
|
||||
}
|
||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||
}
|
||||
};
|
||||
|
||||
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||
once: true
|
||||
});
|
||||
|
||||
internalBackdrop(true);
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
cancelAnimation() {
|
||||
const elem = this.currentAnimatingElement;
|
||||
if (elem) {
|
||||
elem.classList.remove('backdropImageFadeIn');
|
||||
this.currentAnimatingElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.isDestroyed = true;
|
||||
this.cancelAnimation();
|
||||
}
|
||||
function Backdrop() {
|
||||
}
|
||||
|
||||
let backdropContainer;
|
||||
Backdrop.prototype.load = function (url, parent, existingBackdropImage) {
|
||||
var img = new Image();
|
||||
var self = this;
|
||||
|
||||
img.onload = function () {
|
||||
if (self.isDestroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var backdropImage = document.createElement('div');
|
||||
backdropImage.classList.add('backdropImage');
|
||||
backdropImage.classList.add('displayingBackdropImage');
|
||||
backdropImage.style.backgroundImage = "url('" + url + "')";
|
||||
backdropImage.setAttribute('data-url', url);
|
||||
|
||||
backdropImage.classList.add('backdropImageFadeIn');
|
||||
parent.appendChild(backdropImage);
|
||||
|
||||
if (!enableAnimation(backdropImage)) {
|
||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||
}
|
||||
internalBackdrop(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var onAnimationComplete = function () {
|
||||
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||
once: true
|
||||
});
|
||||
if (backdropImage === self.currentAnimatingElement) {
|
||||
self.currentAnimatingElement = null;
|
||||
}
|
||||
if (existingBackdropImage && existingBackdropImage.parentNode) {
|
||||
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
|
||||
}
|
||||
};
|
||||
|
||||
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
|
||||
once: true
|
||||
});
|
||||
|
||||
internalBackdrop(true);
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
};
|
||||
|
||||
Backdrop.prototype.cancelAnimation = function () {
|
||||
var elem = this.currentAnimatingElement;
|
||||
if (elem) {
|
||||
elem.classList.remove('backdropImageFadeIn');
|
||||
this.currentAnimatingElement = null;
|
||||
}
|
||||
};
|
||||
|
||||
Backdrop.prototype.destroy = function () {
|
||||
this.isDestroyed = true;
|
||||
this.cancelAnimation();
|
||||
};
|
||||
|
||||
var backdropContainer;
|
||||
function getBackdropContainer() {
|
||||
if (!backdropContainer) {
|
||||
backdropContainer = document.querySelector('.backdropContainer');
|
||||
@@ -106,7 +101,7 @@ import ServerConnections from '../ServerConnections';
|
||||
return backdropContainer;
|
||||
}
|
||||
|
||||
export function clearBackdrop(clearAll) {
|
||||
function clearBackdrop(clearAll) {
|
||||
clearRotation();
|
||||
|
||||
if (currentLoadingBackdrop) {
|
||||
@@ -114,7 +109,7 @@ import ServerConnections from '../ServerConnections';
|
||||
currentLoadingBackdrop = null;
|
||||
}
|
||||
|
||||
const elem = getBackdropContainer();
|
||||
var elem = getBackdropContainer();
|
||||
elem.innerHTML = '';
|
||||
|
||||
if (clearAll) {
|
||||
@@ -124,7 +119,7 @@ import ServerConnections from '../ServerConnections';
|
||||
internalBackdrop(false);
|
||||
}
|
||||
|
||||
let backgroundContainer;
|
||||
var backgroundContainer;
|
||||
function getBackgroundContainer() {
|
||||
if (!backgroundContainer) {
|
||||
backgroundContainer = document.querySelector('.backgroundContainer');
|
||||
@@ -140,27 +135,31 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
}
|
||||
|
||||
let hasInternalBackdrop;
|
||||
var hasInternalBackdrop;
|
||||
function internalBackdrop(enabled) {
|
||||
hasInternalBackdrop = enabled;
|
||||
setBackgroundContainerBackgroundEnabled();
|
||||
}
|
||||
|
||||
let hasExternalBackdrop;
|
||||
export function externalBackdrop(enabled) {
|
||||
var hasExternalBackdrop;
|
||||
function externalBackdrop(enabled) {
|
||||
hasExternalBackdrop = enabled;
|
||||
setBackgroundContainerBackgroundEnabled();
|
||||
}
|
||||
|
||||
let currentLoadingBackdrop;
|
||||
function getRandom(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
var currentLoadingBackdrop;
|
||||
function setBackdropImage(url) {
|
||||
if (currentLoadingBackdrop) {
|
||||
currentLoadingBackdrop.destroy();
|
||||
currentLoadingBackdrop = null;
|
||||
}
|
||||
|
||||
const elem = getBackdropContainer();
|
||||
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
|
||||
var elem = getBackdropContainer();
|
||||
var existingBackdropImage = elem.querySelector('.displayingBackdropImage');
|
||||
|
||||
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
|
||||
if (existingBackdropImage.getAttribute('data-url') === url) {
|
||||
@@ -169,7 +168,7 @@ import ServerConnections from '../ServerConnections';
|
||||
existingBackdropImage.classList.remove('displayingBackdropImage');
|
||||
}
|
||||
|
||||
const instance = new Backdrop();
|
||||
var instance = new Backdrop();
|
||||
instance.load(url, elem, existingBackdropImage);
|
||||
currentLoadingBackdrop = instance;
|
||||
}
|
||||
@@ -177,9 +176,9 @@ import ServerConnections from '../ServerConnections';
|
||||
function getItemImageUrls(item, imageOptions) {
|
||||
imageOptions = imageOptions || {};
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
var apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
|
||||
return item.BackdropImageTags.map((imgTag, index) => {
|
||||
return item.BackdropImageTags.map(function (imgTag, index) {
|
||||
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
|
||||
type: 'Backdrop',
|
||||
tag: imgTag,
|
||||
@@ -190,7 +189,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
|
||||
return item.ParentBackdropImageTags.map((imgTag, index) => {
|
||||
return item.ParentBackdropImageTags.map(function (imgTag, index) {
|
||||
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
|
||||
type: 'Backdrop',
|
||||
tag: imgTag,
|
||||
@@ -204,13 +203,13 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
function getImageUrls(items, imageOptions) {
|
||||
const list = [];
|
||||
const onImg = img => {
|
||||
var list = [];
|
||||
var onImg = function (img) {
|
||||
list.push(img);
|
||||
};
|
||||
|
||||
for (let i = 0, length = items.length; i < length; i++) {
|
||||
const itemImages = getItemImageUrls(items[i], imageOptions);
|
||||
for (var i = 0, length = items.length; i < length; i++) {
|
||||
var itemImages = getItemImageUrls(items[i], imageOptions);
|
||||
itemImages.forEach(onImg);
|
||||
}
|
||||
|
||||
@@ -230,7 +229,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
// If you don't care about the order of the elements inside
|
||||
// the array, you should sort both arrays here.
|
||||
for (let i = 0; i < a.length; ++i) {
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
@@ -243,12 +242,12 @@ import ServerConnections from '../ServerConnections';
|
||||
return userSettings.enableBackdrops();
|
||||
}
|
||||
|
||||
let rotationInterval;
|
||||
let currentRotatingImages = [];
|
||||
let currentRotationIndex = -1;
|
||||
export function setBackdrops(items, imageOptions, enableImageRotation) {
|
||||
var rotationInterval;
|
||||
var currentRotatingImages = [];
|
||||
var currentRotationIndex = -1;
|
||||
function setBackdrops(items, imageOptions, enableImageRotation) {
|
||||
if (enabled()) {
|
||||
const images = getImageUrls(items, imageOptions);
|
||||
var images = getImageUrls(items, imageOptions);
|
||||
|
||||
if (images.length) {
|
||||
startRotation(images, enableImageRotation);
|
||||
@@ -280,7 +279,7 @@ import ServerConnections from '../ServerConnections';
|
||||
return;
|
||||
}
|
||||
|
||||
let newIndex = currentRotationIndex + 1;
|
||||
var newIndex = currentRotationIndex + 1;
|
||||
if (newIndex >= currentRotatingImages.length) {
|
||||
newIndex = 0;
|
||||
}
|
||||
@@ -290,7 +289,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
function clearRotation() {
|
||||
const interval = rotationInterval;
|
||||
var interval = rotationInterval;
|
||||
if (interval) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
@@ -300,7 +299,7 @@ import ServerConnections from '../ServerConnections';
|
||||
currentRotationIndex = -1;
|
||||
}
|
||||
|
||||
export function setBackdrop(url, imageOptions) {
|
||||
function setBackdrop(url, imageOptions) {
|
||||
if (url && typeof url !== 'string') {
|
||||
url = getImageUrls([url], imageOptions)[0];
|
||||
}
|
||||
@@ -313,11 +312,10 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
setBackdrops: setBackdrops,
|
||||
setBackdrop: setBackdrop,
|
||||
clearBackdrop: clearBackdrop,
|
||||
externalBackdrop: externalBackdrop
|
||||
};
|
||||
return {
|
||||
setBackdrops: setBackdrops,
|
||||
setBackdrop: setBackdrop,
|
||||
clear: clearBackdrop,
|
||||
externalBackdrop: externalBackdrop
|
||||
};
|
||||
});
|
||||
|
||||
@@ -160,6 +160,7 @@ button::-moz-focus-inner {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -168,13 +169,8 @@ button::-moz-focus-inner {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cardContent.cardImageContainer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cardScalable .cardImageContainer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
@@ -213,10 +209,6 @@ button::-moz-focus-inner {
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
.defaultCardBackground {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cardContent:not(.defaultCardBackground) {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -226,8 +218,8 @@ button::-moz-focus-inner {
|
||||
}
|
||||
|
||||
.visualCardBox .cardContent {
|
||||
border-top-left-radius: 0.2em;
|
||||
border-top-right-radius: 0.2em;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.cardContent-shadow,
|
||||
@@ -247,13 +239,33 @@ button::-moz-focus-inner {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cardImage-img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
|
||||
/* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */
|
||||
min-height: 70%;
|
||||
min-width: 70%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.coveredImage-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.coveredImage-noscale-img {
|
||||
max-height: none;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.coveredImage {
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.coveredImage.coveredImage-contain {
|
||||
background-size: contain;
|
||||
.coveredImage-noScale {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.cardFooter {
|
||||
@@ -360,15 +372,18 @@ button::-moz-focus-inner {
|
||||
.cardDefaultText {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cardImageIcon {
|
||||
.cardImageContainer .cardImageIcon {
|
||||
font-size: 5em;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.cardImageIcon-small {
|
||||
font-size: 3em !important;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
.cardIndicators {
|
||||
right: 0.225em;
|
||||
top: 0.225em;
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
* @module components/cardBuilder/cardBuilder
|
||||
*/
|
||||
|
||||
import datetime from '../../scripts/datetime';
|
||||
import imageLoader from '../images/imageLoader';
|
||||
import itemHelper from '../itemHelper';
|
||||
import focusManager from '../focusManager';
|
||||
import indicators from '../indicators/indicators';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import layoutManager from '../layoutManager';
|
||||
import dom from '../../scripts/dom';
|
||||
import browser from '../../scripts/browser';
|
||||
import { playbackManager } from '../playback/playbackmanager';
|
||||
import itemShortcuts from '../shortcuts';
|
||||
import imageHelper from '../../scripts/imagehelper';
|
||||
import './card.css';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../guide/programs.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import datetime from 'datetime';
|
||||
import imageLoader from 'imageLoader';
|
||||
import connectionManager from 'connectionManager';
|
||||
import itemHelper from 'itemHelper';
|
||||
import focusManager from 'focusManager';
|
||||
import indicators from 'indicators';
|
||||
import globalize from 'globalize';
|
||||
import layoutManager from 'layoutManager';
|
||||
import dom from 'dom';
|
||||
import browser from 'browser';
|
||||
import playbackManager from 'playbackManager';
|
||||
import itemShortcuts from 'itemShortcuts';
|
||||
import imageHelper from 'scripts/imagehelper';
|
||||
import 'css!./card';
|
||||
import 'paper-icon-button-light';
|
||||
import 'programStyles';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
@@ -277,7 +277,7 @@ import ServerConnections from '../ServerConnections';
|
||||
*/
|
||||
function getImageWidth(shape, screenWidth, isOrientationLandscape) {
|
||||
const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape);
|
||||
return Math.round(screenWidth / imagesPerRow);
|
||||
return Math.round(screenWidth / imagesPerRow) * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,10 +291,12 @@ import ServerConnections from '../ServerConnections';
|
||||
const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items);
|
||||
|
||||
if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) {
|
||||
|
||||
const requestedShape = options.shape;
|
||||
options.shape = null;
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
|
||||
if (primaryImageAspectRatio >= 3) {
|
||||
options.shape = 'banner';
|
||||
options.coverImage = true;
|
||||
@@ -362,16 +364,16 @@ import ServerConnections from '../ServerConnections';
|
||||
let hasOpenRow;
|
||||
let hasOpenSection;
|
||||
|
||||
const sectionTitleTagName = options.sectionTitleTagName || 'div';
|
||||
let sectionTitleTagName = options.sectionTitleTagName || 'div';
|
||||
let apiClient;
|
||||
let lastServerId;
|
||||
|
||||
for (const [i, item] of items.entries()) {
|
||||
const serverId = item.ServerId || options.serverId;
|
||||
let serverId = item.ServerId || options.serverId;
|
||||
|
||||
if (serverId !== lastServerId) {
|
||||
lastServerId = serverId;
|
||||
apiClient = ServerConnections.getApiClient(lastServerId);
|
||||
apiClient = connectionManager.getApiClient(lastServerId);
|
||||
}
|
||||
|
||||
if (options.indexBy) {
|
||||
@@ -392,6 +394,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (newIndexValue !== currentIndexValue) {
|
||||
|
||||
if (hasOpenRow) {
|
||||
html += '</div>';
|
||||
hasOpenRow = false;
|
||||
@@ -399,6 +402,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (hasOpenSection) {
|
||||
|
||||
html += '</div>';
|
||||
|
||||
if (isVertical) {
|
||||
@@ -422,6 +426,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.rows && itemsInRow === 0) {
|
||||
|
||||
if (hasOpenRow) {
|
||||
html += '</div>';
|
||||
hasOpenRow = false;
|
||||
@@ -498,7 +503,7 @@ import ServerConnections from '../ServerConnections';
|
||||
let imgUrl = null;
|
||||
let imgTag = null;
|
||||
let coverImage = false;
|
||||
const uiAspect = getDesiredAspect(shape);
|
||||
let uiAspect = null;
|
||||
let imgType = null;
|
||||
let itemId = null;
|
||||
|
||||
@@ -543,8 +548,11 @@ import ServerConnections from '../ServerConnections';
|
||||
forceName = true;
|
||||
}
|
||||
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
}
|
||||
} else if (item.SeriesPrimaryImageTag) {
|
||||
imgType = 'Primary';
|
||||
@@ -560,8 +568,11 @@ import ServerConnections from '../ServerConnections';
|
||||
forceName = true;
|
||||
}
|
||||
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
}
|
||||
} else if (item.ParentPrimaryImageTag) {
|
||||
imgType = 'Primary';
|
||||
@@ -573,8 +584,11 @@ import ServerConnections from '../ServerConnections';
|
||||
itemId = item.AlbumId;
|
||||
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
|
||||
|
||||
if (primaryImageAspectRatio && uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
if (primaryImageAspectRatio) {
|
||||
uiAspect = getDesiredAspect(shape);
|
||||
if (uiAspect) {
|
||||
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
|
||||
}
|
||||
}
|
||||
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
|
||||
imgType = 'Thumb';
|
||||
@@ -604,20 +618,15 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (imgTag && imgType) {
|
||||
// TODO: This place is a mess. Could do with a good spring cleaning.
|
||||
if (!height && width && uiAspect) {
|
||||
height = width / uiAspect;
|
||||
}
|
||||
imgUrl = apiClient.getScaledImageUrl(itemId, {
|
||||
type: imgType,
|
||||
fillHeight: height,
|
||||
fillWidth: width,
|
||||
quality: 96,
|
||||
maxHeight: height,
|
||||
maxWidth: width,
|
||||
tag: imgTag
|
||||
});
|
||||
}
|
||||
|
||||
const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
|
||||
let blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
|
||||
|
||||
return {
|
||||
imgUrl: imgUrl,
|
||||
@@ -652,7 +661,7 @@ import ServerConnections from '../ServerConnections';
|
||||
for (let i = 0; i < character.length; i++) {
|
||||
sum += parseInt(character.charAt(i));
|
||||
}
|
||||
const index = String(sum).substr(-1);
|
||||
let index = String(sum).substr(-1);
|
||||
|
||||
return (index % numRandomColors) + 1;
|
||||
} else {
|
||||
@@ -677,8 +686,9 @@ import ServerConnections from '../ServerConnections';
|
||||
let valid = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
|
||||
let currentCssClass = cssClass;
|
||||
const text = lines[i];
|
||||
let text = lines[i];
|
||||
|
||||
if (valid > 0 && isOuterFooter) {
|
||||
currentCssClass += ' cardText-secondary';
|
||||
@@ -703,7 +713,8 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (forceLines) {
|
||||
const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
|
||||
|
||||
let linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
|
||||
|
||||
while (valid < linesLength) {
|
||||
html += "<div class='" + cssClass + "'> </div>";
|
||||
@@ -734,6 +745,7 @@ import ServerConnections from '../ServerConnections';
|
||||
let airTimeText = '';
|
||||
|
||||
if (item.StartDate) {
|
||||
|
||||
try {
|
||||
let date = datetime.parseISO8601Date(item.StartDate);
|
||||
|
||||
@@ -780,6 +792,7 @@ import ServerConnections from '../ServerConnections';
|
||||
const showOtherText = isOuterFooter ? !overlayText : overlayText;
|
||||
|
||||
if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
|
||||
|
||||
if (options.cardFooterAside !== 'none') {
|
||||
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
|
||||
}
|
||||
@@ -794,7 +807,9 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
if (showOtherText) {
|
||||
if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
|
||||
|
||||
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
|
||||
|
||||
if (item.SeriesId) {
|
||||
lines.push(getTextActionButton({
|
||||
Id: item.SeriesId,
|
||||
@@ -807,12 +822,15 @@ import ServerConnections from '../ServerConnections';
|
||||
lines.push(item.SeriesName);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (isUsingLiveTvNaming(item)) {
|
||||
|
||||
lines.push(item.Name);
|
||||
|
||||
if (!item.EpisodeTitle) {
|
||||
titleAdded = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
|
||||
|
||||
@@ -830,6 +848,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (showMediaTitle) {
|
||||
|
||||
const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, {
|
||||
includeParentInfo: options.includeParentInfoInTitle
|
||||
});
|
||||
@@ -846,6 +865,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
if (showOtherText) {
|
||||
if (options.showParentTitle && parentTitleUnderneath) {
|
||||
|
||||
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
|
||||
item.AlbumArtists[0].Type = 'MusicArtist';
|
||||
item.AlbumArtists[0].IsFolder = true;
|
||||
@@ -879,6 +899,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showPremiereDate) {
|
||||
|
||||
if (item.PremiereDate) {
|
||||
try {
|
||||
lines.push(datetime.toLocaleDateString(
|
||||
@@ -887,6 +908,7 @@ import ServerConnections from '../ServerConnections';
|
||||
));
|
||||
} catch (err) {
|
||||
lines.push('');
|
||||
|
||||
}
|
||||
} else {
|
||||
lines.push('');
|
||||
@@ -894,10 +916,14 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showYear || options.showSeriesYear) {
|
||||
|
||||
if (item.Type === 'Series') {
|
||||
if (item.Status === 'Continuing') {
|
||||
|
||||
lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || ''));
|
||||
|
||||
} else {
|
||||
|
||||
if (item.EndDate && item.ProductionYear) {
|
||||
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear();
|
||||
lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
|
||||
@@ -911,7 +937,9 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showRuntime) {
|
||||
|
||||
if (item.RunTimeTicks) {
|
||||
|
||||
lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks));
|
||||
} else {
|
||||
lines.push('');
|
||||
@@ -919,11 +947,14 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showAirTime) {
|
||||
|
||||
lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || '');
|
||||
}
|
||||
|
||||
if (options.showChannelName) {
|
||||
|
||||
if (item.ChannelId) {
|
||||
|
||||
lines.push(getTextActionButton({
|
||||
|
||||
Id: item.ChannelId,
|
||||
@@ -940,6 +971,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showCurrentProgram && item.Type === 'TvChannel') {
|
||||
|
||||
if (item.CurrentProgram) {
|
||||
lines.push(item.CurrentProgram.Name);
|
||||
} else {
|
||||
@@ -948,6 +980,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (options.showCurrentProgramTime && item.Type === 'TvChannel') {
|
||||
|
||||
if (item.CurrentProgram) {
|
||||
lines.push(getAirTimeText(item.CurrentProgram, false, true) || '');
|
||||
} else {
|
||||
@@ -957,6 +990,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
if (options.showSeriesTimerTime) {
|
||||
if (item.RecordAnyTime) {
|
||||
|
||||
lines.push(globalize.translate('Anytime'));
|
||||
} else {
|
||||
lines.push(datetime.getDisplayTime(item.StartDate));
|
||||
@@ -982,10 +1016,6 @@ import ServerConnections from '../ServerConnections';
|
||||
lines = [];
|
||||
}
|
||||
|
||||
if (overlayText && showTitle) {
|
||||
lines = [item.Name];
|
||||
}
|
||||
|
||||
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
|
||||
|
||||
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
|
||||
@@ -995,6 +1025,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (html) {
|
||||
|
||||
if (!isOuterFooter || logoUrl || options.cardLayout) {
|
||||
html = '<div class="' + footerClass + '">' + html;
|
||||
|
||||
@@ -1036,25 +1067,31 @@ import ServerConnections from '../ServerConnections';
|
||||
* @returns {string} HTML markup for the item count indicator.
|
||||
*/
|
||||
function getItemCountsHtml(options, item) {
|
||||
const counts = [];
|
||||
let counts = [];
|
||||
let childText;
|
||||
|
||||
if (item.Type === 'Playlist') {
|
||||
|
||||
childText = '';
|
||||
|
||||
if (item.RunTimeTicks) {
|
||||
|
||||
let minutes = item.RunTimeTicks / 600000000;
|
||||
|
||||
minutes = minutes || 1;
|
||||
|
||||
childText += globalize.translate('ValueMinutes', Math.round(minutes));
|
||||
|
||||
} else {
|
||||
childText += globalize.translate('ValueMinutes', 0);
|
||||
}
|
||||
|
||||
counts.push(childText);
|
||||
|
||||
} else if (item.Type === 'Genre' || item.Type === 'Studio') {
|
||||
|
||||
if (item.MovieCount) {
|
||||
|
||||
childText = item.MovieCount === 1 ?
|
||||
globalize.translate('ValueOneMovie') :
|
||||
globalize.translate('ValueMovieCount', item.MovieCount);
|
||||
@@ -1063,6 +1100,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (item.SeriesCount) {
|
||||
|
||||
childText = item.SeriesCount === 1 ?
|
||||
globalize.translate('ValueOneSeries') :
|
||||
globalize.translate('ValueSeriesCount', item.SeriesCount);
|
||||
@@ -1070,14 +1108,18 @@ import ServerConnections from '../ServerConnections';
|
||||
counts.push(childText);
|
||||
}
|
||||
if (item.EpisodeCount) {
|
||||
|
||||
childText = item.EpisodeCount === 1 ?
|
||||
globalize.translate('ValueOneEpisode') :
|
||||
globalize.translate('ValueEpisodeCount', item.EpisodeCount);
|
||||
|
||||
counts.push(childText);
|
||||
}
|
||||
|
||||
} else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') {
|
||||
|
||||
if (item.AlbumCount) {
|
||||
|
||||
childText = item.AlbumCount === 1 ?
|
||||
globalize.translate('ValueOneAlbum') :
|
||||
globalize.translate('ValueAlbumCount', item.AlbumCount);
|
||||
@@ -1085,6 +1127,7 @@ import ServerConnections from '../ServerConnections';
|
||||
counts.push(childText);
|
||||
}
|
||||
if (item.SongCount) {
|
||||
|
||||
childText = item.SongCount === 1 ?
|
||||
globalize.translate('ValueOneSong') :
|
||||
globalize.translate('ValueSongCount', item.SongCount);
|
||||
@@ -1092,13 +1135,16 @@ import ServerConnections from '../ServerConnections';
|
||||
counts.push(childText);
|
||||
}
|
||||
if (item.MusicVideoCount) {
|
||||
|
||||
childText = item.MusicVideoCount === 1 ?
|
||||
globalize.translate('ValueOneMusicVideo') :
|
||||
globalize.translate('ValueMusicVideoCount', item.MusicVideoCount);
|
||||
|
||||
counts.push(childText);
|
||||
}
|
||||
|
||||
} else if (item.Type === 'Series') {
|
||||
|
||||
childText = item.RecursiveItemCount === 1 ?
|
||||
globalize.translate('ValueOneEpisode') :
|
||||
globalize.translate('ValueEpisodeCount', item.RecursiveItemCount);
|
||||
@@ -1114,11 +1160,10 @@ import ServerConnections from '../ServerConnections';
|
||||
/**
|
||||
* Imports the refresh indicator element.
|
||||
*/
|
||||
function importRefreshIndicator() {
|
||||
function requireRefreshIndicator() {
|
||||
if (!refreshIndicatorLoaded) {
|
||||
refreshIndicatorLoaded = true;
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator');
|
||||
require(['emby-itemrefreshindicator']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1152,11 +1197,13 @@ import ServerConnections from '../ServerConnections';
|
||||
let shape = options.shape;
|
||||
|
||||
if (shape === 'mixed') {
|
||||
|
||||
shape = null;
|
||||
|
||||
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
|
||||
|
||||
if (primaryImageAspectRatio) {
|
||||
|
||||
if (primaryImageAspectRatio >= 1.33) {
|
||||
shape = 'mixedBackdrop';
|
||||
} else if (primaryImageAspectRatio > 0.71) {
|
||||
@@ -1212,8 +1259,8 @@ import ServerConnections from '../ServerConnections';
|
||||
if (coveredImage) {
|
||||
cardImageContainerClass += ' coveredImage';
|
||||
|
||||
if (item.Type === 'TvChannel') {
|
||||
cardImageContainerClass += ' coveredImage-contain';
|
||||
if (item.MediaType === 'Photo' || item.Type === 'PhotoAlbum' || item.Type === 'Folder' || item.ProgramInfo || item.Type === 'Program' || item.Type === 'Recording') {
|
||||
cardImageContainerClass += ' coveredImage-noScale';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1248,6 +1295,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
if (overlayText) {
|
||||
|
||||
logoUrl = null;
|
||||
|
||||
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
|
||||
@@ -1318,7 +1366,7 @@ import ServerConnections from '../ServerConnections';
|
||||
let cardBoxClose = '';
|
||||
let cardScalableClose = '';
|
||||
|
||||
const cardContentClass = 'cardContent';
|
||||
let cardContentClass = 'cardContent';
|
||||
|
||||
let blurhashAttrib = '';
|
||||
if (blurhash && blurhash.length > 0) {
|
||||
@@ -1337,7 +1385,7 @@ import ServerConnections from '../ServerConnections';
|
||||
cardImageContainerClose = '</button>';
|
||||
}
|
||||
|
||||
const cardScalableClass = 'cardScalable';
|
||||
let cardScalableClass = 'cardScalable';
|
||||
|
||||
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
|
||||
cardBoxClose = '</div>';
|
||||
@@ -1356,6 +1404,7 @@ import ServerConnections from '../ServerConnections';
|
||||
indicatorsHtml += indicators.getTypeIndicator(item);
|
||||
|
||||
if (options.showGroupCount) {
|
||||
|
||||
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
|
||||
minCount: 1
|
||||
});
|
||||
@@ -1366,7 +1415,7 @@ import ServerConnections from '../ServerConnections';
|
||||
if (item.Type === 'CollectionFolder' || item.CollectionType) {
|
||||
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
|
||||
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
|
||||
importRefreshIndicator();
|
||||
requireRefreshIndicator();
|
||||
}
|
||||
|
||||
if (indicatorsHtml) {
|
||||
@@ -1414,28 +1463,26 @@ import ServerConnections from '../ServerConnections';
|
||||
const mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : '';
|
||||
const collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : '';
|
||||
const channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : '';
|
||||
const pathData = item.Path ? (' data-path="' + item.Path + '"') : '';
|
||||
const contextData = options.context ? (' data-context="' + options.context + '"') : '';
|
||||
const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : '';
|
||||
const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : '';
|
||||
const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : '';
|
||||
|
||||
let additionalCardContent = '';
|
||||
|
||||
if (layoutManager.desktop && !options.disableHoverMenu) {
|
||||
additionalCardContent += getHoverMenuHtml(item, action);
|
||||
additionalCardContent += getHoverMenuHtml(item, action, options);
|
||||
}
|
||||
|
||||
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
|
||||
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML markup for the card overlay.
|
||||
* @param {object} item - Item used to generate the card overlay.
|
||||
* @param {string} action - Action assigned to the overlay.
|
||||
* @param {Array} options - Card builder options.
|
||||
* @returns {string} HTML markup of the card overlay.
|
||||
*/
|
||||
function getHoverMenuHtml(item, action) {
|
||||
function getHoverMenuHtml(item, action, options) {
|
||||
let html = '';
|
||||
|
||||
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
|
||||
@@ -1451,16 +1498,15 @@ import ServerConnections from '../ServerConnections';
|
||||
const userData = item.UserData || {};
|
||||
|
||||
if (itemHelper.canMarkPlayed(item)) {
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-playstatebutton/emby-playstatebutton');
|
||||
require(['emby-playstatebutton']);
|
||||
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check"></span></button>';
|
||||
}
|
||||
|
||||
if (itemHelper.canRate(item)) {
|
||||
|
||||
const likes = userData.Likes == null ? '' : userData.Likes;
|
||||
|
||||
/* eslint-disable-next-line @babel/no-unused-expressions */
|
||||
import('../../elements/emby-ratingbutton/emby-ratingbutton');
|
||||
require(['emby-ratingbutton']);
|
||||
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite"></span></button>';
|
||||
}
|
||||
|
||||
@@ -1537,6 +1583,7 @@ import ServerConnections from '../ServerConnections';
|
||||
const html = buildCardsHtmlInternal(items, options);
|
||||
|
||||
if (html) {
|
||||
|
||||
if (options.itemsContainer.cardBuilderHtml !== html) {
|
||||
options.itemsContainer.innerHTML = html;
|
||||
|
||||
@@ -1549,6 +1596,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
imageLoader.lazyChildren(options.itemsContainer);
|
||||
} else {
|
||||
|
||||
options.itemsContainer.innerHTML = html;
|
||||
options.itemsContainer.cardBuilderHtml = null;
|
||||
}
|
||||
@@ -1572,6 +1620,7 @@ import ServerConnections from '../ServerConnections';
|
||||
indicatorsElem = card.querySelector('.cardIndicators');
|
||||
|
||||
if (!indicatorsElem) {
|
||||
|
||||
const cardImageContainer = card.querySelector('.cardImageContainer');
|
||||
indicatorsElem = document.createElement('div');
|
||||
indicatorsElem.classList.add('cardIndicators');
|
||||
@@ -1595,9 +1644,11 @@ import ServerConnections from '../ServerConnections';
|
||||
let itemProgressBar = null;
|
||||
|
||||
if (userData.Played) {
|
||||
|
||||
playedIndicator = card.querySelector('.playedIndicator');
|
||||
|
||||
if (!playedIndicator) {
|
||||
|
||||
playedIndicator = document.createElement('div');
|
||||
playedIndicator.classList.add('playedIndicator');
|
||||
playedIndicator.classList.add('indicator');
|
||||
@@ -1606,8 +1657,10 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
|
||||
} else {
|
||||
|
||||
playedIndicator = card.querySelector('.playedIndicator');
|
||||
if (playedIndicator) {
|
||||
|
||||
playedIndicator.parentNode.removeChild(playedIndicator);
|
||||
}
|
||||
}
|
||||
@@ -1615,6 +1668,7 @@ import ServerConnections from '../ServerConnections';
|
||||
countIndicator = card.querySelector('.countIndicator');
|
||||
|
||||
if (!countIndicator) {
|
||||
|
||||
countIndicator = document.createElement('div');
|
||||
countIndicator.classList.add('countIndicator');
|
||||
indicatorsElem = ensureIndicators(card, indicatorsElem);
|
||||
@@ -1622,8 +1676,10 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
countIndicator.innerHTML = userData.UnplayedItemCount;
|
||||
} else if (enableCountIndicator) {
|
||||
|
||||
countIndicator = card.querySelector('.countIndicator');
|
||||
if (countIndicator) {
|
||||
|
||||
countIndicator.parentNode.removeChild(countIndicator);
|
||||
}
|
||||
}
|
||||
@@ -1635,6 +1691,7 @@ import ServerConnections from '../ServerConnections';
|
||||
});
|
||||
|
||||
if (progressHtml) {
|
||||
|
||||
itemProgressBar = card.querySelector('.itemProgressBar');
|
||||
|
||||
if (!itemProgressBar) {
|
||||
@@ -1653,6 +1710,7 @@ import ServerConnections from '../ServerConnections';
|
||||
|
||||
itemProgressBar.innerHTML = progressHtml;
|
||||
} else {
|
||||
|
||||
itemProgressBar = card.querySelector('.itemProgressBar');
|
||||
if (itemProgressBar) {
|
||||
itemProgressBar.parentNode.removeChild(itemProgressBar);
|
||||
@@ -1683,7 +1741,7 @@ import ServerConnections from '../ServerConnections';
|
||||
const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]');
|
||||
|
||||
for (let i = 0, length = cells.length; i < length; i++) {
|
||||
const cell = cells[i];
|
||||
let cell = cells[i];
|
||||
const icon = cell.querySelector('.timerIndicator');
|
||||
if (!icon) {
|
||||
const indicatorsElem = ensureIndicators(cell);
|
||||
@@ -1702,8 +1760,8 @@ import ServerConnections from '../ServerConnections';
|
||||
const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]');
|
||||
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const cell = cells[i];
|
||||
const icon = cell.querySelector('.timerIndicator');
|
||||
let cell = cells[i];
|
||||
let icon = cell.querySelector('.timerIndicator');
|
||||
if (icon) {
|
||||
icon.parentNode.removeChild(icon);
|
||||
}
|
||||
@@ -1720,8 +1778,8 @@ import ServerConnections from '../ServerConnections';
|
||||
const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]');
|
||||
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const cell = cells[i];
|
||||
const icon = cell.querySelector('.timerIndicator');
|
||||
let cell = cells[i];
|
||||
let icon = cell.querySelector('.timerIndicator');
|
||||
if (icon) {
|
||||
icon.parentNode.removeChild(icon);
|
||||
}
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
* @module components/cardBuilder/chaptercardbuilder
|
||||
*/
|
||||
|
||||
import datetime from '../../scripts/datetime';
|
||||
import imageLoader from '../images/imageLoader';
|
||||
import layoutManager from '../layoutManager';
|
||||
import browser from '../../scripts/browser';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import datetime from 'datetime';
|
||||
import imageLoader from 'imageLoader';
|
||||
import connectionManager from 'connectionManager';
|
||||
import layoutManager from 'layoutManager';
|
||||
import browser from 'browser';
|
||||
|
||||
const enableFocusTransform = !browser.slow && !browser.edge;
|
||||
|
||||
function buildChapterCardsHtml(item, chapters, options) {
|
||||
|
||||
// TODO move card creation code to Card component
|
||||
|
||||
let className = 'card itemAction chapterCard';
|
||||
@@ -34,6 +35,7 @@ import ServerConnections from '../ServerConnections';
|
||||
let shape = (options.backdropShape || 'backdrop');
|
||||
|
||||
if (videoStream.Width && videoStream.Height) {
|
||||
|
||||
if ((videoStream.Width / videoStream.Height) <= 1.2) {
|
||||
shape = (options.squareShape || 'square');
|
||||
}
|
||||
@@ -48,9 +50,10 @@ import ServerConnections from '../ServerConnections';
|
||||
let html = '';
|
||||
let itemsInRow = 0;
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(item.ServerId);
|
||||
const apiClient = connectionManager.getApiClient(item.ServerId);
|
||||
|
||||
for (let i = 0, length = chapters.length; i < length; i++) {
|
||||
|
||||
if (options.rows && itemsInRow === 0) {
|
||||
html += '<div class="cardColumn">';
|
||||
}
|
||||
@@ -70,10 +73,12 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) {
|
||||
|
||||
if (ImageTag) {
|
||||
|
||||
return apiClient.getScaledImageUrl(Id, {
|
||||
|
||||
maxWidth: maxWidth,
|
||||
maxWidth: maxWidth * 2,
|
||||
tag: ImageTag,
|
||||
type: 'Chapter',
|
||||
index
|
||||
@@ -84,6 +89,7 @@ import ServerConnections from '../ServerConnections';
|
||||
}
|
||||
|
||||
function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) {
|
||||
|
||||
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
|
||||
|
||||
let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
|
||||
@@ -104,10 +110,13 @@ import ServerConnections from '../ServerConnections';
|
||||
const cardBoxCssClass = 'cardBox';
|
||||
const cardScalableClass = 'cardScalable';
|
||||
|
||||
return `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
|
||||
const html = `<button type="button" class="${className}"${dataAttributes}><div class="${cardBoxCssClass}"><div class="${cardScalableClass}"><div class="cardPadder-${shape}"></div>${cardImageContainer}</div><div class="innerCardFooter">${nameHtml}</div></div></div></button>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
export function buildChapterCards(item, chapters, options) {
|
||||
|
||||
if (options.parentContainer) {
|
||||
// Abort if the container has been disposed
|
||||
if (!document.body.contains(options.parentContainer)) {
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
* @module components/cardBuilder/peoplecardbuilder
|
||||
*/
|
||||
|
||||
import cardBuilder from './cardBuilder';
|
||||
import cardBuilder from 'cardBuilder';
|
||||
|
||||
export function buildPeopleCards(items, options) {
|
||||
|
||||
options = Object.assign(options || {}, {
|
||||
cardLayout: false,
|
||||
centerText: true,
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
class CastSenderApi {
|
||||
load() {
|
||||
if (window.appMode === 'cordova' || window.appMode === 'android') {
|
||||
window.chrome = window.chrome || {};
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
let ccLoaded = false;
|
||||
if (ccLoaded) {
|
||||
define([], function() {
|
||||
'use strict';
|
||||
|
||||
if (window.appMode === 'cordova' || window.appMode === 'android') {
|
||||
return {
|
||||
load: function () {
|
||||
window.chrome = window.chrome || {};
|
||||
return Promise.resolve();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var ccLoaded = false;
|
||||
return {
|
||||
load: function () {
|
||||
if (ccLoaded) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
const fileref = document.createElement('script');
|
||||
fileref.setAttribute('type', 'text/javascript');
|
||||
return new Promise(function (resolve, reject) {
|
||||
var fileref = document.createElement('script');
|
||||
fileref.setAttribute('type', 'text/javascript');
|
||||
|
||||
fileref.onload = function () {
|
||||
ccLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
fileref.onload = function () {
|
||||
ccLoaded = true;
|
||||
resolve();
|
||||
};
|
||||
|
||||
fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js');
|
||||
document.querySelector('head').appendChild(fileref);
|
||||
});
|
||||
}
|
||||
fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js');
|
||||
document.querySelector('head').appendChild(fileref);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default CastSenderApi;
|
||||
});
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import dom from '../../scripts/dom';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import loading from '../loading/loading';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import actionsheet from '../actionSheet/actionSheet';
|
||||
import '../../elements/emby-input/emby-input';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../listview/listview.css';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../formdialog.css';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'actionsheet', 'emby-input', 'paper-icon-button-light', 'emby-button', 'listViewStyle', 'material-icons', 'formDialogStyle'], function (dom, dialogHelper, loading, connectionManager, globalize, actionsheet) {
|
||||
'use strict';
|
||||
|
||||
export default class channelMapper {
|
||||
constructor(options) {
|
||||
return function (options) {
|
||||
function mapChannel(button, channelId, providerChannelId) {
|
||||
loading.show();
|
||||
const providerId = options.providerId;
|
||||
ServerConnections.getApiClient(options.serverId).ajax({
|
||||
var providerId = options.providerId;
|
||||
connectionManager.getApiClient(options.serverId).ajax({
|
||||
type: 'POST',
|
||||
url: ApiClient.getUrl('LiveTv/ChannelMappings'),
|
||||
data: JSON.stringify({
|
||||
data: {
|
||||
providerId: providerId,
|
||||
tunerChannelId: channelId,
|
||||
providerChannelId: providerChannelId
|
||||
}),
|
||||
contentType: 'application/json',
|
||||
},
|
||||
dataType: 'json'
|
||||
}).then(mapping => {
|
||||
const listItem = dom.parentWithClass(button, 'listItem');
|
||||
}).then(function (mapping) {
|
||||
var listItem = dom.parentWithClass(button, 'listItem');
|
||||
button.setAttribute('data-providerid', mapping.ProviderChannelId);
|
||||
listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
|
||||
loading.hide();
|
||||
@@ -35,42 +23,42 @@ export default class channelMapper {
|
||||
}
|
||||
|
||||
function onChannelsElementClick(e) {
|
||||
const btnMap = dom.parentWithClass(e.target, 'btnMap');
|
||||
var btnMap = dom.parentWithClass(e.target, 'btnMap');
|
||||
|
||||
if (btnMap) {
|
||||
const channelId = btnMap.getAttribute('data-id');
|
||||
const providerChannelId = btnMap.getAttribute('data-providerid');
|
||||
const menuItems = currentMappingOptions.ProviderChannels.map(m => {
|
||||
var channelId = btnMap.getAttribute('data-id');
|
||||
var providerChannelId = btnMap.getAttribute('data-providerid');
|
||||
var menuItems = currentMappingOptions.ProviderChannels.map(function (m) {
|
||||
return {
|
||||
name: m.Name,
|
||||
id: m.Id,
|
||||
selected: m.Id.toLowerCase() === providerChannelId.toLowerCase()
|
||||
};
|
||||
}).sort((a, b) => {
|
||||
}).sort(function (a, b) {
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
actionsheet.show({
|
||||
positionTo: btnMap,
|
||||
items: menuItems
|
||||
}).then(newChannelId => {
|
||||
}).then(function (newChannelId) {
|
||||
mapChannel(btnMap, channelId, newChannelId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getChannelMappingOptions(serverId, providerId) {
|
||||
const apiClient = ServerConnections.getApiClient(serverId);
|
||||
var apiClient = connectionManager.getApiClient(serverId);
|
||||
return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', {
|
||||
providerId: providerId
|
||||
}));
|
||||
}
|
||||
|
||||
function getMappingSecondaryName(mapping, providerName) {
|
||||
return `${mapping.ProviderChannelName || ''} - ${providerName}`;
|
||||
return (mapping.ProviderChannelName || '') + ' - ' + providerName;
|
||||
}
|
||||
|
||||
function getTunerChannelHtml(channel, providerName) {
|
||||
let html = '';
|
||||
var html = '';
|
||||
html += '<div class="listItem">';
|
||||
html += '<span class="material-icons listItemIcon dvr"></span>';
|
||||
html += '<div class="listItemBody two-line">';
|
||||
@@ -85,16 +73,16 @@ export default class channelMapper {
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += `<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="${channel.Id}" data-providerid="${channel.ProviderChannelId}"><span class="material-icons mode_edit"></span></button>`;
|
||||
html += '<button class="btnMap autoSize" is="paper-icon-button-light" type="button" data-id="' + channel.Id + '" data-providerid="' + channel.ProviderChannelId + '"><span class="material-icons mode_edit"></span></button>';
|
||||
return html += '</div>';
|
||||
}
|
||||
|
||||
function getEditorHtml() {
|
||||
let html = '';
|
||||
var html = '';
|
||||
html += '<div class="formDialogContent smoothScrollY">';
|
||||
html += '<div class="dialogContentInner dialog-content-centered">';
|
||||
html += '<form style="margin:auto;">';
|
||||
html += `<h1>${globalize.translate('Channels')}</h1>`;
|
||||
html += '<h1>' + globalize.translate('HeaderChannels') + '</h1>';
|
||||
html += '<div class="channels paperList">';
|
||||
html += '</div>';
|
||||
html += '</form>';
|
||||
@@ -103,29 +91,30 @@ export default class channelMapper {
|
||||
}
|
||||
|
||||
function initEditor(dlg, options) {
|
||||
getChannelMappingOptions(options.serverId, options.providerId).then(result => {
|
||||
getChannelMappingOptions(options.serverId, options.providerId).then(function (result) {
|
||||
currentMappingOptions = result;
|
||||
const channelsElement = dlg.querySelector('.channels');
|
||||
channelsElement.innerHTML = result.TunerChannels.map(channel => {
|
||||
var channelsElement = dlg.querySelector('.channels');
|
||||
channelsElement.innerHTML = result.TunerChannels.map(function (channel) {
|
||||
return getTunerChannelHtml(channel, result.ProviderName);
|
||||
}).join('');
|
||||
channelsElement.addEventListener('click', onChannelsElementClick);
|
||||
});
|
||||
}
|
||||
|
||||
let currentMappingOptions;
|
||||
var currentMappingOptions;
|
||||
var self = this;
|
||||
|
||||
this.show = () => {
|
||||
const dialogOptions = {
|
||||
self.show = function () {
|
||||
var dialogOptions = {
|
||||
removeOnClose: true
|
||||
};
|
||||
dialogOptions.size = 'small';
|
||||
const dlg = dialogHelper.createDialog(dialogOptions);
|
||||
var dlg = dialogHelper.createDialog(dialogOptions);
|
||||
dlg.classList.add('formDialog');
|
||||
dlg.classList.add('ui-body-a');
|
||||
dlg.classList.add('background-theme-a');
|
||||
let html = '';
|
||||
const title = globalize.translate('MapChannels');
|
||||
var html = '';
|
||||
var title = globalize.translate('MapChannels');
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
@@ -135,13 +124,13 @@ export default class channelMapper {
|
||||
html += getEditorHtml();
|
||||
dlg.innerHTML = html;
|
||||
initEditor(dlg, options);
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
return new Promise(resolve => {
|
||||
return new Promise(function (resolve, reject) {
|
||||
dlg.addEventListener('close', resolve);
|
||||
dialogHelper.open(dlg);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,32 +1,16 @@
|
||||
import dom from '../../scripts/dom';
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import loading from '../loading/loading';
|
||||
import layoutManager from '../layoutManager';
|
||||
import { appRouter } from '../appRouter';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../../elements/emby-checkbox/emby-checkbox';
|
||||
import '../../elements/emby-input/emby-input';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../formdialog.css';
|
||||
import '../../assets/css/flexstyles.scss';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import toast from '../toast/toast';
|
||||
define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dom, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
let currentServerId;
|
||||
var currentServerId;
|
||||
|
||||
function onSubmit(e) {
|
||||
loading.show();
|
||||
|
||||
const panel = dom.parentWithClass(this, 'dialog');
|
||||
var panel = dom.parentWithClass(this, 'dialog');
|
||||
|
||||
const collectionId = panel.querySelector('#selectCollectionToAddTo').value;
|
||||
var collectionId = panel.querySelector('#selectCollectionToAddTo').value;
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||
var apiClient = connectionManager.getApiClient(currentServerId);
|
||||
|
||||
if (collectionId) {
|
||||
addToCollection(apiClient, panel, collectionId);
|
||||
@@ -39,7 +23,8 @@ import toast from '../toast/toast';
|
||||
}
|
||||
|
||||
function createCollection(apiClient, dlg) {
|
||||
const url = apiClient.getUrl('Collections', {
|
||||
|
||||
var url = apiClient.getUrl('Collections', {
|
||||
|
||||
Name: dlg.querySelector('#txtNewCollectionName').value,
|
||||
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
|
||||
@@ -51,23 +36,27 @@ import toast from '../toast/toast';
|
||||
url: url,
|
||||
dataType: 'json'
|
||||
|
||||
}).then(result => {
|
||||
}).then(function (result) {
|
||||
|
||||
loading.hide();
|
||||
|
||||
const id = result.Id;
|
||||
var id = result.Id;
|
||||
|
||||
dlg.submitted = true;
|
||||
dialogHelper.close(dlg);
|
||||
redirectToCollection(apiClient, id);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function redirectToCollection(apiClient, id) {
|
||||
|
||||
appRouter.showItem(id, apiClient.serverId());
|
||||
}
|
||||
|
||||
function addToCollection(apiClient, dlg, id) {
|
||||
const url = apiClient.getUrl(`Collections/${id}/Items`, {
|
||||
|
||||
var url = apiClient.getUrl('Collections/' + id + '/Items', {
|
||||
|
||||
Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
|
||||
});
|
||||
@@ -76,13 +65,16 @@ import toast from '../toast/toast';
|
||||
type: 'POST',
|
||||
url: url
|
||||
|
||||
}).then(() => {
|
||||
}).then(function () {
|
||||
|
||||
loading.hide();
|
||||
|
||||
dlg.submitted = true;
|
||||
dialogHelper.close(dlg);
|
||||
|
||||
toast(globalize.translate('MessageItemsAdded'));
|
||||
require(['toast'], function (toast) {
|
||||
toast(globalize.translate('MessageItemsAdded'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,13 +83,14 @@ import toast from '../toast/toast';
|
||||
}
|
||||
|
||||
function populateCollections(panel) {
|
||||
|
||||
loading.show();
|
||||
|
||||
const select = panel.querySelector('#selectCollectionToAddTo');
|
||||
var select = panel.querySelector('#selectCollectionToAddTo');
|
||||
|
||||
panel.querySelector('.newCollectionInfo').classList.add('hide');
|
||||
|
||||
const options = {
|
||||
var options = {
|
||||
|
||||
Recursive: true,
|
||||
IncludeItemTypes: 'BoxSet',
|
||||
@@ -105,14 +98,16 @@ import toast from '../toast/toast';
|
||||
EnableTotalRecordCount: false
|
||||
};
|
||||
|
||||
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
|
||||
let html = '';
|
||||
var apiClient = connectionManager.getApiClient(currentServerId);
|
||||
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
|
||||
|
||||
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
|
||||
var html = '';
|
||||
|
||||
html += result.Items.map(i => {
|
||||
return `<option value="${i.Id}">${i.Name}</option>`;
|
||||
html += '<option value="">' + globalize.translate('OptionNew') + '</option>';
|
||||
|
||||
html += result.Items.map(function (i) {
|
||||
|
||||
return '<option value="' + i.Id + '">' + i.Name + '</option>';
|
||||
});
|
||||
|
||||
select.innerHTML = html;
|
||||
@@ -124,7 +119,8 @@ import toast from '../toast/toast';
|
||||
}
|
||||
|
||||
function getEditorHtml() {
|
||||
let html = '';
|
||||
|
||||
var html = '';
|
||||
|
||||
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
||||
html += '<div class="dialogContentInner dialog-content-centered">';
|
||||
@@ -138,27 +134,27 @@ import toast from '../toast/toast';
|
||||
html += '<br/>';
|
||||
html += '<br/>';
|
||||
html += '<div class="selectContainer">';
|
||||
html += `<select is="emby-select" label="${globalize.translate('LabelCollection')}" id="selectCollectionToAddTo" autofocus></select>`;
|
||||
html += '<select is="emby-select" label="' + globalize.translate('LabelCollection') + '" id="selectCollectionToAddTo" autofocus></select>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="newCollectionInfo">';
|
||||
|
||||
html += '<div class="inputContainer">';
|
||||
html += `<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="${globalize.translate('LabelName')}" />`;
|
||||
html += `<div class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
|
||||
html += '<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="' + globalize.translate('LabelName') + '" />';
|
||||
html += '<div class="fieldDescription">' + globalize.translate('NewCollectionNameExample') + '</div>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<label class="checkboxContainer">';
|
||||
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
|
||||
html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
|
||||
html += '<span>' + globalize.translate('SearchForCollectionInternetMetadata') + '</span>';
|
||||
html += '</label>';
|
||||
|
||||
// newCollectionInfo
|
||||
html += '</div>';
|
||||
|
||||
html += '<div class="formDialogFooter">';
|
||||
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('ButtonOk')}</button>`;
|
||||
html += '<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">' + globalize.translate('ButtonOk') + '</button>';
|
||||
html += '</div>';
|
||||
|
||||
html += '<input type="hidden" class="fldSelectedItemIds" />';
|
||||
@@ -171,6 +167,7 @@ import toast from '../toast/toast';
|
||||
}
|
||||
|
||||
function initEditor(content, items) {
|
||||
|
||||
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
|
||||
if (this.value) {
|
||||
content.querySelector('.newCollectionInfo').classList.add('hide');
|
||||
@@ -191,7 +188,7 @@ import toast from '../toast/toast';
|
||||
} else {
|
||||
content.querySelector('.fldSelectCollection').classList.add('hide');
|
||||
|
||||
const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
|
||||
var selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
|
||||
selectCollectionToAddTo.innerHTML = '';
|
||||
selectCollectionToAddTo.value = '';
|
||||
triggerChange(selectCollectionToAddTo);
|
||||
@@ -199,70 +196,79 @@ import toast from '../toast/toast';
|
||||
}
|
||||
|
||||
function centerFocus(elem, horiz, on) {
|
||||
import('../../scripts/scrollHelper').then((scrollHelper) => {
|
||||
const fn = on ? 'on' : 'off';
|
||||
require(['scrollHelper'], function (scrollHelper) {
|
||||
var fn = on ? 'on' : 'off';
|
||||
scrollHelper.centerFocus[fn](elem, horiz);
|
||||
});
|
||||
}
|
||||
|
||||
export class showEditor {
|
||||
constructor(options) {
|
||||
const items = options.items || {};
|
||||
currentServerId = options.serverId;
|
||||
function CollectionEditor() {
|
||||
|
||||
const dialogOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
};
|
||||
|
||||
if (layoutManager.tv) {
|
||||
dialogOptions.size = 'fullscreen';
|
||||
} else {
|
||||
dialogOptions.size = 'small';
|
||||
}
|
||||
|
||||
const dlg = dialogHelper.createDialog(dialogOptions);
|
||||
|
||||
dlg.classList.add('formDialog');
|
||||
|
||||
let html = '';
|
||||
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
||||
html += '</div>';
|
||||
|
||||
html += getEditorHtml();
|
||||
|
||||
dlg.innerHTML = html;
|
||||
|
||||
initEditor(dlg, items);
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
|
||||
}
|
||||
|
||||
return dialogHelper.open(dlg).then(() => {
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
|
||||
}
|
||||
|
||||
if (dlg.submitted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
export default showEditor;
|
||||
CollectionEditor.prototype.show = function (options) {
|
||||
|
||||
var items = options.items || {};
|
||||
currentServerId = options.serverId;
|
||||
|
||||
var dialogOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false
|
||||
};
|
||||
|
||||
if (layoutManager.tv) {
|
||||
dialogOptions.size = 'fullscreen';
|
||||
} else {
|
||||
dialogOptions.size = 'small';
|
||||
}
|
||||
|
||||
var dlg = dialogHelper.createDialog(dialogOptions);
|
||||
|
||||
dlg.classList.add('formDialog');
|
||||
|
||||
var html = '';
|
||||
var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
|
||||
|
||||
html += '<div class="formDialogHeader">';
|
||||
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
|
||||
html += '<h3 class="formDialogHeaderTitle">';
|
||||
html += title;
|
||||
html += '</h3>';
|
||||
|
||||
if (appHost.supports('externallinks')) {
|
||||
html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate('Help') + '"><span class="material-icons info"></span><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
html += getEditorHtml();
|
||||
|
||||
dlg.innerHTML = html;
|
||||
|
||||
initEditor(dlg, items);
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
|
||||
}
|
||||
|
||||
return dialogHelper.open(dlg).then(function () {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
|
||||
}
|
||||
|
||||
if (dlg.submitted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
|
||||
return CollectionEditor;
|
||||
});
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
import browser from '../../scripts/browser';
|
||||
import dialog from '../dialog/dialog';
|
||||
import globalize from '../../scripts/globalize';
|
||||
define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize) {
|
||||
'use strict';
|
||||
|
||||
function replaceAll(str, find, replace) {
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
function nativeConfirm(options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
function replaceAll(str, find, replace) {
|
||||
return str.split(find).join(replace);
|
||||
}
|
||||
|
||||
const text = replaceAll(options.text || '', '<br/>', '\n');
|
||||
const result = window.confirm(text);
|
||||
if (browser.tv && window.confirm) {
|
||||
// Use the native confirm dialog
|
||||
return function (options) {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
title: '',
|
||||
text: options
|
||||
};
|
||||
}
|
||||
|
||||
if (result) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
}
|
||||
var text = replaceAll(options.text || '', '<br/>', '\n');
|
||||
var result = confirm(text);
|
||||
|
||||
function customConfirm(text, title) {
|
||||
let options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
text: text
|
||||
if (result) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
options = text;
|
||||
// Use our own dialog
|
||||
return function (text, title) {
|
||||
var options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
text: text
|
||||
};
|
||||
} else {
|
||||
options = text;
|
||||
}
|
||||
|
||||
var items = [];
|
||||
|
||||
items.push({
|
||||
name: options.cancelText || globalize.translate('ButtonCancel'),
|
||||
id: 'cancel',
|
||||
type: 'cancel'
|
||||
});
|
||||
|
||||
items.push({
|
||||
name: options.confirmText || globalize.translate('ButtonOk'),
|
||||
id: 'ok',
|
||||
type: options.primary === 'delete' ? 'delete' : 'submit'
|
||||
});
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
return dialog(options).then(function (result) {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const items = [];
|
||||
|
||||
items.push({
|
||||
name: options.cancelText || globalize.translate('ButtonCancel'),
|
||||
id: 'cancel',
|
||||
type: 'cancel'
|
||||
});
|
||||
|
||||
items.push({
|
||||
name: options.confirmText || globalize.translate('ButtonOk'),
|
||||
id: 'ok',
|
||||
type: options.primary === 'delete' ? 'delete' : 'submit'
|
||||
});
|
||||
|
||||
options.buttons = items;
|
||||
|
||||
return dialog.show(options).then(result => {
|
||||
if (result === 'ok') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
});
|
||||
}
|
||||
|
||||
const confirm = browser.tv && window.confirm ? nativeConfirm : customConfirm;
|
||||
|
||||
export default confirm;
|
||||
});
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||
import dom from '../../scripts/dom';
|
||||
import layoutManager from '../layoutManager';
|
||||
import scrollHelper from '../../scripts/scrollHelper';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import 'material-design-icons-iconfont';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-button/paper-icon-button-light';
|
||||
import '../../elements/emby-input/emby-input';
|
||||
import '../formdialog.css';
|
||||
import '../../assets/css/flexstyles.scss';
|
||||
import template from './dialog.template.html';
|
||||
define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (dialogHelper, dom, layoutManager, scrollHelper, globalize, require) {
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable indent */
|
||||
function showDialog(options, template) {
|
||||
|
||||
function showDialog(options = { dialogOptions: {}, buttons: [] }) {
|
||||
const dialogOptions = {
|
||||
var dialogOptions = {
|
||||
removeOnClose: true,
|
||||
scrollY: false,
|
||||
...options.dialogOptions
|
||||
scrollY: false
|
||||
};
|
||||
|
||||
const enableTvLayout = layoutManager.tv;
|
||||
var enableTvLayout = layoutManager.tv;
|
||||
|
||||
if (enableTvLayout) {
|
||||
dialogOptions.size = 'fullscreen';
|
||||
}
|
||||
|
||||
const dlg = dialogHelper.createDialog(dialogOptions);
|
||||
var dlg = dialogHelper.createDialog(dialogOptions);
|
||||
|
||||
dlg.classList.add('formDialog');
|
||||
|
||||
@@ -34,7 +22,7 @@ import template from './dialog.template.html';
|
||||
|
||||
dlg.classList.add('align-items-center');
|
||||
dlg.classList.add('justify-content-center');
|
||||
const formDialogContent = dlg.querySelector('.formDialogContent');
|
||||
var formDialogContent = dlg.querySelector('.formDialogContent');
|
||||
formDialogContent.classList.add('no-grow');
|
||||
|
||||
if (enableTvLayout) {
|
||||
@@ -42,36 +30,41 @@ import template from './dialog.template.html';
|
||||
formDialogContent.style['max-height'] = '60%';
|
||||
scrollHelper.centerFocus.on(formDialogContent, false);
|
||||
} else {
|
||||
formDialogContent.style.maxWidth = `${Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)}px`;
|
||||
formDialogContent.style.maxWidth = (Math.min((options.buttons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px';
|
||||
dlg.classList.add('dialog-fullscreen-lowres');
|
||||
}
|
||||
|
||||
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
|
||||
// dialogHelper.close(dlg);
|
||||
//});
|
||||
|
||||
if (options.title) {
|
||||
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
|
||||
} else {
|
||||
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
|
||||
}
|
||||
|
||||
const displayText = options.html || options.text || '';
|
||||
var displayText = options.html || options.text || '';
|
||||
dlg.querySelector('.text').innerHTML = displayText;
|
||||
|
||||
if (!displayText) {
|
||||
dlg.querySelector('.dialogContentInner').classList.add('hide');
|
||||
}
|
||||
|
||||
let i;
|
||||
let length;
|
||||
let html = '';
|
||||
let hasDescriptions = false;
|
||||
var i;
|
||||
var length;
|
||||
var html = '';
|
||||
var hasDescriptions = false;
|
||||
|
||||
for (i = 0, length = options.buttons.length; i < length; i++) {
|
||||
const item = options.buttons[i];
|
||||
const autoFocus = i === 0 ? ' autofocus' : '';
|
||||
|
||||
let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
|
||||
var item = options.buttons[i];
|
||||
var autoFocus = i === 0 ? ' autofocus' : '';
|
||||
|
||||
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
|
||||
|
||||
if (item.type) {
|
||||
buttonClass += ` button-${item.type}`;
|
||||
buttonClass += ' button-' + item.type;
|
||||
}
|
||||
|
||||
if (item.description) {
|
||||
@@ -82,10 +75,10 @@ import template from './dialog.template.html';
|
||||
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom';
|
||||
}
|
||||
|
||||
html += `<button is="emby-button" type="button" class="${buttonClass}" data-id="${item.id}"${autoFocus}>${item.name}</button>`;
|
||||
html += '<button is="emby-button" type="button" class="' + buttonClass + '" data-id="' + item.id + '"' + autoFocus + '>' + item.name + '</button>';
|
||||
|
||||
if (item.description) {
|
||||
html += `<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">${item.description}</div>`;
|
||||
html += '<div class="formDialogFooterItem formDialogFooterItem-autosize fieldDescription" style="margin-top:.25em!important;margin-bottom:1.25em!important;">' + item.description + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,18 +88,19 @@ import template from './dialog.template.html';
|
||||
dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical');
|
||||
}
|
||||
|
||||
let dialogResult;
|
||||
var dialogResult;
|
||||
function onButtonClick() {
|
||||
dialogResult = this.getAttribute('data-id');
|
||||
dialogHelper.close(dlg);
|
||||
}
|
||||
|
||||
const buttons = dlg.querySelectorAll('.btnOption');
|
||||
var buttons = dlg.querySelectorAll('.btnOption');
|
||||
for (i = 0, length = buttons.length; i < length; i++) {
|
||||
buttons[i].addEventListener('click', onButtonClick);
|
||||
}
|
||||
|
||||
return dialogHelper.open(dlg).then(() => {
|
||||
return dialogHelper.open(dlg).then(function () {
|
||||
|
||||
if (enableTvLayout) {
|
||||
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
|
||||
}
|
||||
@@ -119,8 +113,9 @@ import template from './dialog.template.html';
|
||||
});
|
||||
}
|
||||
|
||||
export function show(text, title) {
|
||||
let options;
|
||||
return function (text, title) {
|
||||
|
||||
var options;
|
||||
if (typeof text === 'string') {
|
||||
options = {
|
||||
title: title,
|
||||
@@ -130,10 +125,10 @@ import template from './dialog.template.html';
|
||||
options = text;
|
||||
}
|
||||
|
||||
return showDialog(options);
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
export default {
|
||||
show: show
|
||||
};
|
||||
return new Promise(function (resolve, reject) {
|
||||
require(['text!./dialog.template.html'], function (template) {
|
||||
showDialog(options, template).then(resolve, reject);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
import { appRouter } from '../appRouter';
|
||||
import focusManager from '../focusManager';
|
||||
import browser from '../../scripts/browser';
|
||||
import layoutManager from '../layoutManager';
|
||||
import inputManager from '../../scripts/inputManager';
|
||||
import dom from '../../scripts/dom';
|
||||
import './dialoghelper.css';
|
||||
import '../../assets/css/scrollstyles.css';
|
||||
define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 'dom', 'css!./dialoghelper.css', 'scrollStyles'], function (appRouter, focusManager, browser, layoutManager, inputManager, dom) {
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable indent */
|
||||
|
||||
let globalOnOpenCallback;
|
||||
var globalOnOpenCallback;
|
||||
|
||||
function enableAnimation() {
|
||||
|
||||
// too slow
|
||||
if (browser.tv) {
|
||||
return false;
|
||||
@@ -21,6 +14,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function removeCenterFocus(dlg) {
|
||||
|
||||
if (layoutManager.tv) {
|
||||
if (dlg.classList.contains('scrollX')) {
|
||||
centerFocus(dlg, true, false);
|
||||
@@ -31,8 +25,9 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function tryRemoveElement(elem) {
|
||||
const parentNode = elem.parentNode;
|
||||
var parentNode = elem.parentNode;
|
||||
if (parentNode) {
|
||||
|
||||
// Seeing crashes in edge webview
|
||||
try {
|
||||
parentNode.removeChild(elem);
|
||||
@@ -43,13 +38,15 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function DialogHashHandler(dlg, hash, resolve) {
|
||||
const self = this;
|
||||
|
||||
var self = this;
|
||||
self.originalUrl = window.location.href;
|
||||
const activeElement = document.activeElement;
|
||||
let removeScrollLockOnClose = false;
|
||||
var activeElement = document.activeElement;
|
||||
var removeScrollLockOnClose = false;
|
||||
|
||||
function onHashChange(e) {
|
||||
const isBack = self.originalUrl === window.location.href;
|
||||
|
||||
var isBack = self.originalUrl === window.location.href;
|
||||
|
||||
if (isBack || !isOpened(dlg)) {
|
||||
window.removeEventListener('popstate', onHashChange);
|
||||
@@ -62,6 +59,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function onBackCommand(e) {
|
||||
|
||||
if (e.detail.command === 'back') {
|
||||
self.closedByBack = true;
|
||||
e.preventDefault();
|
||||
@@ -71,6 +69,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function onDialogClosed() {
|
||||
|
||||
if (!isHistoryEnabled(dlg)) {
|
||||
inputManager.off(dlg, onBackCommand);
|
||||
}
|
||||
@@ -85,9 +84,9 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
if (!self.closedByBack && isHistoryEnabled(dlg)) {
|
||||
const state = window.history.state || {};
|
||||
var state = history.state || {};
|
||||
if (state.dialogId === hash) {
|
||||
window.history.back();
|
||||
history.back();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +97,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
if (dlg.getAttribute('data-removeonclose') !== 'false') {
|
||||
removeCenterFocus(dlg);
|
||||
|
||||
const dialogContainer = dlg.dialogContainer;
|
||||
var dialogContainer = dlg.dialogContainer;
|
||||
if (dialogContainer) {
|
||||
tryRemoveElement(dialogContainer);
|
||||
dlg.dialogContainer = null;
|
||||
@@ -109,7 +108,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
|
||||
//resolve();
|
||||
// if we just called history.back(), then use a timeout to allow the history events to fire first
|
||||
setTimeout(() => {
|
||||
setTimeout(function () {
|
||||
resolve({
|
||||
element: dlg,
|
||||
closedByBack: self.closedByBack
|
||||
@@ -119,7 +118,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
|
||||
dlg.addEventListener('close', onDialogClosed);
|
||||
|
||||
const center = !dlg.classList.contains('dialog-fixedSize');
|
||||
var center = !dlg.classList.contains('dialog-fixedSize');
|
||||
if (center) {
|
||||
dlg.classList.add('centeredDialog');
|
||||
}
|
||||
@@ -142,7 +141,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
animateDialogOpen(dlg);
|
||||
|
||||
if (isHistoryEnabled(dlg)) {
|
||||
appRouter.pushState({ dialogId: hash }, 'Dialog', `#${hash}`);
|
||||
appRouter.pushState({ dialogId: hash }, 'Dialog', '#' + hash);
|
||||
|
||||
window.addEventListener('popstate', onHashChange);
|
||||
} else {
|
||||
@@ -151,10 +150,11 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function addBackdropOverlay(dlg) {
|
||||
const backdrop = document.createElement('div');
|
||||
|
||||
var backdrop = document.createElement('div');
|
||||
backdrop.classList.add('dialogBackdrop');
|
||||
|
||||
const backdropParent = dlg.dialogContainer || dlg;
|
||||
var backdropParent = dlg.dialogContainer || dlg;
|
||||
backdropParent.parentNode.insertBefore(backdrop, backdropParent);
|
||||
dlg.backdrop = backdrop;
|
||||
|
||||
@@ -162,7 +162,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
void backdrop.offsetWidth;
|
||||
backdrop.classList.add('dialogBackdropOpened');
|
||||
|
||||
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => {
|
||||
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', function (e) {
|
||||
if (e.target === dlg.dialogContainer) {
|
||||
close(dlg);
|
||||
}
|
||||
@@ -170,7 +170,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
passive: true
|
||||
});
|
||||
|
||||
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => {
|
||||
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
|
||||
if (e.target === dlg.dialogContainer) {
|
||||
// Close the application dialog menu
|
||||
close(dlg);
|
||||
@@ -184,36 +184,40 @@ import '../../assets/css/scrollstyles.css';
|
||||
return dlg.getAttribute('data-history') === 'true';
|
||||
}
|
||||
|
||||
export function open(dlg) {
|
||||
function open(dlg) {
|
||||
|
||||
if (globalOnOpenCallback) {
|
||||
globalOnOpenCallback(dlg);
|
||||
}
|
||||
|
||||
const parent = dlg.parentNode;
|
||||
var parent = dlg.parentNode;
|
||||
if (parent) {
|
||||
parent.removeChild(dlg);
|
||||
}
|
||||
|
||||
const dialogContainer = document.createElement('div');
|
||||
var dialogContainer = document.createElement('div');
|
||||
dialogContainer.classList.add('dialogContainer');
|
||||
dialogContainer.appendChild(dlg);
|
||||
dlg.dialogContainer = dialogContainer;
|
||||
document.body.appendChild(dialogContainer);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
|
||||
return new Promise(function (resolve, reject) {
|
||||
|
||||
new DialogHashHandler(dlg, 'dlg' + new Date().getTime(), resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function isOpened(dlg) {
|
||||
|
||||
//return dlg.opened;
|
||||
return !dlg.classList.contains('hide');
|
||||
}
|
||||
|
||||
export function close(dlg) {
|
||||
function close(dlg) {
|
||||
|
||||
if (isOpened(dlg)) {
|
||||
if (isHistoryEnabled(dlg)) {
|
||||
window.history.back();
|
||||
history.back();
|
||||
} else {
|
||||
closeDialog(dlg);
|
||||
}
|
||||
@@ -221,13 +225,15 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function closeDialog(dlg) {
|
||||
|
||||
if (!dlg.classList.contains('hide')) {
|
||||
|
||||
dlg.dispatchEvent(new CustomEvent('closing', {
|
||||
bubbles: false,
|
||||
cancelable: false
|
||||
}));
|
||||
|
||||
const onAnimationFinish = () => {
|
||||
var onAnimationFinish = function () {
|
||||
focusManager.popScope(dlg);
|
||||
|
||||
dlg.classList.add('hide');
|
||||
@@ -242,7 +248,8 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function animateDialogOpen(dlg) {
|
||||
const onAnimationFinish = () => {
|
||||
|
||||
var onAnimationFinish = function () {
|
||||
focusManager.pushScope(dlg);
|
||||
|
||||
if (dlg.getAttribute('data-autofocus') === 'true') {
|
||||
@@ -256,7 +263,8 @@ import '../../assets/css/scrollstyles.css';
|
||||
};
|
||||
|
||||
if (enableAnimation()) {
|
||||
const onFinish = () => {
|
||||
|
||||
var onFinish = function () {
|
||||
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
|
||||
once: true
|
||||
});
|
||||
@@ -272,24 +280,27 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function animateDialogClose(dlg, onAnimationFinish) {
|
||||
|
||||
if (enableAnimation()) {
|
||||
let animated = true;
|
||||
|
||||
var animated = true;
|
||||
|
||||
switch (dlg.animationConfig.exit.name) {
|
||||
|
||||
case 'fadeout':
|
||||
dlg.style.animation = `fadeout ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
|
||||
dlg.style.animation = 'fadeout ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
|
||||
break;
|
||||
case 'scaledown':
|
||||
dlg.style.animation = `scaledown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
|
||||
dlg.style.animation = 'scaledown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
|
||||
break;
|
||||
case 'slidedown':
|
||||
dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
|
||||
dlg.style.animation = 'slidedown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
|
||||
break;
|
||||
default:
|
||||
animated = false;
|
||||
break;
|
||||
}
|
||||
const onFinish = () => {
|
||||
var onFinish = function () {
|
||||
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
|
||||
once: true
|
||||
});
|
||||
@@ -307,9 +318,14 @@ import '../../assets/css/scrollstyles.css';
|
||||
onAnimationFinish();
|
||||
}
|
||||
|
||||
const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
|
||||
var supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
|
||||
|
||||
function shouldLockDocumentScroll(options) {
|
||||
|
||||
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.lockScroll != null) {
|
||||
return options.lockScroll;
|
||||
}
|
||||
@@ -318,10 +334,6 @@ import '../../assets/css/scrollstyles.css';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.size) {
|
||||
return true;
|
||||
}
|
||||
@@ -330,7 +342,8 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function removeBackdrop(dlg) {
|
||||
const backdrop = dlg.backdrop;
|
||||
|
||||
var backdrop = dlg.backdrop;
|
||||
|
||||
if (!backdrop) {
|
||||
return;
|
||||
@@ -338,11 +351,12 @@ import '../../assets/css/scrollstyles.css';
|
||||
|
||||
dlg.backdrop = null;
|
||||
|
||||
const onAnimationFinish = () => {
|
||||
var onAnimationFinish = function () {
|
||||
tryRemoveElement(backdrop);
|
||||
};
|
||||
|
||||
if (enableAnimation()) {
|
||||
|
||||
backdrop.classList.remove('dialogBackdropOpened');
|
||||
|
||||
// this is not firing animatonend
|
||||
@@ -354,22 +368,20 @@ import '../../assets/css/scrollstyles.css';
|
||||
}
|
||||
|
||||
function centerFocus(elem, horiz, on) {
|
||||
import('../../scripts/scrollHelper').then((scrollHelper) => {
|
||||
const fn = on ? 'on' : 'off';
|
||||
require(['scrollHelper'], function (scrollHelper) {
|
||||
var fn = on ? 'on' : 'off';
|
||||
scrollHelper.centerFocus[fn](elem, horiz);
|
||||
});
|
||||
}
|
||||
|
||||
export function createDialog(options = {}) {
|
||||
function createDialog(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
// If there's no native dialog support, use a plain div
|
||||
// Also not working well in samsung tizen browser, content inside not clickable
|
||||
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
|
||||
const dlg = document.createElement('div');
|
||||
|
||||
// Add an id so we can access the dialog element
|
||||
if (options.id) {
|
||||
dlg.id = options.id;
|
||||
}
|
||||
var dlg = document.createElement('div');
|
||||
|
||||
dlg.classList.add('focuscontainer');
|
||||
dlg.classList.add('hide');
|
||||
@@ -378,7 +390,7 @@ import '../../assets/css/scrollstyles.css';
|
||||
dlg.setAttribute('data-lockscroll', 'true');
|
||||
}
|
||||
|
||||
if (options.enableHistory === true) {
|
||||
if (options.enableHistory !== false && appRouter.enableNativeHistory()) {
|
||||
dlg.setAttribute('data-history', 'true');
|
||||
}
|
||||
|
||||
@@ -394,14 +406,17 @@ import '../../assets/css/scrollstyles.css';
|
||||
dlg.setAttribute('data-autofocus', 'true');
|
||||
}
|
||||
|
||||
const defaultEntryAnimation = 'scaleup';
|
||||
const defaultExitAnimation = 'scaledown';
|
||||
const entryAnimation = options.entryAnimation || defaultEntryAnimation;
|
||||
const exitAnimation = options.exitAnimation || defaultExitAnimation;
|
||||
var defaultEntryAnimation;
|
||||
var defaultExitAnimation;
|
||||
|
||||
defaultEntryAnimation = 'scaleup';
|
||||
defaultExitAnimation = 'scaledown';
|
||||
var entryAnimation = options.entryAnimation || defaultEntryAnimation;
|
||||
var exitAnimation = options.exitAnimation || defaultExitAnimation;
|
||||
|
||||
// If it's not fullscreen then lower the default animation speed to make it open really fast
|
||||
const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
|
||||
const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
|
||||
var entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
|
||||
var exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
|
||||
|
||||
dlg.animationConfig = {
|
||||
// scale up
|
||||
@@ -446,22 +461,24 @@ import '../../assets/css/scrollstyles.css';
|
||||
|
||||
if (options.size) {
|
||||
dlg.classList.add('dialog-fixedSize');
|
||||
dlg.classList.add(`dialog-${options.size}`);
|
||||
dlg.classList.add('dialog-' + options.size);
|
||||
}
|
||||
|
||||
if (enableAnimation()) {
|
||||
|
||||
switch (dlg.animationConfig.entry.name) {
|
||||
|
||||
case 'fadein':
|
||||
dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`;
|
||||
dlg.style.animation = 'fadein ' + entryAnimationDuration + 'ms ease-out normal';
|
||||
break;
|
||||
case 'scaleup':
|
||||
dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`;
|
||||
dlg.style.animation = 'scaleup ' + entryAnimationDuration + 'ms ease-out normal both';
|
||||
break;
|
||||
case 'slideup':
|
||||
dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`;
|
||||
dlg.style.animation = 'slideup ' + entryAnimationDuration + 'ms ease-out normal';
|
||||
break;
|
||||
case 'slidedown':
|
||||
dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`;
|
||||
dlg.style.animation = 'slidedown ' + entryAnimationDuration + 'ms ease-out normal';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -471,15 +488,12 @@ import '../../assets/css/scrollstyles.css';
|
||||
return dlg;
|
||||
}
|
||||
|
||||
export function setOnOpen(val) {
|
||||
globalOnOpenCallback = val;
|
||||
}
|
||||
|
||||
/* eslint-enable indent */
|
||||
|
||||
export default {
|
||||
open: open,
|
||||
close: close,
|
||||
createDialog: createDialog,
|
||||
setOnOpen: setOnOpen
|
||||
};
|
||||
return {
|
||||
open: open,
|
||||
close: close,
|
||||
createDialog: createDialog,
|
||||
setOnOpen: function (val) {
|
||||
globalOnOpenCallback = val;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||