Merge branch 'master' into 'convert-to-csss'

This commit is contained in:
Guilherme Danno 2020-11-01 22:59:47 -03:00
commit b38aec0526
606 changed files with 57780 additions and 51916 deletions

View file

@ -0,0 +1,63 @@
jobs:
- job: Build
displayName: 'Build'
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
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 build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
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'
- script: 'mv dist jellyfin-web'
displayName: 'Rename Directory'
- task: ArchiveFiles@2
displayName: 'Archive Directory'
inputs:
rootFolderOrFile: 'jellyfin-web'
includeRootFolder: true
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
artifactName: 'jellyfin-web-$(BuildConfiguration)'

View 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'

View file

@ -0,0 +1,122 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS:
BuildConfiguration: centos
Debian:
BuildConfiguration: debian
Fedora:
BuildConfiguration: fedora
Portable:
BuildConfiguration: portable
pool:
vmImage: 'ubuntu-latest'
steps:
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-web-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-web-$(BuildConfiguration)'
displayName: 'Run Dockerfile (stable)'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-web-$(BuildConfiguration)'
- task: SSH@0
displayName: 'Create target directory on repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
contents: '**'
targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- job: BuildDocker
displayName: 'Build Docker'
pool:
vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: 0.0.0
steps:
- script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
- task: Docker@2
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
repository: 'jellyfin/jellyfin-web'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker'
containerRegistry: Docker Hub
tags: |
unstable-$(Build.BuildNumber)
unstable
- task: Docker@2
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
repository: 'jellyfin/jellyfin-web'
command: buildAndPush
buildContext: '.'
Dockerfile: 'deployment/Dockerfile.docker'
containerRegistry: Docker Hub
tags: |
stable-$(Build.BuildNumber)
$(JellyfinVersion)
- job: CollectArtifacts
displayName: 'Collect Artifacts'
dependsOn:
- BuildPackage
- BuildDocker
condition: and(succeeded('BuildPackage'), succeeded('BuildDocker'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: SSH@0
displayName: 'Update Unstable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable'
- task: SSH@0
displayName: 'Update Stable Repository'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags')
inputs:
sshEndpoint: repository
runOptions: 'inline'
inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'

View file

@ -12,94 +12,6 @@ pr:
- '*' - '*'
jobs: jobs:
- job: Build - template: azure-pipelines-build.yml
displayName: 'Build' - template: azure-pipelines-lint.yml
- template: azure-pipelines-package.yml
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
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 build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
displayName: 'Build Bundle'
condition: eq(variables['BuildConfiguration'], 'production')
- script: 'yarn build:standalone'
displayName: 'Build Standalone'
condition: eq(variables['BuildConfiguration'], 'standalone')
- script: 'test -d dist'
displayName: 'Check Build'
- script: 'mv dist jellyfin-web'
displayName: 'Rename Directory'
- task: ArchiveFiles@2
displayName: 'Archive Directory'
inputs:
rootFolderOrFile: 'jellyfin-web'
includeRootFolder: true
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
artifactName: 'jellyfin-web-$(BuildConfiguration)'
- 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'

View file

@ -8,5 +8,5 @@ trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
end_of_line = lf end_of_line = lf
[json] [*.json]
indent_size = 2 indent_size = 2

View file

@ -2,4 +2,3 @@ node_modules
dist dist
.idea .idea
.vscode .vscode
src/libraries

View file

@ -1,6 +1,9 @@
const restrictedGlobals = require('confusing-browser-globals');
module.exports = { module.exports = {
root: true, root: true,
plugins: [ plugins: [
'@babel',
'promise', 'promise',
'import', 'import',
'eslint-comments' 'eslint-comments'
@ -27,28 +30,36 @@ module.exports = {
'plugin:compat/recommended' 'plugin:compat/recommended'
], ],
rules: { rules: {
'block-spacing': ["error"], 'block-spacing': ['error'],
'brace-style': ["error"], 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }],
'comma-dangle': ["error", "never"], 'comma-dangle': ['error', 'never'],
'comma-spacing': ["error"], 'comma-spacing': ['error'],
'eol-last': ["error"], 'eol-last': ['error'],
'indent': ["error", 4, { "SwitchCase": 1 }], 'indent': ['error', 4, { 'SwitchCase': 1 }],
'keyword-spacing': ["error"], 'keyword-spacing': ['error'],
'max-statements-per-line': ["error"], 'max-statements-per-line': ['error'],
'no-floating-decimal': ["error"], 'no-floating-decimal': ['error'],
'no-multi-spaces': ["error"], 'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ["error", { "max": 1 }], 'no-multiple-empty-lines': ['error', { 'max': 1 }],
'no-trailing-spaces': ["error"], 'no-restricted-globals': ['error'].concat(restrictedGlobals),
'one-var': ["error", "never"], 'no-trailing-spaces': ['error'],
'quotes': ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": false }], '@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }],
'semi': ["error"], 'one-var': ['error', 'never'],
'space-before-blocks': ["error"] 'padded-blocks': ['error', 'never'],
'prefer-const': ['error', {'destructuring': 'all'}],
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
'@babel/semi': ['error'],
'no-var': ['error'],
'space-before-blocks': ['error'],
'space-infix-ops': 'error',
'yoda': 'error'
}, },
overrides: [ overrides: [
{ {
files: [ files: [
'./src/**/*.js' './src/**/*.js'
], ],
parser: '@babel/eslint-parser',
env: { env: {
node: false, node: false,
amd: true, amd: true,
@ -73,7 +84,6 @@ module.exports = {
'ApiClient': 'writable', 'ApiClient': 'writable',
'AppInfo': 'writable', 'AppInfo': 'writable',
'chrome': 'writable', 'chrome': 'writable',
'ConnectionManager': 'writable',
'DlnaProfilePage': 'writable', 'DlnaProfilePage': 'writable',
'Dashboard': 'writable', 'Dashboard': 'writable',
'DashboardPage': 'writable', 'DashboardPage': 'writable',
@ -96,11 +106,11 @@ module.exports = {
}, },
rules: { rules: {
// TODO: Fix warnings and remove these rules // TODO: Fix warnings and remove these rules
'no-redeclare': ["warn"], 'no-redeclare': ['off'],
'no-unused-vars': ["warn"], 'no-useless-escape': ['off'],
'no-useless-escape': ["warn"], 'no-unused-vars': ['off'],
// TODO: Remove after ES6 migration is complete // TODO: Remove after ES6 migration is complete
'import/no-unresolved': ["off"] 'import/no-unresolved': ['off']
}, },
settings: { settings: {
polyfills: [ polyfills: [
@ -130,6 +140,7 @@ module.exports = {
'Object.getOwnPropertyDescriptor', 'Object.getOwnPropertyDescriptor',
'Object.getPrototypeOf', 'Object.getPrototypeOf',
'Object.keys', 'Object.keys',
'Object.entries',
'Object.getOwnPropertyNames', 'Object.getOwnPropertyNames',
'Function.name', 'Function.name',
'Function.hasInstance', 'Function.hasInstance',
@ -190,4 +201,4 @@ module.exports = {
} }
} }
] ]
} };

4
.github/CODEOWNERS vendored
View file

@ -1,4 +1,6 @@
.ci @dkanada @EraYaN .ci @dkanada @EraYaN
.github @jellyfin/core .github @jellyfin/core
build.sh @joshuaboniface fedora @joshuaboniface
debian @joshuaboniface
.copr @joshuaboniface
deployment @joshuaboniface deployment @joshuaboniface

View file

@ -1,23 +1,20 @@
--- ---
name: Bug report name: Bug Report
about: Create a bug report about: You have noticed a general issue or regression, and would like to report it
title: ''
labels: bug labels: bug
assignees: ''
--- ---
**Describe the bug** **Describe The Bug**
<!-- A clear and concise description of what the bug is. --> <!-- A clear and concise description of what the bug is. -->
**To Reproduce** **Steps To Reproduce**
<!-- Steps to reproduce the behavior: --> <!-- Steps to reproduce the behavior: -->
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
4. See error 4. See error
**Expected behavior** **Expected Behavior**
<!-- A clear and concise description of what you expected to happen. --> <!-- A clear and concise description of what you expected to happen. -->
**Logs** **Logs**
@ -27,9 +24,9 @@ assignees: ''
<!-- If applicable, add screenshots to help explain your problem. --> <!-- If applicable, add screenshots to help explain your problem. -->
**System (please complete the following information):** **System (please complete the following information):**
- OS: [e.g. Docker, Debian, Windows] - Platform: [e.g. Linux, Windows, iPhone, Tizen]
- Browser: [e.g. Firefox, Chrome, Safari] - Browser: [e.g. Firefox, Chrome, Safari]
- Jellyfin Version: [e.g. 10.0.1] - Jellyfin Version: [e.g. 10.6.0]
**Additional context** **Additional Context**
<!-- Add any other context about the problem here. --> <!-- Add any other context about the problem here. -->

View file

@ -0,0 +1,22 @@
---
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. -->

View file

@ -0,0 +1,13 @@
---
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. -->

View file

@ -0,0 +1,9 @@
---
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]
* [ ] ...

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
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 Normal file
View file

@ -0,0 +1,24 @@
# 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.

11
.gitignore vendored
View file

@ -1,11 +1,14 @@
# config
config.json
# npm # npm
dist dist
web web
node_modules node_modules
# config
config.json
# ide # ide
.idea .idea
.vscode .vscode
# log
yarn-error.log

View file

@ -34,7 +34,13 @@
- [Ryan Hartzell](https://github.com/ryan-hartzell) - [Ryan Hartzell](https://github.com/ryan-hartzell)
- [Thibault Nocchi](https://github.com/ThibaultNocchi) - [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi) - [MrTimscampi](https://github.com/MrTimscampi)
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
- [Sarab Singh](https://github.com/sarab97) - [Sarab Singh](https://github.com/sarab97)
- [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)
# Emby Contributors # Emby Contributors

View file

@ -44,7 +44,8 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies ### Dependencies
- Yarn - [Node.js](https://nodejs.org/en/download/)
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
- Gulp-cli - Gulp-cli
### Getting Started ### Getting Started
@ -78,4 +79,4 @@ Jellyfin Web is the frontend used for most of the clients available for end user
```sh ```sh
yarn build:standalone yarn build:standalone
``` ```

View file

@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin-web" name: "jellyfin-web"
version: "10.6.0" version: "10.7.0"
packages: packages:
- debian.all - debian.all
- fedora.all - fedora.all

View file

@ -4,6 +4,7 @@
set -o errexit set -o errexit
set -o pipefail set -o pipefail
set -o xtrace
usage() { usage() {
echo -e "bump_version - increase the shared version and generate changelogs" echo -e "bump_version - increase the shared version and generate changelogs"
@ -23,10 +24,7 @@ build_file="./build.yaml"
new_version="$1" new_version="$1"
# Parse the version from shared version file # Parse the version from shared version file
old_version="$( old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )"
grep "appVersion" ${shared_version_file} | head -1 \
| sed -E 's/var appVersion = "([0-9\.]+)";/\1/'
)"
echo "Old version in appHost is: $old_version" echo "Old version in appHost is: $old_version"
# Set the shared version to the specified new_version # Set the shared version to the specified new_version
@ -34,11 +32,8 @@ old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' cha
new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )" new_version_sed="$( cut -f1 -d'-' <<<"${new_version}" )"
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file} sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
old_version="$( old_version="$( grep "version:" ${build_file} | sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/' )"
grep "version:" ${build_file} \ echo "Old version in ${build_file}: ${old_version}"
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
)"
echo "Old version in ${build_file}: $old_version`"
# Set the build.yaml version to the specified new_version # Set the build.yaml version to the specified new_version
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
@ -54,7 +49,7 @@ fi
debian_changelog_file="debian/changelog" debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )" debian_changelog_temp="$( mktemp )"
# Create new temp file with our changelog # Create new temp file with our changelog
echo -e "jellyfin (${new_version_deb}) unstable; urgency=medium echo -e "jellyfin-web (${new_version_deb}) unstable; urgency=medium
* New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version} * New upstream version ${new_version}; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v${new_version}
@ -65,15 +60,15 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp}
mv ${debian_changelog_temp} ${debian_changelog_file} mv ${debian_changelog_temp} ${debian_changelog_file}
# Write out a temporary Yum changelog with our new stuff prepended and some templated formatting # Write out a temporary Yum changelog with our new stuff prepended and some templated formatting
fedora_spec_file="fedora/jellyfin.spec" fedora_spec_file="fedora/jellyfin-web.spec"
fedora_changelog_temp="$( mktemp )" fedora_changelog_temp="$( mktemp )"
fedora_spec_temp_dir="$( mktemp -d )" fedora_spec_temp_dir="$( mktemp -d )"
fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin.spec.tmp" fedora_spec_temp="${fedora_spec_temp_dir}/jellyfin-web.spec.tmp"
# Make a copy of our spec file for hacking # Make a copy of our spec file for hacking
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/ cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
pushd ${fedora_spec_temp_dir} pushd ${fedora_spec_temp_dir}
# Split out the stuff before and after changelog # Split out the stuff before and after changelog
csplit jellyfin.spec "/^%changelog/" # produces xx00 xx01 csplit jellyfin-web.spec "/^%changelog/" # produces xx00 xx01
# Update the version in xx00 # Update the version in xx00
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00 sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# Remove the header from xx01 # Remove the header from xx01
@ -92,5 +87,5 @@ mv ${fedora_spec_temp} ${fedora_spec_file}
rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir} rm -rf ${fedora_changelog_temp} ${fedora_spec_temp_dir}
# Stage the changed files for commit # Stage the changed files for commit
git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file} Dockerfile* git add ${shared_version_file} ${build_file} ${debian_changelog_file} ${fedora_spec_file}
git status git status

6
debian/changelog vendored
View file

@ -1,3 +1,9 @@
jellyfin-web (10.7.0-1) unstable; urgency=medium
* Forthcoming stable release
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 27 Jul 2020 19:13:31 -0400
jellyfin-web (10.6.0-1) unstable; urgency=medium jellyfin-web (10.6.0-1) unstable; urgency=medium
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0 * New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0

View file

@ -1,7 +1,9 @@
FROM centos:7 FROM centos:7
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist ARG ARTIFACT_DIR=/dist
# Docker run environment # Docker run environment
ENV SOURCE_DIR=/jellyfin ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist ENV ARTIFACT_DIR=/dist
@ -9,19 +11,19 @@ ENV IS_DOCKER=YES
# Prepare CentOS environment # Prepare CentOS environment
RUN yum update -y \ RUN yum update -y \
&& yum install -y epel-release \ && yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel && yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
# Install recent NodeJS and Yarn # Install recent NodeJS and Yarn
RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \ RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \ && rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
&& yum install -y yarn && yum install -y yarn
# Link to build script # Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.centos.all /build.sh RUN ln -sf ${SOURCE_DIR}/deployment/build.centos /build.sh
VOLUME ${SOURCE_DIR}/ VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}/ VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"] ENTRYPOINT ["/build.sh"]

View file

@ -1,7 +1,9 @@
FROM debian:10 FROM debian:10
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist ARG ARTIFACT_DIR=/dist
# Docker run environment # Docker run environment
ENV SOURCE_DIR=/jellyfin ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist ENV ARTIFACT_DIR=/dist
@ -10,16 +12,16 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment # Prepare Debian build environment
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y debhelper mmv npm git && apt-get install -y debhelper mmv npm git
# Prepare Yarn # Prepare Yarn
RUN npm install -g yarn RUN npm install -g yarn
# Link to build script # Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.debian.all /build.sh RUN ln -sf ${SOURCE_DIR}/deployment/build.debian /build.sh
VOLUME ${SOURCE_DIR}/ VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}/ VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"] ENTRYPOINT ["/build.sh"]

View file

@ -0,0 +1,11 @@
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 python
WORKDIR ${SOURCE_DIR}
COPY . .
RUN yarn install && mv dist ${ARTIFACT_DIR}

View file

@ -1,7 +1,9 @@
FROM fedora:31 FROM fedora:31
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist ARG ARTIFACT_DIR=/dist
# Docker run environment # Docker run environment
ENV SOURCE_DIR=/jellyfin ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist ENV ARTIFACT_DIR=/dist
@ -9,13 +11,13 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment # Prepare Fedora environment
RUN dnf update -y \ RUN dnf update -y \
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core 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 # Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora.all /build.sh RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
VOLUME ${SOURCE_DIR}/ VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}/ VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"] ENTRYPOINT ["/build.sh"]

View file

@ -1,16 +1,17 @@
FROM debian:10 FROM debian:10
# Docker build arguments # Docker build arguments
ARG SOURCE_DIR=/jellyfin ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist ARG ARTIFACT_DIR=/dist
# Docker run environment # Docker run environment
ENV SOURCE_DIR=/jellyfin ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV IS_DOCKER=YES ENV IS_DOCKER=YES
# Prepare Debian build environment # Prepare Debian build environment
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y mmv npm git && apt-get install -y mmv npm git
# Prepare Yarn # Prepare Yarn
RUN npm install -g yarn RUN npm install -g yarn
@ -18,8 +19,8 @@ RUN npm install -g yarn
# Link to build script # Link to build script
RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh RUN ln -sf ${SOURCE_DIR}/deployment/build.portable /build.sh
VOLUME ${SOURCE_DIR}/ VOLUME ${SOURCE_DIR}
VOLUME ${ARTIFACT_DIR}/ VOLUME ${ARTIFACT_DIR}
ENTRYPOINT ["/build.sh"] ENTRYPOINT ["/build.sh"]

41
deployment/build.centos Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd fedora
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
sed -i "/%changelog/q" jellyfin-web.spec
cat <<EOF >>jellyfin-web.spec
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
EOF
popd
fi
# build rpm
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# move the artifacts
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

View file

@ -1,27 +0,0 @@
#!/bin/bash
#= CentOS 7 all .rpm
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

39
deployment/build.debian Executable file
View file

@ -0,0 +1,39 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd debian
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
cat <<EOF >changelog
jellyfin-web (${BUILD_ID}-unstable) unstable; urgency=medium
* Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
-- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 )
EOF
popd
fi
# build deb
dpkg-buildpackage -us -uc --pre-clean --post-clean
mkdir -p ${ARTIFACT_DIR}
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}
cp -a /tmp/yarn.lock yarn.lock
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

View file

@ -1,25 +0,0 @@
#!/bin/bash
#= Debian/Ubuntu all .deb
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build DEB
dpkg-buildpackage -us -uc --pre-clean --post-clean
mkdir -p ${ARTIFACT_DIR}/
mv ../jellyfin*.{deb,dsc,tar.gz,buildinfo,changes} ${ARTIFACT_DIR}/
cp -a /tmp/yarn.lock yarn.lock
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
popd

41
deployment/build.fedora Executable file
View file

@ -0,0 +1,41 @@
#!/bin/bash
set -o errexit
set -o xtrace
# move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# modify changelog to unstable configuration if IS_UNSTABLE
if [[ ${IS_UNSTABLE} == 'yes' ]]; then
pushd fedora
PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' )
sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin-web.spec
sed -i "/%changelog/q" jellyfin-web.spec
cat <<EOF >>jellyfin-web.spec
* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org>
- Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID}
EOF
popd
fi
# build rpm
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# move the artifacts
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

View file

@ -1,27 +0,0 @@
#!/bin/bash
#= Fedora 29+ all .rpm
set -o errexit
set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
cp -a yarn.lock /tmp/yarn.lock
# Build RPM
make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS
rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mv /root/rpmbuild/RPMS/noarch/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}
fi
rm -f fedora/jellyfin*.tar.gz
cp -a /tmp/yarn.lock yarn.lock
popd

View file

@ -1,25 +1,27 @@
#!/bin/bash #!/bin/bash
#= Portable .NET DLL .tar.gz
set -o errexit set -o errexit
set -o xtrace set -o xtrace
# Move to source directory # move to source directory
pushd ${SOURCE_DIR} pushd ${SOURCE_DIR}
# Get version # get version
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" if [[ ${IS_UNSTABLE} == 'yes' ]]; then
version="${BUILD_ID}"
else
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
fi
# Build archives # build archives
npx yarn install npx yarn install
mv dist/ jellyfin-web_${version} mv dist jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version} tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist/ rm -rf dist
# Move the artifacts out # move the artifacts
mkdir -p ${ARTIFACT_DIR}/ mkdir -p ${ARTIFACT_DIR}
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/ mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}
if [[ ${IS_DOCKER} == YES ]]; then if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR} chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}

View file

@ -1,7 +1,7 @@
%global debug_package %{nil} %global debug_package %{nil}
Name: jellyfin-web Name: jellyfin-web
Version: 10.6.0 Version: 10.7.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: The Free Software Media System web client Summary: The Free Software Media System web client
License: GPLv3 License: GPLv3
@ -12,8 +12,11 @@ Source0: jellyfin-web-%{version}.tar.gz
%if 0%{?centos} %if 0%{?centos}
BuildRequires: yarn BuildRequires: yarn
%else %else
BuildRequires nodejs-yarn BuildRequires: nodejs-yarn
%endif %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 BuildArch: noarch
# Disable Automatic Dependency Processing # Disable Automatic Dependency Processing
@ -39,5 +42,7 @@ mv dist %{buildroot}%{_datadir}/jellyfin-web
%{_datadir}/licenses/jellyfin/LICENSE %{_datadir}/licenses/jellyfin/LICENSE
%changelog %changelog
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org> * Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release - Forthcoming stable release

View file

@ -45,7 +45,7 @@ const options = {
query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg'] query: ['src/**/*.png', 'src/**/*.jpg', 'src/**/*.gif', 'src/**/*.svg']
}, },
copy: { copy: {
query: ['src/**/*.json', 'src/**/*.ico'] query: ['src/**/*.json', 'src/**/*.ico', 'src/**/*.mp3']
}, },
injectBundle: { injectBundle: {
query: 'src/index.html' query: 'src/index.html'
@ -181,16 +181,15 @@ function copy(query) {
.pipe(browserSync.stream()); .pipe(browserSync.stream());
} }
function copyIndex() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function injectBundle() { function injectBundle() {
return src(options.injectBundle.query, { base: './src/' }) return src(options.injectBundle.query, { base: './src/' })
.pipe(inject( .pipe(inject(
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true } src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), {
relative: true,
transform: function (filepath) {
return `<script src="${filepath}" defer></script>`;
}
}
)) ))
.pipe(dest('dist/')) .pipe(dest('dist/'))
.pipe(browserSync.stream()); .pipe(browserSync.stream());
@ -200,6 +199,6 @@ function build(standalone) {
return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy));
} }
exports.default = series(build(false), copyIndex); exports.default = series(build(false), injectBundle);
exports.standalone = series(build(true), injectBundle); exports.standalone = series(build(true), injectBundle);
exports.serve = series(exports.standalone, serve); exports.serve = series(exports.standalone, serve);

View file

@ -5,27 +5,31 @@
"repository": "https://github.com/jellyfin/jellyfin-web", "repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.6", "@babel/core": "^7.12.3",
"@babel/plugin-transform-modules-amd": "^7.9.6", "@babel/eslint-parser": "^7.12.1",
"@babel/polyfill": "^7.8.7", "@babel/eslint-plugin": "^7.12.1",
"@babel/preset-env": "^7.8.6", "@babel/plugin-proposal-class-properties": "^7.10.1",
"autoprefixer": "^9.7.6", "@babel/plugin-proposal-private-methods": "^7.12.1",
"@babel/plugin-transform-modules-amd": "^7.12.1",
"@babel/polyfill": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"autoprefixer": "^9.8.6",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"browser-sync": "^2.26.7", "browser-sync": "^2.26.13",
"clean-webpack-plugin": "^3.0.0", "confusing-browser-globals": "^1.0.10",
"copy-webpack-plugin": "^5.1.1", "copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2", "css-loader": "^5.0.0",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"del": "^5.1.0", "del": "^6.0.0",
"eslint": "^6.8.0", "eslint": "^7.12.0",
"eslint-plugin-compat": "^3.5.1", "eslint-plugin-compat": "^3.5.1",
"eslint-plugin-eslint-comments": "^3.1.2", "eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"file-loader": "^6.0.0", "file-loader": "^6.1.1",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-babel": "^8.0.0", "gulp-babel": "^8.0.0",
"gulp-cli": "^2.2.0", "gulp-cli": "^2.3.0",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-htmlmin": "^5.0.1", "gulp-htmlmin": "^5.0.1",
"gulp-if": "^3.0.0", "gulp-if": "^3.0.0",
@ -35,52 +39,51 @@
"gulp-postcss": "^8.0.0", "gulp-postcss": "^8.0.0",
"gulp-sass": "^4.0.2", "gulp-sass": "^4.0.2",
"gulp-sourcemaps": "^2.6.5", "gulp-sourcemaps": "^2.6.5",
"gulp-terser": "^1.2.0", "gulp-terser": "^1.4.0",
"html-webpack-plugin": "^4.3.0", "html-webpack-plugin": "^4.5.0",
"lazypipe": "^1.0.2", "lazypipe": "^1.0.2",
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.7.0", "postcss-preset-env": "^6.7.0",
"style-loader": "^1.1.3", "style-loader": "^2.0.0",
"stylelint": "^13.3.3", "stylelint": "^13.7.2",
"stylelint-config-rational-order": "^0.1.2", "stylelint-config-rational-order": "^0.1.2",
"stylelint-no-browser-hacks": "^1.2.1", "stylelint-no-browser-hacks": "^1.2.1",
"stylelint-order": "^4.0.0", "stylelint-order": "^4.1.0",
"webpack": "^4.41.5", "webpack": "^5.2.0",
"webpack-cli": "^3.3.10",
"webpack-concat-plugin": "^3.0.0",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2", "webpack-merge": "^4.2.2",
"webpack-stream": "^5.2.1" "webpack-stream": "^6.1.0",
"worker-plugin": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"alameda": "^1.4.0", "alameda": "^1.4.0",
"blurhash": "^1.1.3",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"date-fns": "^2.13.0", "date-fns": "^2.16.1",
"document-register-element": "^1.14.3", "epubjs": "^0.3.85",
"fast-text-encoding": "^1.0.1", "pdfjs-dist": "2.4.456",
"fast-text-encoding": "^1.0.3",
"flv.js": "^1.5.0", "flv.js": "^1.5.0",
"headroom.js": "^0.11.0", "headroom.js": "^0.12.0",
"hls.js": "^0.13.1", "hls.js": "^0.14.16",
"howler": "^2.1.3", "howler": "^2.2.1",
"intersection-observer": "^0.10.0", "intersection-observer": "^0.11.0",
"jellyfin-apiclient": "^1.1.1", "jellyfin-apiclient": "^1.4.2",
"jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"jstree": "^3.3.7", "jstree": "^3.3.10",
"libarchive.js": "^1.3.0",
"libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv", "libass-wasm": "https://github.com/jellyfin/JavascriptSubtitlesOctopus#4.0.0-jf-smarttv",
"material-design-icons-iconfont": "^5.0.1", "material-design-icons-iconfont": "^6.1.0",
"native-promise-only": "^0.8.0-a", "native-promise-only": "^0.8.0-a",
"page": "^1.11.6", "page": "^1.11.6",
"query-string": "^6.11.1", "query-string": "^6.13.6",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"screenfull": "^5.0.2", "screenfull": "^5.0.2",
"shaka-player": "^2.5.11", "sortablejs": "^1.12.0",
"sortablejs": "^1.10.2", "swiper": "^6.3.4",
"swiper": "^5.3.7",
"webcomponents.js": "^0.7.24", "webcomponents.js": "^0.7.24",
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.4.1"
}, },
"babel": { "babel": {
"presets": [ "presets": [
@ -89,26 +92,285 @@
"overrides": [ "overrides": [
{ {
"test": [ "test": [
"src/components/accessSchedule/accessSchedule.js",
"src/components/actionSheet/actionSheet.js",
"src/components/activitylog.js",
"src/components/alert.js",
"src/components/alphaPicker/alphaPicker.js",
"src/components/appFooter/appFooter.js",
"src/components/apphost.js",
"src/components/appRouter.js",
"src/components/autoFocuser.js", "src/components/autoFocuser.js",
"src/components/backdrop/backdrop.js",
"src/components/cardbuilder/cardBuilder.js", "src/components/cardbuilder/cardBuilder.js",
"src/components/filedownloader.js", "src/components/cardbuilder/chaptercardbuilder.js",
"src/components/cardbuilder/peoplecardbuilder.js",
"src/components/channelMapper/channelMapper.js",
"src/components/collectionEditor/collectionEditor.js",
"src/components/confirm/confirm.js",
"src/components/dialog/dialog.js",
"src/components/dialogHelper/dialogHelper.js",
"src/components/directorybrowser/directorybrowser.js",
"src/components/displaySettings/displaySettings.js",
"src/components/favoriteitems.js",
"src/components/fetchhelper.js",
"src/components/filterdialog/filterdialog.js",
"src/components/filtermenu/filtermenu.js",
"src/components/focusManager.js",
"src/components/groupedcards.js",
"src/components/guide/guide.js",
"src/components/guide/guide-settings.js",
"src/components/homeScreenSettings/homeScreenSettings.js",
"src/components/homesections/homesections.js",
"src/components/htmlMediaHelper.js",
"src/components/imageOptionsEditor/imageOptionsEditor.js",
"src/components/images/imageLoader.js", "src/components/images/imageLoader.js",
"src/components/lazyloader/lazyloader-intersectionobserver.js", "src/components/imageDownloader/imageDownloader.js",
"src/components/imageeditor/imageeditor.js",
"src/components/imageUploader/imageUploader.js",
"src/components/indicators/indicators.js",
"src/components/itemContextMenu.js",
"src/components/itemHelper.js",
"src/components/itemidentifier/itemidentifier.js",
"src/components/itemMediaInfo/itemMediaInfo.js",
"src/components/itemsrefresher.js",
"src/components/layoutManager.js",
"src/components/lazyLoader/lazyLoaderIntersectionObserver.js",
"src/components/libraryoptionseditor/libraryoptionseditor.js",
"src/components/listview/listview.js",
"src/components/loading/loading.js",
"src/components/maintabsmanager.js",
"src/components/mediainfo/mediainfo.js",
"src/components/mediaLibraryCreator/mediaLibraryCreator.js",
"src/components/mediaLibraryEditor/mediaLibraryEditor.js",
"src/components/metadataEditor/metadataEditor.js",
"src/components/metadataEditor/personEditor.js",
"src/components/multiSelect/multiSelect.js",
"src/components/notifications/notifications.js",
"src/components/nowPlayingBar/nowPlayingBar.js",
"src/components/packageManager.js",
"src/components/playback/brightnessosd.js",
"src/components/playback/mediasession.js", "src/components/playback/mediasession.js",
"src/components/playback/nowplayinghelper.js",
"src/components/playback/playbackorientation.js",
"src/components/playback/playbackmanager.js",
"src/components/playback/playerSelectionMenu.js",
"src/components/playback/playersettingsmenu.js",
"src/components/playback/playmethodhelper.js",
"src/components/playback/playqueuemanager.js",
"src/components/playback/remotecontrolautoplay.js",
"src/components/playback/volumeosd.js",
"src/components/playbackSettings/playbackSettings.js",
"src/components/playerstats/playerstats.js",
"src/components/playlisteditor/playlisteditor.js",
"src/components/playmenu.js",
"src/components/pluginManager.js",
"src/components/prompt/prompt.js",
"src/components/qualityOptions.js",
"src/components/quickConnectSettings/quickConnectSettings.js",
"src/components/recordingcreator/recordingbutton.js",
"src/components/recordingcreator/recordingcreator.js",
"src/components/recordingcreator/seriesrecordingeditor.js",
"src/components/recordingcreator/recordinghelper.js",
"src/components/refreshdialog/refreshdialog.js",
"src/components/recordingcreator/recordingeditor.js",
"src/components/recordingcreator/recordingfields.js",
"src/components/remotecontrol/remotecontrol.js",
"src/components/sanatizefilename.js", "src/components/sanatizefilename.js",
"src/components/scrollManager.js", "src/components/scrollManager.js",
"src/plugins/experimentalWarnings/plugin.js",
"src/plugins/sessionPlayer/plugin.js",
"src/plugins/htmlAudioPlayer/plugin.js",
"src/plugins/comicsPlayer/plugin.js",
"src/plugins/chromecastPlayer/plugin.js",
"src/components/slideshow/slideshow.js",
"src/components/sortmenu/sortmenu.js",
"src/plugins/htmlVideoPlayer/plugin.js",
"src/plugins/logoScreensaver/plugin.js",
"src/plugins/playAccessValidation/plugin.js",
"src/components/search/searchfields.js",
"src/components/search/searchresults.js",
"src/components/settingshelper.js",
"src/components/shortcuts.js",
"src/components/subtitleeditor/subtitleeditor.js",
"src/components/subtitlesync/subtitlesync.js",
"src/components/subtitlesettings/subtitleappearancehelper.js",
"src/components/subtitlesettings/subtitlesettings.js",
"src/components/syncPlay/groupSelectionMenu.js",
"src/components/syncPlay/playbackPermissionManager.js",
"src/components/syncPlay/syncPlayManager.js",
"src/components/syncPlay/timeSyncManager.js",
"src/components/themeMediaPlayer.js",
"src/components/tabbedview/tabbedview.js",
"src/components/viewManager/viewManager.js",
"src/components/tvproviders/schedulesdirect.js",
"src/components/tvproviders/xmltv.js",
"src/components/toast/toast.js",
"src/components/tunerPicker.js",
"src/components/upnextdialog/upnextdialog.js",
"src/components/userdatabuttons/userdatabuttons.js",
"src/components/viewContainer.js",
"src/components/viewSettings/viewSettings.js",
"src/components/castSenderApi.js",
"src/controllers/session/addServer/index.js",
"src/controllers/session/forgotPassword/index.js",
"src/controllers/session/resetPassword/index.js",
"src/controllers/session/login/index.js",
"src/controllers/session/selectServer/index.js",
"src/controllers/dashboard/apikeys.js",
"src/controllers/dashboard/dashboard.js",
"src/controllers/dashboard/devices/device.js",
"src/controllers/dashboard/devices/devices.js",
"src/controllers/dashboard/dlna/profile.js",
"src/controllers/dashboard/dlna/profiles.js",
"src/controllers/dashboard/dlna/settings.js",
"src/controllers/dashboard/encodingsettings.js",
"src/controllers/dashboard/general.js",
"src/controllers/dashboard/librarydisplay.js",
"src/controllers/dashboard/logs.js",
"src/controllers/music/musicalbums.js",
"src/controllers/music/musicartists.js",
"src/controllers/music/musicgenres.js",
"src/controllers/music/musicplaylists.js",
"src/controllers/music/musicrecommended.js",
"src/controllers/music/songs.js",
"src/controllers/dashboard/library.js",
"src/controllers/dashboard/metadataImages.js",
"src/controllers/dashboard/metadatanfo.js",
"src/controllers/dashboard/networking.js",
"src/controllers/dashboard/notifications/notification/index.js",
"src/controllers/dashboard/notifications/notifications/index.js",
"src/controllers/dashboard/playback.js",
"src/controllers/dashboard/plugins/add/index.js",
"src/controllers/dashboard/plugins/installed/index.js",
"src/controllers/dashboard/plugins/available/index.js",
"src/controllers/dashboard/plugins/repositories/index.js",
"src/controllers/dashboard/quickConnect.js",
"src/controllers/dashboard/scheduledtasks/scheduledtask.js",
"src/controllers/dashboard/scheduledtasks/scheduledtasks.js",
"src/controllers/dashboard/serveractivity.js",
"src/controllers/dashboard/streaming.js",
"src/controllers/dashboard/users/useredit.js",
"src/controllers/dashboard/users/userlibraryaccess.js",
"src/controllers/dashboard/users/usernew.js",
"src/controllers/dashboard/users/userparentalcontrol.js",
"src/controllers/dashboard/users/userpasswordpage.js",
"src/controllers/dashboard/users/userprofilespage.js",
"src/controllers/home.js",
"src/controllers/list.js",
"src/controllers/edititemmetadata.js",
"src/controllers/favorites.js",
"src/controllers/hometab.js",
"src/controllers/movies/moviecollections.js",
"src/controllers/movies/moviegenres.js",
"src/controllers/movies/movies.js",
"src/controllers/movies/moviesrecommended.js",
"src/controllers/movies/movietrailers.js",
"src/controllers/playback/nowplaying.js",
"src/controllers/playback/videoosd.js",
"src/controllers/itemDetails/index.js",
"src/controllers/playback/queue/index.js",
"src/controllers/playback/video/index.js",
"src/controllers/searchpage.js",
"src/controllers/livetv/livetvguide.js",
"src/controllers/livetvtuner.js",
"src/controllers/livetv/livetvsuggested.js",
"src/controllers/livetvstatus.js",
"src/controllers/livetvguideprovider.js",
"src/controllers/livetvsettings.js",
"src/controllers/livetv/livetvrecordings.js",
"src/controllers/livetv/livetvschedule.js",
"src/controllers/livetv/livetvseriestimers.js",
"src/controllers/livetv/livetvchannels.js",
"src/controllers/shows/episodes.js",
"src/controllers/shows/tvgenres.js",
"src/controllers/shows/tvlatest.js",
"src/controllers/shows/tvrecommended.js",
"src/controllers/shows/tvshows.js",
"src/controllers/shows/tvstudios.js",
"src/controllers/shows/tvupcoming.js",
"src/controllers/user/display/index.js",
"src/controllers/user/home/index.js",
"src/controllers/user/menu/index.js",
"src/controllers/user/playback/index.js",
"src/controllers/user/profile/index.js",
"src/controllers/user/quickConnect/index.js",
"src/controllers/user/subtitles/index.js",
"src/controllers/wizard/finish/index.js",
"src/controllers/wizard/remote/index.js",
"src/controllers/wizard/settings/index.js",
"src/controllers/wizard/start/index.js",
"src/controllers/wizard/user/index.js",
"src/elements/emby-button/emby-button.js",
"src/elements/emby-button/paper-icon-button-light.js",
"src/elements/emby-checkbox/emby-checkbox.js",
"src/elements/emby-collapse/emby-collapse.js",
"src/elements/emby-input/emby-input.js",
"src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js",
"src/elements/emby-itemscontainer/emby-itemscontainer.js",
"src/elements/emby-playstatebutton/emby-playstatebutton.js",
"src/elements/emby-programcell/emby-programcell.js",
"src/elements/emby-progressbar/emby-progressbar.js",
"src/elements/emby-progressring/emby-progressring.js",
"src/elements/emby-radio/emby-radio.js",
"src/elements/emby-ratingbutton/emby-ratingbutton.js",
"src/elements/emby-scrollbuttons/emby-scrollbuttons.js",
"src/elements/emby-scroller/emby-scroller.js",
"src/elements/emby-select/emby-select.js",
"src/elements/emby-slider/emby-slider.js",
"src/elements/emby-tabs/emby-tabs.js",
"src/elements/emby-textarea/emby-textarea.js",
"src/elements/emby-toggle/emby-toggle.js",
"src/libraries/screensavermanager.js",
"src/libraries/navdrawer/navdrawer.js",
"src/libraries/scroller.js",
"src/plugins/backdropScreensaver/plugin.js",
"src/plugins/bookPlayer/plugin.js",
"src/plugins/pdfPlayer/plugin.js",
"src/plugins/bookPlayer/tableOfContents.js",
"src/plugins/chromecastPlayer/chromecastHelper.js",
"src/plugins/photoPlayer/plugin.js",
"src/plugins/youtubePlayer/plugin.js",
"src/scripts/alphanumericshortcuts.js",
"src/scripts/autoBackdrops.js",
"src/scripts/autocast.js",
"src/scripts/browser.js",
"src/scripts/clientUtils.js",
"src/scripts/datetime.js",
"src/scripts/deleteHelper.js",
"src/scripts/dfnshelper.js", "src/scripts/dfnshelper.js",
"src/scripts/dom.js", "src/scripts/dom.js",
"src/scripts/editorsidebar.js",
"src/scripts/fileDownloader.js",
"src/scripts/filesystem.js", "src/scripts/filesystem.js",
"src/scripts/globalize.js",
"src/scripts/imagehelper.js", "src/scripts/imagehelper.js",
"src/scripts/itembynamedetailpage.js",
"src/scripts/inputManager.js", "src/scripts/inputManager.js",
"src/scripts/keyboardnavigation.js", "src/scripts/autoThemes.js",
"src/scripts/themeManager.js",
"src/scripts/keyboardNavigation.js",
"src/scripts/libraryMenu.js",
"src/scripts/libraryBrowser.js",
"src/scripts/livetvcomponents.js",
"src/scripts/mouseManager.js",
"src/scripts/multiDownload.js",
"src/scripts/playlists.js",
"src/scripts/scrollHelper.js",
"src/scripts/serverNotifications.js",
"src/scripts/routes.js",
"src/scripts/settings/appSettings.js", "src/scripts/settings/appSettings.js",
"src/scripts/settings/userSettings.js", "src/scripts/settings/userSettings.js",
"src/scripts/settings/webSettings.js" "src/scripts/settings/webSettings.js",
"src/scripts/shell.js",
"src/scripts/taskbutton.js",
"src/scripts/themeLoader.js",
"src/scripts/touchHelper.js"
], ],
"plugins": [ "plugins": [
"@babel/plugin-transform-modules-amd" "@babel/plugin-transform-modules-amd",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
] ]
} }
] ]
@ -118,7 +380,7 @@
"last 2 Chrome versions", "last 2 Chrome versions",
"last 2 ChromeAndroid versions", "last 2 ChromeAndroid versions",
"last 2 Safari versions", "last 2 Safari versions",
"last 2 iOS versions", "iOS > 10",
"last 2 Edge versions", "last 2 Edge versions",
"Chrome 27", "Chrome 27",
"Chrome 38", "Chrome 38",
@ -126,9 +388,11 @@
"Chrome 53", "Chrome 53",
"Chrome 56", "Chrome 56",
"Chrome 63", "Chrome 63",
"Edge 18",
"Firefox ESR" "Firefox ESR"
], ],
"scripts": { "scripts": {
"start": "yarn serve",
"serve": "gulp serve --development", "serve": "gulp serve --development",
"prepare": "gulp --production", "prepare": "gulp --production",
"build:development": "gulp --development", "build:development": "gulp --development",

33
scripts/duplicates.py Normal file
View file

@ -0,0 +1,33 @@
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')

View file

@ -11,7 +11,7 @@ langlst = os.listdir(langdir)
keys = [] keys = []
with open('scout.txt', 'r') as f: with open('unused.txt', 'r') as f:
for line in f: for line in f:
keys.append(line.strip('\n')) keys.append(line.strip('\n'))

View file

@ -15,6 +15,8 @@ print(langlst)
input('press enter to continue') input('press enter to continue')
keysus = [] keysus = []
missing = []
with open(langdir + '/' + 'en-us.json') as en: with open(langdir + '/' + 'en-us.json') as en:
langus = json.load(en) langus = json.load(en)
for key in langus: for key in langus:
@ -32,10 +34,19 @@ for lang in langlst:
for key in langjson: for key in langjson:
if key in keysus: if key in keysus:
langjnew[key] = langjson[key] langjnew[key] = langjson[key]
elif key not in missing:
missing.append(key)
f.seek(0) f.seek(0)
f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False)) f.write(json.dumps(langjnew, indent=inde, sort_keys=False, ensure_ascii=False))
f.write('\n') f.write('\n')
f.truncate() f.truncate()
f.close() f.close()
print(missing)
print('LENGTH: ' + str(len(missing)))
with open('missing.txt', 'w') as out:
for item in missing:
out.write(item + '\n')
out.close()
print('DONE') print('DONE')

View file

@ -16,7 +16,7 @@ langlst.append('en-us.json')
dep = [] dep = []
def grep(key): 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) p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = p.stdout.readlines() output = p.stdout.readlines()
if output: if output:
@ -34,7 +34,7 @@ for lang in langlst:
print(dep) print(dep)
print('LENGTH: ' + str(len(dep))) print('LENGTH: ' + str(len(dep)))
with open('scout.txt', 'w') as out: with open('unused.txt', 'w') as out:
for item in dep: for item in dep:
out.write(item + '\n') out.write(item + '\n')
out.close() out.close()

Binary file not shown.

View file

@ -235,6 +235,15 @@ div[data-role=controlgroup] a.ui-btn-active {
width: 50%; width: 50%;
} }
.localUsers .cardText-secondary {
white-space: pre-wrap;
height: 3em;
}
.customCssContainer textarea {
resize: none;
}
@media all and (min-width: 70em) { @media all and (min-width: 70em) {
.dashboardSections { .dashboardSections {
-webkit-flex-wrap: wrap; -webkit-flex-wrap: wrap;

View file

@ -30,6 +30,10 @@
align-items: flex-start; align-items: flex-start;
} }
.align-items-flex-end {
align-items: flex-end;
}
.justify-content-center { .justify-content-center {
justify-content: center; justify-content: center;
} }
@ -38,6 +42,10 @@
justify-content: flex-end; justify-content: flex-end;
} }
.justify-content-space-between {
justify-content: space-between;
}
.flex-wrap-wrap { .flex-wrap-wrap {
flex-wrap: wrap; flex-wrap: wrap;
} }

View file

@ -6,7 +6,6 @@
html { html {
@include font; @include font;
font-size: 93%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
text-size-adjust: 100%; text-size-adjust: 100%;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -26,7 +25,9 @@ h3 {
} }
.layout-tv { .layout-tv {
font-size: 130%; /* 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 { .layout-mobile {

View file

@ -1,8 +1,3 @@
html { html {
font-size: 82% !important; font-size: 82% !important;
} }
.formDialogFooter {
position: static !important;
margin: 0 -1em !important;
}

View file

@ -24,14 +24,14 @@
padding-top: 7em !important; padding-top: 7em !important;
} }
.layout-mobile .libraryPage {
padding-top: 4em !important;
}
.itemDetailPage { .itemDetailPage {
padding-top: 0 !important; padding-top: 0 !important;
} }
.layout-tv .itemDetailPage {
padding-top: 4.2em !important;
}
.standalonePage { .standalonePage {
padding-top: 4.5em !important; padding-top: 4.5em !important;
} }
@ -164,6 +164,13 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
contain: layout style paint; contain: layout style paint;
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 { .hiddenViewMenuBar .skinHeader {
@ -178,6 +185,10 @@
width: 100%; width: 100%;
} }
.layout-tv .sectionTabs {
width: 55%;
}
.selectedMediaFolder { .selectedMediaFolder {
background-color: #f2f2f2 !important; background-color: #f2f2f2 !important;
} }
@ -235,12 +246,6 @@
text-align: center; text-align: center;
} }
.layout-desktop .searchTabButton,
.layout-mobile .searchTabButton,
.layout-tv .headerSearchButton {
display: none !important;
}
.mainDrawer-scrollContainer { .mainDrawer-scrollContainer {
padding-bottom: 10vh; padding-bottom: 10vh;
} }
@ -272,7 +277,7 @@
} }
} }
@media all and (max-width: 84em) { @media all and (max-width: 100em) {
.withSectionTabs .headerTop { .withSectionTabs .headerTop {
padding-bottom: 0.55em; padding-bottom: 0.55em;
} }
@ -280,9 +285,13 @@
.sectionTabs { .sectionTabs {
font-size: 83.5%; font-size: 83.5%;
} }
.layout-tv .sectionTabs {
width: 100%;
}
} }
@media all and (min-width: 84em) { @media all and (min-width: 100em) {
.headerTop { .headerTop {
padding: 0.8em 0.8em; padding: 0.8em 0.8em;
} }
@ -438,16 +447,17 @@
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: center; background-position: center;
background-attachment: fixed; background-attachment: fixed;
height: 50vh; height: 40vh;
position: relative; position: relative;
animation: backdrop-fadein 800ms ease-in normal both;
} }
.layout-mobile .itemBackdrop { .layout-mobile .itemBackdrop {
background-attachment: scroll; background-attachment: scroll;
height: 26.5vh;
} }
.layout-desktop .itemBackdrop::after, .layout-desktop .itemBackdrop::after {
.layout-tv .itemBackdrop::after {
content: ""; content: "";
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -455,18 +465,28 @@
display: block; display: block;
} }
.layout-desktop .noBackdrop .itemBackdrop, .layout-tv .itemBackdrop,
.layout-tv .noBackdrop .itemBackdrop { .layout-desktop .noBackdrop .itemBackdrop {
display: none; display: none;
} }
.detailPageContent { .detailPageContent {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-left: 2%; padding-left: 32.45vw;
padding-right: 2%; padding-right: 2%;
} }
.layout-mobile .detailPageContent {
padding-left: 5%;
padding-right: 5%;
}
.layout-desktop .detailPageContent .emby-scroller,
.layout-tv .detailPageContent .emby-scroller {
margin-left: 0;
}
.layout-desktop .noBackdrop .detailPageContent, .layout-desktop .noBackdrop .detailPageContent,
.layout-tv .noBackdrop .detailPageContent { .layout-tv .noBackdrop .detailPageContent {
margin-top: 2.5em; margin-top: 2.5em;
@ -477,6 +497,10 @@
margin-top: 0; margin-top: 0;
} }
.detailSectionContent a {
color: inherit;
}
.personBackdrop { .personBackdrop {
background-size: contain; background-size: contain;
} }
@ -495,7 +519,23 @@
.parentName { .parentName {
display: block; display: block;
margin-bottom: 0.5em; margin: 0 0 0;
}
.layout-mobile .parentName {
margin: 0.6em 0 0;
}
.musicParentName {
margin: 0.15em 0 0.2em;
}
.layout-mobile .musicParentName {
margin: -0.25em 0 0.25em;
}
.layout-mobile .itemExternalLinks {
display: none;
} }
.mainDetailButtons { .mainDetailButtons {
@ -503,8 +543,6 @@
-webkit-box-align: center; -webkit-box-align: center;
-webkit-align-items: center; -webkit-align-items: center;
align-items: center; align-items: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
margin: 1em 0; margin: 1em 0;
} }
@ -520,6 +558,35 @@
font-weight: 600; font-weight: 600;
} }
.itemName.originalTitle {
margin: 0.2em 0 0.2em;
}
.itemName.parentNameLast {
margin: 0 0 0;
}
.layout-mobile .itemName.parentNameLast {
margin: 0.4em 0 0.4em;
}
.layout-mobile h1.itemName,
.layout-mobile h1.parentName {
font-size: 1.6em;
}
.itemName.parentNameLast.withOriginalTitle {
margin: 0 0 0;
}
.layout-mobile .itemName.parentNameLast.withOriginalTitle {
margin: 0.6em 0 0;
}
.layout-mobile .itemName.originalTitle {
margin: 0.5em 0 0.5em;
}
.nameContainer { .nameContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -546,6 +613,19 @@
text-align: center; text-align: center;
} }
.layout-mobile .mainDetailButtons {
margin-top: 1em;
margin-bottom: 0.5em;
}
.subtitle {
margin: 0.15em 0 0.2em;
}
.layout-mobile .subtitle {
margin: 0.2em 0 0.2em;
}
.detailPagePrimaryContainer { .detailPagePrimaryContainer {
display: flex; display: flex;
align-items: center; align-items: center;
@ -553,10 +633,14 @@
z-index: 2; z-index: 2;
} }
.layout-tv .detailPagePrimaryContainer {
display: block;
}
.layout-mobile .detailPagePrimaryContainer { .layout-mobile .detailPagePrimaryContainer {
display: block; display: block;
position: relative; position: relative;
top: 0; padding: 0.5em 3.3% 0.5em;
} }
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, .layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
@ -566,13 +650,18 @@
padding-left: 32.45vw; padding-left: 32.45vw;
} }
.layout-desktop .detailSticky, .layout-desktop .detailRibbon {
.layout-tv .detailSticky {
margin-top: -7.2em; margin-top: -7.2em;
height: 7.2em;
} }
.layout-desktop .noBackdrop .detailSticky, .layout-tv .detailRibbon {
.layout-tv .noBackdrop .detailSticky { margin-top: 0;
height: inherit;
}
.layout-desktop .noBackdrop .detailRibbon,
.layout-tv .noBackdrop .detailRibbon {
margin-top: 0; margin-top: 0;
} }
@ -584,6 +673,9 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: left; text-align: left;
min-width: 0;
max-width: 100%;
overflow: hidden;
} }
.layout-mobile .infoText { .layout-mobile .infoText {
@ -594,12 +686,29 @@
margin: 1.25em 0; margin: 1.25em 0;
} }
.detailImageContainer { .layout-mobile .detailPageSecondaryContainer {
position: relative; margin: 1em 0;
margin-top: -25vh; }
.layout-mobile .detailImageContainer {
display: none;
}
.detailImageContainer .card {
position: absolute;
top: 50%;
float: left; float: left;
width: 25vw; width: 25vw;
z-index: 3; z-index: 3;
transform: translateY(-50%);
}
.detailImageContainer .card.backdropCard {
top: 35%;
}
.detailImageContainer .card.squareCard {
top: 40%;
} }
.layout-desktop .noBackdrop .detailImageContainer, .layout-desktop .noBackdrop .detailImageContainer,
@ -612,11 +721,11 @@
} }
.detailLogo { .detailLogo {
width: 30vw; width: 25vw;
height: 25vh; height: 16vh;
position: absolute; position: absolute;
top: 10vh; top: 10vh;
right: 20vw; right: 25vw;
background-size: contain; background-size: contain;
} }
@ -641,7 +750,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
.itemDetailGalleryLink.defaultCardBackground { .itemDetailGalleryLink.defaultCardBackground {
height: 23vw; /* Dirty hack to get it to look somewhat square. Less than ideal. */ /* Dirty hack to get it to look somewhat square. Less than ideal. */
height: 23vw;
} }
.itemDetailGalleryLink.defaultCardBackground > .material-icons { .itemDetailGalleryLink.defaultCardBackground > .material-icons {
@ -655,14 +765,18 @@ div.itemDetailGalleryLink.defaultCardBackground {
position: relative; position: relative;
} }
.layout-desktop .detailPageWrapperContainer, .layout-desktop .itemBackdrop {
.layout-tv .detailPageWrapperContainer { height: 40vh;
margin-top: 7.2em;
} }
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer, .layout-desktop .detailPageWrapperContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer { .layout-tv .detailPageWrapperContainer {
padding-left: 3.3%; margin-top: 0.1em;
}
.layout-desktop .detailImageContainer .card,
.layout-tv .detailImageContainer .card {
top: 10%;
} }
.btnPlaySimple { .btnPlaySimple {
@ -677,13 +791,8 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
.emby-button.detailFloatingButton { .emby-button.detailFloatingButton {
position: absolute; font-size: 1.4em;
background-color: rgba(0, 0, 0, 0.5) !important; margin-right: 0.5em !important;
z-index: 1;
top: 50%;
left: 50%;
margin: -2.2em 0 0 -2.2em;
padding: 0.4em !important;
color: rgba(255, 255, 255, 0.76); color: rgba(255, 255, 255, 0.76);
} }
@ -693,16 +802,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
@media all and (max-width: 62.5em) { @media all and (max-width: 62.5em) {
.parentName { .parentName {
margin-bottom: 1em; margin-bottom: 0;
} }
.itemDetailPage { .itemDetailPage {
padding-top: 0 !important; padding-top: 0 !important;
} }
.detailimg-hidemobile {
display: none;
}
} }
@media all and (min-width: 31.25em) { @media all and (min-width: 31.25em) {
@ -750,7 +855,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
-webkit-align-items: center; -webkit-align-items: center;
align-items: center; align-items: center;
margin: 0 !important; margin: 0 !important;
padding: 0.5em 0.7em !important; padding: 0.7em 0.7em !important;
} }
@media all and (min-width: 29em) { @media all and (min-width: 29em) {
@ -797,9 +902,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
.detailImageProgressContainer { .detailImageProgressContainer {
position: absolute;
bottom: 0; bottom: 0;
width: 22.786458333333332vw; margin-top: -0.4vw;
width: 100%;
} }
.detailButton-text { .detailButton-text {
@ -818,25 +923,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
} }
@media all and (min-width: 62.5em) { @media all and (min-width: 100em) {
.headerTop {
padding-left: 0.8em;
padding-right: 0.8em;
}
.headerTabs {
align-self: center;
width: auto;
align-items: center;
justify-content: center;
margin-top: -4.2em;
position: relative;
}
.detailFloatingButton {
display: none !important;
}
.personBackdrop { .personBackdrop {
display: none !important; display: none !important;
} }
@ -845,6 +932,11 @@ div.itemDetailGalleryLink.defaultCardBackground {
font-size: 108%; font-size: 108%;
margin: 1.25em 0; margin: 1.25em 0;
} }
.layout-tv .mainDetailButtons {
font-size: 108%;
margin: 1em 0 1.25em;
}
} }
@media all and (max-width: 50em) { @media all and (max-width: 50em) {
@ -866,6 +958,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
} }
} }
.detailVerticalSection .emby-scrollbuttons {
padding-top: 0.4em;
}
.layout-tv .detailVerticalSection { .layout-tv .detailVerticalSection {
margin-bottom: 3.4em !important; margin-bottom: 3.4em !important;
} }
@ -954,6 +1050,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
margin-bottom: 2.7em; margin-bottom: 2.7em;
} }
.layout-mobile .verticalSection-extrabottompadding {
margin-bottom: 1em;
}
.sectionTitleButton, .sectionTitleButton,
.sectionTitleIconButton { .sectionTitleIconButton {
margin-right: 0 !important; margin-right: 0 !important;
@ -979,7 +1079,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
div:not(.sectionTitleContainer-cards) > .sectionTitle-cards { div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin: 0; margin: 0;
padding-top: 1.25em; padding-top: 0.5em;
padding-bottom: 0.2em;
}
.layout-mobile :not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin: 0;
padding-top: 0.5em;
} }
.sectionTitleButton { .sectionTitleButton {
@ -1046,13 +1152,13 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
} }
.layout-tv .padded-top-focusscale { .layout-tv .padded-top-focusscale {
padding-top: 1em; padding-top: 1.5em;
margin-top: -1em; margin-top: -1.5em;
} }
.layout-tv .padded-bottom-focusscale { .layout-tv .padded-bottom-focusscale {
padding-bottom: 1em; padding-bottom: 1.5em;
margin-bottom: -1em; margin-bottom: -1.5em;
} }
@media all and (min-height: 31.25em) { @media all and (min-height: 31.25em) {
@ -1132,6 +1238,12 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.trackSelections .selectContainer .selectLabel { .trackSelections .selectContainer .selectLabel {
margin: 0 0.2em 0 0; margin: 0 0.2em 0 0;
line-height: 1.75;
}
.layout-mobile .detailsGroupItem .label,
.layout-mobile .trackSelections .selectContainer .selectLabel {
flex-basis: 4.5em;
} }
.trackSelections .selectContainer .detailTrackSelect { .trackSelections .selectContainer .detailTrackSelect {
@ -1144,3 +1256,21 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin-top: 0; margin-top: 0;
font-size: 1.4em; font-size: 1.4em;
} }
.overview-controls {
display: flex;
justify-content: flex-end;
}
.detail-clamp-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 12;
-webkit-box-orient: vertical;
}
@media all and (min-width: 40em) {
.detail-clamp-text {
-webkit-line-clamp: 6;
}
}

View file

@ -12,6 +12,7 @@
.hiddenScrollX, .hiddenScrollX,
.layout-tv .scrollX { .layout-tv .scrollX {
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none;
} }
.hiddenScrollX-forced { .hiddenScrollX-forced {
@ -40,6 +41,7 @@
.hiddenScrollY, .hiddenScrollY,
.layout-tv .smoothScrollY { .layout-tv .smoothScrollY {
-ms-overflow-style: none; -ms-overflow-style: none;
scrollbar-width: none;
/* Can't do this because it not only hides the scrollbar, but also prevents scrolling */ /* Can't do this because it not only hides the scrollbar, but also prevents scrolling */

View file

@ -29,7 +29,7 @@ body {
.material-icons { .material-icons {
/* Fix font ligatures on older WebOS versions */ /* Fix font ligatures on older WebOS versions */
-webkit-font-feature-settings: "liga"; font-feature-settings: "liga";
} }
.backgroundContainer { .backgroundContainer {
@ -44,10 +44,6 @@ body {
.layout-mobile, .layout-mobile,
.layout-tv { .layout-tv {
-webkit-touch-callout: none; -webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
} }
@ -126,3 +122,30 @@ form {
transform: translateY(-100%); transform: translateY(-100%);
} }
} }
.drawerContent {
/* make sure the bottom of the drawer is visible when music is playing */
padding-bottom: 4em;
}
.force-scroll {
overflow-y: scroll;
}
.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;
}

View file

@ -6,31 +6,43 @@
-ms-user-select: none; -ms-user-select: none;
} }
.osdPoster img,
.pageContainer,
.videoOsdBottom { .videoOsdBottom {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 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;
} }
.osdHeader { .skinHeader-withBackground.osdHeader {
-webkit-transition: opacity 0.3s ease-out;
-o-transition: opacity 0.3s ease-out;
transition: opacity 0.3s ease-out; transition: opacity 0.3s ease-out;
position: relative; position: relative;
z-index: 1; z-index: 1;
background: rgba(0, 0, 0, 0.7) !important; background: linear-gradient(180deg, rgba(16, 16, 16, 0.75) 0%, rgba(16, 16, 16, 0) 100%);
-webkit-backdrop-filter: none !important; backdrop-filter: none;
backdrop-filter: none !important; color: #eee;
color: #eee !important; height: 7.5em;
} }
.osdHeader-hidden { .osdHeader-hidden {
opacity: 0; opacity: 0;
} }
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton) { .osdHeader .headerTop {
max-height: 3.5em;
}
.osdHeader .headerButton:not(.headerBackButton):not(.headerCastButton):not(.headerSyncButton) {
display: none; display: none;
} }
@ -87,34 +99,17 @@
opacity: 0.6; 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 { .videoOsdBottom-hidden {
opacity: 0; opacity: 0;
} }
.osdControls { .osdControls {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1; flex-grow: 1;
padding: 0 0.8em;
}
.layout-desktop .osdControls {
max-width: calc(100vh * 1.77 - 2vh);
} }
.videoOsdBottom .buttons { .videoOsdBottom .buttons {
@ -146,7 +141,7 @@
} }
.volumeButtons { .volumeButtons {
margin: 0 0.5em 0 auto; margin: 0 1em 0 0.29em;
display: flex; display: flex;
-webkit-align-items: center; -webkit-align-items: center;
align-items: center; align-items: center;
@ -154,33 +149,13 @@
.osdTimeText { .osdTimeText {
margin-left: 1em; margin-left: 1em;
margin-right: auto;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
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, .osdTitle,
.osdTitleSmall { .osdTitleSmall {
margin: 0 1em 0 0; margin: 0 1em 0 0;
@ -248,14 +223,7 @@
animation: spin 4s linear infinite; animation: spin 4s linear infinite;
} }
.pageContainer {
top: 0;
position: fixed;
}
@media all and (max-width: 30em) { @media all and (max-width: 30em) {
.btnFastForward,
.btnRewind,
.osdMediaInfo, .osdMediaInfo,
.osdPoster { .osdPoster {
display: none !important; display: none !important;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before After
Before After

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24"><title>Microsoft Edge icon</title><path d="M21.86 17.86q.14 0 .25.12.1.13.1.25t-.11.33l-.32.46-.43.53-.44.5q-.21.25-.38.42l-.22.23q-.58.53-1.34 1.04-.76.51-1.6.91-.86.4-1.74.64t-1.67.24q-.9 0-1.69-.28-.8-.28-1.48-.78-.68-.5-1.22-1.17-.53-.66-.92-1.44-.38-.77-.58-1.6-.2-.83-.2-1.67 0-1 .32-1.96.33-.97.87-1.8.14.95.55 1.77.41.82 1.02 1.5.6.68 1.38 1.21.78.54 1.64.9.86.36 1.77.56.92.2 1.8.2 1.12 0 2.18-.24 1.06-.23 2.06-.72l.2-.1.2-.05zm-15.5-1.27q0 1.1.27 2.15.27 1.06.78 2.03.51.96 1.24 1.77.74.82 1.66 1.4-1.47-.2-2.8-.74-1.33-.55-2.48-1.37-1.15-.83-2.08-1.9-.92-1.07-1.58-2.33T.36 14.94Q0 13.54 0 12.06q0-.81.32-1.49.31-.68.83-1.23.53-.55 1.2-.96.66-.4 1.35-.66.74-.27 1.5-.39.78-.12 1.55-.12.7 0 1.42.1.72.12 1.4.35.68.23 1.32.57.63.35 1.16.83-.35 0-.7.07-.33.07-.65.23v-.02q-.63.28-1.2.74-.57.46-1.05 1.04-.48.58-.87 1.26-.38.67-.65 1.39-.27.71-.42 1.44-.15.72-.15 1.38zM11.96.06q1.7 0 3.33.39 1.63.38 3.07 1.15 1.43.77 2.62 1.93 1.18 1.16 1.98 2.7.49.94.76 1.96.28 1 .28 2.08 0 .89-.23 1.7-.24.8-.69 1.48-.45.68-1.1 1.22-.64.53-1.45.88-.54.24-1.11.36-.58.13-1.16.13-.42 0-.97-.03-.54-.03-1.1-.12-.55-.1-1.05-.28-.5-.19-.84-.5-.12-.09-.23-.24-.1-.16-.1-.33 0-.15.16-.35.16-.2.35-.5.2-.28.36-.68.16-.4.16-.95 0-1.06-.4-1.96-.4-.91-1.06-1.64-.66-.74-1.52-1.28-.86-.55-1.79-.89-.84-.3-1.72-.44-.87-.14-1.76-.14-1.55 0-3.06.45T.94 7.55q.71-1.74 1.81-3.13 1.1-1.38 2.52-2.35Q6.68 1.1 8.37.58q1.7-.52 3.58-.52Z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

View file

@ -2,160 +2,158 @@
* require.js module definitions bundled by webpack * require.js module definitions bundled by webpack
*/ */
// Use define from require.js not webpack's define // Use define from require.js not webpack's define
var _define = window.define; const _define = window.define;
// document-register-element
var docRegister = require('document-register-element');
_define('document-register-element', function() {
return docRegister;
});
// fetch // fetch
var fetch = require('whatwg-fetch'); const fetch = require('whatwg-fetch');
_define('fetch', function() { _define('fetch', function() {
return fetch; return fetch;
}); });
// Blurhash
const blurhash = require('blurhash');
_define('blurhash', function() {
return blurhash;
});
// query-string // query-string
var query = require('query-string'); const query = require('query-string');
_define('queryString', function() { _define('queryString', function() {
return query; return query;
}); });
// flvjs // flvjs
var flvjs = require('flv.js/dist/flv').default; const flvjs = require('flv.js/dist/flv').default;
_define('flvjs', function() { _define('flvjs', function() {
return flvjs; return flvjs;
}); });
// jstree // jstree
var jstree = require('jstree'); const jstree = require('jstree');
require('jstree/dist/themes/default/style.css'); require('jstree/dist/themes/default/style.css');
_define('jstree', function() { _define('jstree', function() {
return jstree; return jstree;
}); });
// jquery // jquery
var jquery = require('jquery'); const jquery = require('jquery');
_define('jQuery', function() { _define('jQuery', function() {
return jquery; return jquery;
}); });
// hlsjs // hlsjs
var hlsjs = require('hls.js'); const hlsjs = require('hls.js');
_define('hlsjs', function() { _define('hlsjs', function() {
return hlsjs; return hlsjs;
}); });
// howler // howler
var howler = require('howler'); const howler = require('howler');
_define('howler', function() { _define('howler', function() {
return howler; return howler;
}); });
// resize-observer-polyfill // resize-observer-polyfill
var resize = require('resize-observer-polyfill').default; const resize = require('resize-observer-polyfill').default;
_define('resize-observer-polyfill', function() { _define('resize-observer-polyfill', function() {
return resize; return resize;
}); });
// shaka
var shaka = require('shaka-player');
_define('shaka', function() {
return shaka;
});
// swiper // swiper
var swiper = require('swiper/js/swiper'); const swiper = require('swiper/swiper-bundle');
require('swiper/css/swiper.min.css'); require('swiper/swiper-bundle.css');
_define('swiper', function() { _define('swiper', function() {
return swiper; return swiper;
}); });
// sortable // sortable
var sortable = require('sortablejs').default; const sortable = require('sortablejs').default;
_define('sortable', function() { _define('sortable', function() {
return sortable; return sortable;
}); });
// webcomponents // webcomponents
var webcomponents = require('webcomponents.js/webcomponents-lite'); const webcomponents = require('webcomponents.js/webcomponents-lite');
_define('webcomponents', function() { _define('webcomponents', function() {
return webcomponents; return webcomponents;
}); });
// libass-wasm // libass-wasm
var libassWasm = require('libass-wasm'); const libassWasm = require('libass-wasm');
_define('JavascriptSubtitlesOctopus', function() { _define('JavascriptSubtitlesOctopus', function() {
return libassWasm; return libassWasm;
}); });
// material-icons // material-icons
var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css'); const materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css');
_define('material-icons', function() { _define('material-icons', function() {
return materialIcons; return materialIcons;
}); });
// noto font const epubjs = require('epubjs');
var noto = require('jellyfin-noto'); _define('epubjs', function () {
_define('jellyfin-noto', function () { return epubjs;
return noto; });
const pdfjs = require('pdfjs-dist/build/pdf');
_define('pdfjs', function () {
return pdfjs;
}); });
// page.js // page.js
var page = require('page'); const page = require('page');
_define('page', function() { _define('page', function() {
return page; return page;
}); });
// core-js // core-js
var polyfill = require('@babel/polyfill/dist/polyfill'); const polyfill = require('@babel/polyfill/dist/polyfill');
_define('polyfill', function () { _define('polyfill', function () {
return polyfill; return polyfill;
}); });
// domtokenlist-shim // domtokenlist-shim
var classlist = require('classlist.js'); const classlist = require('classlist.js');
_define('classlist-polyfill', function () { _define('classlist-polyfill', function () {
return classlist; return classlist;
}); });
// Date-FNS // Date-FNS
var dateFns = require('date-fns'); const dateFns = require('date-fns');
_define('date-fns', function () { _define('date-fns', function () {
return dateFns; return dateFns;
}); });
var dateFnsLocale = require('date-fns/locale'); const dateFnsLocale = require('date-fns/locale');
_define('date-fns/locale', function () { _define('date-fns/locale', function () {
return dateFnsLocale; return dateFnsLocale;
}); });
var fast_text_encoding = require('fast-text-encoding'); const fast_text_encoding = require('fast-text-encoding');
_define('fast-text-encoding', function () { _define('fast-text-encoding', function () {
return fast_text_encoding; return fast_text_encoding;
}); });
// intersection-observer // intersection-observer
var intersection_observer = require('intersection-observer'); const intersection_observer = require('intersection-observer');
_define('intersection-observer', function () { _define('intersection-observer', function () {
return intersection_observer; return intersection_observer;
}); });
// screenfull // screenfull
var screenfull = require('screenfull'); const screenfull = require('screenfull');
_define('screenfull', function () { _define('screenfull', function () {
return screenfull; return screenfull;
}); });
// headroom.js // headroom.js
var headroom = require('headroom.js/dist/headroom'); const headroom = require('headroom.js/dist/headroom');
_define('headroom', function () { _define('headroom', function () {
return headroom; return headroom;
}); });
// apiclient // apiclient
var apiclient = require('jellyfin-apiclient'); const apiclient = require('jellyfin-apiclient');
_define('apiclient', function () { _define('apiclient', function () {
return apiclient.ApiClient; return apiclient.ApiClient;
@ -176,3 +174,9 @@ _define('connectionManagerFactory', function () {
_define('appStorage', function () { _define('appStorage', function () {
return apiclient.AppStorage; return apiclient.AppStorage;
}); });
// libarchive.js
const libarchive = require('libarchive.js');
_define('libarchive', function () {
return libarchive;
});

View file

@ -0,0 +1,97 @@
/* eslint-disable indent */
/**
* Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule
*/
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;
const pct = hours % 1;
if (pct) {
minutes = parseInt(60 * pct);
}
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
}
function populateHours(context) {
let html = '';
for (let i = 0; i < 24; i++) {
html += `<option value="${i}">${getDisplayTime(i)}</option>`;
}
html += `<option value="24">${getDisplayTime(0)}</option>`;
context.querySelector('#selectStart').innerHTML = html;
context.querySelector('#selectEnd').innerHTML = html;
}
function loadSchedule(context, {DayOfWeek, StartHour, EndHour}) {
context.querySelector('#selectDay').value = DayOfWeek || 'Sunday';
context.querySelector('#selectStart').value = StartHour || 0;
context.querySelector('#selectEnd').value = EndHour || 0;
}
function submitSchedule(context, options) {
const updatedSchedule = {
DayOfWeek: context.querySelector('#selectDay').value,
StartHour: context.querySelector('#selectStart').value,
EndHour: context.querySelector('#selectEnd').value
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
return void alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
}
context.submitted = true;
options.schedule = Object.assign(options.schedule, updatedSchedule);
dialogHelper.close(context);
}
export function show(options) {
return new Promise((resolve, reject) => {
import('text!./accessSchedule.template.html').then(({default: template}) => {
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;
});
});
});
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -1,5 +1,5 @@
<div class="formDialogHeader"> <div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" title="${LabelPrevious}" tabindex="-1"> <button is="paper-icon-button-light" class="btnCancel autoSize" title="${Previous}" tabindex="-1">
<span class="material-icons arrow_back" aria-hidden="true"></span> <span class="material-icons arrow_back" aria-hidden="true"></span>
</button> </button>
<h3 class="formDialogHeaderTitle"> <h3 class="formDialogHeaderTitle">
@ -12,13 +12,13 @@
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" id="selectDay" label="${LabelAccessDay}"> <select is="emby-select" id="selectDay" label="${LabelAccessDay}">
<option value="Sunday">${OptionSunday}</option> <option value="Sunday">${Sunday}</option>
<option value="Monday">${OptionMonday}</option> <option value="Monday">${Monday}</option>
<option value="Tuesday">${OptionTuesday}</option> <option value="Tuesday">${Tuesday}</option>
<option value="Wednesday">${OptionWednesday}</option> <option value="Wednesday">${Wednesday}</option>
<option value="Thursday">${OptionThursday}</option> <option value="Thursday">${Thursday}</option>
<option value="Friday">${OptionFriday}</option> <option value="Friday">${Friday}</option>
<option value="Saturday">${OptionSaturday}</option> <option value="Saturday">${Saturday}</option>
<option value="Everyday">${OptionEveryday}</option> <option value="Everyday">${OptionEveryday}</option>
<option value="Weekday">${OptionWeekdays}</option> <option value="Weekday">${OptionWeekdays}</option>
<option value="Weekend">${OptionWeekends}</option> <option value="Weekend">${OptionWeekends}</option>
@ -33,7 +33,7 @@
<div class="formDialogFooter"> <div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem"> <button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${ButtonAdd}</span> <span>${Add}</span>
</button> </button>
</div> </div>
</form> </form>

View file

@ -1,89 +0,0 @@
define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-button-light', 'formDialogStyle'], function (dialogHelper, datetime, globalize) {
'use strict';
function getDisplayTime(hours) {
var minutes = 0;
var pct = hours % 1;
if (pct) {
minutes = parseInt(60 * pct);
}
return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0));
}
function populateHours(context) {
var html = '';
for (var i = 0; i < 24; i++) {
html += '<option value="' + i + '">' + getDisplayTime(i) + '</option>';
}
html += '<option value="24">' + getDisplayTime(0) + '</option>';
context.querySelector('#selectStart').innerHTML = html;
context.querySelector('#selectEnd').innerHTML = html;
}
function loadSchedule(context, schedule) {
context.querySelector('#selectDay').value = schedule.DayOfWeek || 'Sunday';
context.querySelector('#selectStart').value = schedule.StartHour || 0;
context.querySelector('#selectEnd').value = schedule.EndHour || 0;
}
function submitSchedule(context, options) {
var updatedSchedule = {
DayOfWeek: context.querySelector('#selectDay').value,
StartHour: context.querySelector('#selectStart').value,
EndHour: context.querySelector('#selectEnd').value
};
if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) {
return void alert(globalize.translate('ErrorMessageStartHourGreaterThanEnd'));
}
context.submitted = true;
options.schedule = Object.assign(options.schedule, updatedSchedule);
dialogHelper.close(context);
}
return {
show: function (options) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'components/accessschedule/accessschedule.template.html', true);
xhr.onload = function (e) {
var template = this.response;
var dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
var html = '';
html += globalize.translateDocument(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', function () {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', function (e) {
submitSchedule(dlg, options);
e.preventDefault();
return false;
});
};
xhr.send();
});
}
};
});

View file

@ -0,0 +1,323 @@
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 = [];
if (!document) {
return results;
}
for (const elem of elems) {
const box = elem.getBoundingClientRect();
results.push({
top: box.top,
left: box.left,
width: box.width,
height: box.height
});
}
return results;
}
function getPosition(options, dlg) {
const windowSize = dom.getWindowSize();
const windowHeight = windowSize.innerHeight;
const windowWidth = windowSize.innerWidth;
const pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2;
}
pos.left += (pos.width || 0) / 2;
const height = dlg.offsetHeight || 300;
const width = dlg.offsetWidth || 160;
// Account for popup size
pos.top -= height / 2;
pos.left -= width / 2;
// Avoid showing too close to the bottom
const overflowX = pos.left + width - windowWidth;
const overflowY = pos.top + height - windowHeight;
if (overflowX > 0) {
pos.left -= (overflowX + 20);
}
if (overflowY > 0) {
pos.top -= (overflowY + 20);
}
pos.top += (options.offsetTop || 0);
pos.left += (options.offsetLeft || 0);
// Do some boundary checking
pos.top = Math.max(pos.top, 10);
pos.left = Math.max(pos.left, 10);
return pos;
}
function centerFocus(elem, horiz, on) {
import('scrollHelper').then(({default: scrollHelper}) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export function show(options) {
// items
// positionTo
// showCancel
// title
const dialogOptions = {
removeOnClose: true,
enableHistory: options.enableHistory,
scrollY: false
};
let isFullscreen;
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
isFullscreen = true;
dialogOptions.autoFocus = true;
} else {
dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation;
dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140;
dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100;
dialogOptions.autoFocus = false;
}
const dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen');
} else {
dlg.classList.add('actionsheet-not-fullscreen');
}
dlg.classList.add('actionSheet');
if (options.dialogClass) {
dlg.classList.add(options.dialogClass);
}
let html = '';
const scrollClassName = layoutManager.tv ? 'scrollY smoothScrollY hiddenScrollY' : 'scrollY';
let style = '';
// Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation
if (options.items.length > 20) {
const minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200;
style += 'min-width:' + minWidth + 'px;';
}
let renderIcon = false;
const icons = [];
let itemIcon;
for (const item of options.items) {
itemIcon = item.icon || (item.selected ? 'check' : null);
if (itemIcon) {
renderIcon = true;
}
icons.push(itemIcon || '');
}
if (layoutManager.tv) {
html += `<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1">
<span class="material-icons arrow_back"></span>
</button>`;
}
// If any items have an icon, give them all an icon just to make sure they're all lined up evenly
const center = options.title && (!renderIcon /*|| itemsWithIcons.length != options.items.length*/);
if (center || layoutManager.tv) {
html += '<div class="actionSheetContent actionSheetContent-centered">';
} else {
html += '<div class="actionSheetContent">';
}
if (options.title) {
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
}
if (options.text) {
html += '<p class="actionSheetText">' + options.text + '</p>';
}
let scrollerClassName = 'actionSheetScroller';
if (layoutManager.tv) {
scrollerClassName += ' actionSheetScroller-tv focuscontainer-x focuscontainer-y';
}
html += '<div class="' + scrollerClassName + ' ' + scrollClassName + '" style="' + style + '">';
let menuItemClass = 'listItem listItem-button actionSheetMenuItem';
if (options.border || options.shaded) {
menuItemClass += ' listItem-border';
}
if (options.menuItemClass) {
menuItemClass += ' ' + options.menuItemClass;
}
if (layoutManager.tv) {
menuItemClass += ' listItem-focusscale';
}
if (layoutManager.mobile) {
menuItemClass += ' actionsheet-xlargeFont';
}
// 'options.items' is HTMLOptionsCollection, so no fancy loops
for (let i = 0; i < options.items.length; i++) {
const item = options.items[i];
if (item.divider) {
html += '<div class="actionsheetDivider"></div>';
continue;
}
const autoFocus = item.selected && layoutManager.tv ? ' autoFocus' : '';
// Check for null in case int 0 was passed in
const optionId = item.id == null || item.id === '' ? item.value : item.id;
html += '<button' + autoFocus + ' is="emby-button" type="button" class="' + menuItemClass + '" data-id="' + optionId + '">';
itemIcon = icons[i];
if (itemIcon) {
html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
} else if (renderIcon && !center) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
}
html += '<div class="listItemBody actionsheetListItemBody">';
html += '<div class="listItemBodyText actionSheetItemText">';
html += (item.name || item.textContent || item.innerText);
html += '</div>';
if (item.secondaryText) {
html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
}
html += '</div>';
if (item.asideText) {
html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
}
html += '</button>';
}
if (options.showCancel) {
html += '<div class="buttons">';
html += `<button is="emby-button" type="button" class="btnCloseActionSheet">${globalize.translate('ButtonCancel')}</button>`;
html += '</div>';
}
html += '</div>';
dlg.innerHTML = html;
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
}
const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) {
btnCloseActionSheet.addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
let selectedId;
let timeout;
if (options.timeout) {
timeout = setTimeout(function () {
dialogHelper.close(dlg);
}, options.timeout);
}
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;
}
}
dialogHelper.close(dlg);
}
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (!isResolved) {
if (selectedId != null) {
if (options.callback) {
options.callback(selectedId);
}
resolve(selectedId);
} else {
reject();
}
}
});
dialogHelper.open(dlg);
const pos = options.positionTo && dialogOptions.size !== 'fullscreen' ? getPosition(options, dlg) : null;
if (pos) {
dlg.style.position = 'fixed';
dlg.style.margin = 0;
dlg.style.left = pos.left + 'px';
dlg.style.top = pos.top + 'px';
}
});
}
export default {
show: show
};

View file

@ -1,360 +0,0 @@
define(['dialogHelper', 'layoutManager', 'globalize', 'browser', 'dom', 'emby-button', 'css!./actionsheet', 'material-icons', 'scrollStyles', 'listViewStyle'], function (dialogHelper, layoutManager, globalize, browser, dom) {
'use strict';
function getOffsets(elems) {
var doc = document;
var results = [];
if (!doc) {
return results;
}
var box;
var elem;
for (var i = 0, length = elems.length; i < length; i++) {
elem = elems[i];
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (elem.getBoundingClientRect) {
box = elem.getBoundingClientRect();
} else {
box = { top: 0, left: 0 };
}
results[i] = {
top: box.top,
left: box.left,
width: box.width,
height: box.height
};
}
return results;
}
function getPosition(options, dlg) {
var windowSize = dom.getWindowSize();
var windowHeight = windowSize.innerHeight;
var windowWidth = windowSize.innerWidth;
var pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2;
}
pos.left += (pos.width || 0) / 2;
var height = dlg.offsetHeight || 300;
var width = dlg.offsetWidth || 160;
// Account for popup size
pos.top -= height / 2;
pos.left -= width / 2;
// Avoid showing too close to the bottom
var overflowX = pos.left + width - windowWidth;
var overflowY = pos.top + height - windowHeight;
if (overflowX > 0) {
pos.left -= (overflowX + 20);
}
if (overflowY > 0) {
pos.top -= (overflowY + 20);
}
pos.top += (options.offsetTop || 0);
pos.left += (options.offsetLeft || 0);
// Do some boundary checking
pos.top = Math.max(pos.top, 10);
pos.left = Math.max(pos.left, 10);
return pos;
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function show(options) {
// items
// positionTo
// showCancel
// title
var dialogOptions = {
removeOnClose: true,
enableHistory: options.enableHistory,
scrollY: false
};
var backButton = false;
var isFullscreen;
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
isFullscreen = true;
backButton = true;
dialogOptions.autoFocus = true;
} else {
dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation;
dialogOptions.entryAnimationDuration = options.entryAnimationDuration || 140;
dialogOptions.exitAnimationDuration = options.exitAnimationDuration || 100;
dialogOptions.autoFocus = false;
}
var dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen');
} else {
dlg.classList.add('actionsheet-not-fullscreen');
}
dlg.classList.add('actionSheet');
if (options.dialogClass) {
dlg.classList.add(options.dialogClass);
}
var html = '';
var scrollClassName = layoutManager.tv ? 'scrollY smoothScrollY hiddenScrollY' : 'scrollY';
var style = '';
// Admittedly a hack but right now the scrollbar is being factored into the width which is causing truncation
if (options.items.length > 20) {
var minWidth = dom.getWindowSize().innerWidth >= 300 ? 240 : 200;
style += 'min-width:' + minWidth + 'px;';
}
var i;
var length;
var option;
var renderIcon = false;
var icons = [];
var itemIcon;
for (i = 0, length = options.items.length; i < length; i++) {
option = options.items[i];
itemIcon = option.icon || (option.selected ? 'check' : null);
if (itemIcon) {
renderIcon = true;
}
icons.push(itemIcon || '');
}
if (layoutManager.tv) {
html += '<button is="paper-icon-button-light" class="btnCloseActionSheet hide-mouse-idle-tv" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
}
// If any items have an icon, give them all an icon just to make sure they're all lined up evenly
var center = options.title && (!renderIcon /*|| itemsWithIcons.length != options.items.length*/);
if (center || layoutManager.tv) {
html += '<div class="actionSheetContent actionSheetContent-centered">';
} else {
html += '<div class="actionSheetContent">';
}
if (options.title) {
html += '<h1 class="actionSheetTitle">';
html += options.title;
html += '</h1>';
}
if (options.text) {
html += '<p class="actionSheetText">';
html += options.text;
html += '</p>';
}
var scrollerClassName = 'actionSheetScroller';
if (layoutManager.tv) {
scrollerClassName += ' actionSheetScroller-tv focuscontainer-x focuscontainer-y';
}
html += '<div class="' + scrollerClassName + ' ' + scrollClassName + '" style="' + style + '">';
var menuItemClass = 'listItem listItem-button actionSheetMenuItem';
if (options.border || options.shaded) {
menuItemClass += ' listItem-border';
}
if (options.menuItemClass) {
menuItemClass += ' ' + options.menuItemClass;
}
if (layoutManager.tv) {
menuItemClass += ' listItem-focusscale';
}
if (layoutManager.mobile) {
menuItemClass += ' actionsheet-xlargeFont';
}
for (i = 0, length = options.items.length; i < length; i++) {
option = options.items[i];
if (option.divider) {
html += '<div class="actionsheetDivider"></div>';
continue;
}
var autoFocus = option.selected && layoutManager.tv ? ' autoFocus' : '';
// Check for null in case int 0 was passed in
var optionId = option.id == null || option.id === '' ? option.value : option.id;
html += '<button' + autoFocus + ' is="emby-button" type="button" class="' + menuItemClass + '" data-id="' + optionId + '">';
itemIcon = icons[i];
if (itemIcon) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ' + itemIcon + '"></span>';
} else if (renderIcon && !center) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
}
html += '<div class="listItemBody actionsheetListItemBody">';
html += '<div class="listItemBodyText actionSheetItemText">';
html += (option.name || option.textContent || option.innerText);
html += '</div>';
if (option.secondaryText) {
html += '<div class="listItemBodyText secondary">';
html += option.secondaryText;
html += '</div>';
}
html += '</div>';
if (option.asideText) {
html += '<div class="listItemAside actionSheetItemAsideText">';
html += option.asideText;
html += '</div>';
}
html += '</button>';
}
if (options.showCancel) {
html += '<div class="buttons">';
html += '<button is="emby-button" type="button" class="btnCloseActionSheet">' + globalize.translate('ButtonCancel') + '</button>';
html += '</div>';
}
html += '</div>';
dlg.innerHTML = html;
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
}
var btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) {
dlg.querySelector('.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';
var selectedId;
var timeout;
if (options.timeout) {
timeout = setTimeout(function () {
dialogHelper.close(dlg);
}, options.timeout);
}
return new Promise(function (resolve, reject) {
var isResolved;
dlg.addEventListener('click', function (e) {
var 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;
}
}
dialogHelper.close(dlg);
}
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (!isResolved) {
if (selectedId != null) {
if (options.callback) {
options.callback(selectedId);
}
resolve(selectedId);
} else {
reject();
}
}
});
dialogHelper.open(dlg);
var pos = options.positionTo && dialogOptions.size !== 'fullscreen' ? getPosition(options, dlg) : null;
if (pos) {
dlg.style.position = 'fixed';
dlg.style.margin = 0;
dlg.style.left = pos.left + 'px';
dlg.style.top = pos.top + 'px';
}
});
}
return {
show: show
};
});

View file

@ -1,13 +1,21 @@
define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings', 'serverNotifications', 'connectionManager', 'emby-button', 'listViewStyle'], function (events, globalize, dom, datefns, dfnshelper, userSettings, serverNotifications, connectionManager) { import events from 'events';
'use strict'; import globalize from 'globalize';
import dom from 'dom';
import * as datefns from 'date-fns';
import dfnshelper from 'dfnshelper';
import serverNotifications from 'serverNotifications';
import 'emby-button';
import 'listViewStyle';
/* eslint-disable indent */
function getEntryHtml(entry, apiClient) { function getEntryHtml(entry, apiClient) {
var html = ''; let html = '';
html += '<div class="listItem listItem-border">'; html += '<div class="listItem listItem-border">';
var color = '#00a4dc'; let color = '#00a4dc';
var icon = 'notifications'; let icon = 'notifications';
if ('Error' == entry.Severity || 'Fatal' == entry.Severity || 'Warn' == entry.Severity) { if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') {
color = '#cc0000'; color = '#cc0000';
icon = 'notification_important'; icon = 'notification_important';
} }
@ -34,10 +42,14 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
html += '</div>'; html += '</div>';
if (entry.Overview) { if (entry.Overview) {
html += '<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="' + entry.Id + '" title="' + globalize.translate('Info') + '"><span class="material-icons info"></span></button>'; html += `<button type="button" is="paper-icon-button-light" class="btnEntryInfo" data-id="${entry.Id}" title="${globalize.translate('Info')}">
<span class="material-icons info"></span>
</button>`;
} }
return html += '</div>'; html += '</div>';
return html;
} }
function renderList(elem, apiClient, result, startIndex, limit) { function renderList(elem, apiClient, result, startIndex, limit) {
@ -47,14 +59,15 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
} }
function reloadData(instance, elem, apiClient, startIndex, limit) { function reloadData(instance, elem, apiClient, startIndex, limit) {
if (null == startIndex) { if (startIndex == null) {
startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0'); startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0');
} }
limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7'); limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7');
var minDate = new Date(); const minDate = new Date();
var hasUserId = 'false' !== elem.getAttribute('data-useractivity'); const hasUserId = elem.getAttribute('data-useractivity') !== 'false';
// TODO: Use date-fns
if (hasUserId) { if (hasUserId) {
minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back
} else { } else {
@ -70,7 +83,7 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
elem.setAttribute('data-activitystartindex', startIndex); elem.setAttribute('data-activitystartindex', startIndex);
elem.setAttribute('data-activitylimit', limit); elem.setAttribute('data-activitylimit', limit);
if (!startIndex) { if (!startIndex) {
var activityContainer = dom.parentWithClass(elem, 'activityContainer'); const activityContainer = dom.parentWithClass(elem, 'activityContainer');
if (activityContainer) { if (activityContainer) {
if (result.Items.length) { if (result.Items.length) {
@ -87,7 +100,7 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
} }
function onActivityLogUpdate(e, apiClient, data) { function onActivityLogUpdate(e, apiClient, data) {
var options = this.options; const options = this.options;
if (options && options.serverId === apiClient.serverId()) { if (options && options.serverId === apiClient.serverId()) {
reloadData(this, options.element, apiClient); reloadData(this, options.element, apiClient);
@ -95,14 +108,14 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
} }
function onListClick(e) { function onListClick(e) {
var btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo');
if (btnEntryInfo) { if (btnEntryInfo) {
var id = btnEntryInfo.getAttribute('data-id'); const id = btnEntryInfo.getAttribute('data-id');
var items = this.items; const items = this.items;
if (items) { if (items) {
var item = items.filter(function (i) { const item = items.filter(function (i) {
return i.Id.toString() === id; return i.Id.toString() === id;
})[0]; })[0];
@ -114,35 +127,35 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
} }
function showItemOverview(item) { function showItemOverview(item) {
require(['alert'], function (alert) { import('alert').then(({default: alert}) => {
alert({ alert({
text: item.Overview text: item.Overview
}); });
}); });
} }
function ActivityLog(options) { class ActivityLog {
constructor(options) {
this.options = options; this.options = options;
var element = options.element; const element = options.element;
element.classList.add('activityLogListWidget'); element.classList.add('activityLogListWidget');
element.addEventListener('click', onListClick.bind(this)); element.addEventListener('click', onListClick.bind(this));
var apiClient = connectionManager.getApiClient(options.serverId); const apiClient = window.connectionManager.getApiClient(options.serverId);
reloadData(this, element, apiClient); reloadData(this, element, apiClient);
var onUpdate = onActivityLogUpdate.bind(this); const onUpdate = onActivityLogUpdate.bind(this);
this.updateFn = onUpdate; this.updateFn = onUpdate;
events.on(serverNotifications, 'ActivityLogEntry', onUpdate); events.on(serverNotifications, 'ActivityLogEntry', onUpdate);
apiClient.sendMessage('ActivityLogEntryStart', '0,1500'); apiClient.sendMessage('ActivityLogEntryStart', '0,1500');
} }
destroy() {
ActivityLog.prototype.destroy = function () { const options = this.options;
var options = this.options;
if (options) { if (options) {
options.element.classList.remove('activityLogListWidget'); options.element.classList.remove('activityLogListWidget');
connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500'); window.connectionManager.getApiClient(options.serverId).sendMessage('ActivityLogEntryStop', '0,1500');
} }
var onUpdate = this.updateFn; const onUpdate = this.updateFn;
if (onUpdate) { if (onUpdate) {
events.off(serverNotifications, 'ActivityLogEntry', onUpdate); events.off(serverNotifications, 'ActivityLogEntry', onUpdate);
@ -150,7 +163,9 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
this.items = null; this.items = null;
this.options = null; this.options = null;
}; }
}
return ActivityLog; export default ActivityLog;
});
/* eslint-enable indent */

View file

@ -1,14 +1,16 @@
define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize) { import browser from 'browser';
'use strict'; import dialog from 'dialog';
import globalize from 'globalize';
/* eslint-disable indent */
function replaceAll(originalString, strReplace, strWith) { function replaceAll(originalString, strReplace, strWith) {
var reg = new RegExp(strReplace, 'ig'); const reg = new RegExp(strReplace, 'ig');
return originalString.replace(reg, strWith); return originalString.replace(reg, strWith);
} }
return function (text, title) { export default function (text, title) {
let options;
var options;
if (typeof text === 'string') { if (typeof text === 'string') {
options = { options = {
title: title, title: title,
@ -21,7 +23,7 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
if (browser.tv && window.alert) { if (browser.tv && window.alert) {
alert(replaceAll(options.text || '', '<br/>', '\n')); alert(replaceAll(options.text || '', '<br/>', '\n'));
} else { } else {
var items = []; const items = [];
items.push({ items.push({
name: globalize.translate('ButtonGotIt'), name: globalize.translate('ButtonGotIt'),
@ -31,7 +33,7 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
options.buttons = items; options.buttons = items;
return dialog(options).then(function (result) { return dialog.show(options).then(function (result) {
if (result === 'ok') { if (result === 'ok') {
return Promise.resolve(); return Promise.resolve();
} }
@ -41,5 +43,6 @@ define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize)
} }
return Promise.resolve(); return Promise.resolve();
}; }
});
/* eslint-enable indent */

View file

@ -0,0 +1,313 @@
/* eslint-disable indent */
/**
* Module alphaPicker.
* @module components/alphaPicker/alphaPicker
*/
import focusManager from 'focusManager';
import layoutManager from 'layoutManager';
import dom from 'dom';
import 'css!./style.css';
import 'paper-icon-button-light';
import 'material-icons';
const selectedButtonClass = 'alphaPickerButton-selected';
function focus() {
const scope = this;
const selected = scope.querySelector(`.${selectedButtonClass}`);
if (selected) {
focusManager.focus(selected);
} else {
focusManager.autoFocus(scope, true);
}
}
function getAlphaPickerButtonClassName(vertical) {
let alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
}
if (vertical) {
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
}
return alphaPickerButtonClassName;
}
function getLetterButton(l, vertical) {
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
}
function mapLetters(letters, vertical) {
return letters.map(l => {
return getLetterButton(l, vertical);
});
}
function render(element, options) {
element.classList.add('alphaPicker');
if (layoutManager.tv) {
element.classList.add('alphaPicker-tv');
}
const vertical = element.classList.contains('alphaPicker-vertical');
if (!vertical) {
element.classList.add('focuscontainer-x');
}
let html = '';
let letters;
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
let rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += `<div class="${rowClassName}">`;
if (options.mode === 'keyboard') {
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
} else {
letters = ['#'];
html += mapLetters(letters, vertical).join('');
}
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
html += mapLetters(letters, vertical).join('');
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 += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
html += `<div class="${rowClassName}">`;
html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
} else {
html += '</div>';
}
element.innerHTML = html;
element.classList.add('focusable');
element.focus = focus;
}
export class AlphaPicker {
constructor(options) {
const self = this;
this.options = options;
const element = options.element;
const itemsContainer = options.itemsContainer;
const itemClass = options.itemClass;
let itemFocusValue;
let 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);
}
value(value, applyValue) {
const element = this.options.element;
let btn;
let selected;
if (value !== undefined) {
if (value != null) {
value = value.toUpperCase();
this._currentValue = value;
if (this.options.mode !== 'keyboard') {
selected = element.querySelector(`.${selectedButtonClass}`);
try {
btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`);
} catch (err) {
console.error('error in querySelector:', err);
}
if (btn && btn !== selected) {
btn.classList.add(selectedButtonClass);
}
if (selected && selected !== btn) {
selected.classList.remove(selectedButtonClass);
}
}
} else {
this._currentValue = value;
selected = element.querySelector(`.${selectedButtonClass}`);
if (selected) {
selected.classList.remove(selectedButtonClass);
}
}
}
if (applyValue) {
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
cancelable: false,
detail: {
value
}
}));
}
return this._currentValue;
}
on(name, fn) {
const element = this.options.element;
element.addEventListener(name, fn);
}
off(name, fn) {
const element = this.options.element;
element.removeEventListener(name, fn);
}
visible(visible) {
const element = this.options.element;
element.style.visibility = visible ? 'visible' : 'hidden';
}
values() {
const element = this.options.element;
const elems = element.querySelectorAll('.alphaPickerButton');
const values = [];
for (let i = 0, length = elems.length; i < length; i++) {
values.push(elems[i].getAttribute('data-value'));
}
return values;
}
focus() {
const element = this.options.element;
focusManager.autoFocus(element, true);
}
destroy() {
const element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
}
}
/* eslint-enable indent */
export default AlphaPicker;

View file

@ -1,321 +0,0 @@
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
'use strict';
var selectedButtonClass = 'alphaPickerButton-selected';
function focus() {
var scope = this;
var selected = scope.querySelector('.' + selectedButtonClass);
if (selected) {
focusManager.focus(selected);
} else {
focusManager.autoFocus(scope, true);
}
}
function getAlphaPickerButtonClassName(vertical) {
var alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
}
if (vertical) {
alphaPickerButtonClassName += ' alphaPickerButton-vertical';
}
return alphaPickerButtonClassName;
}
function getLetterButton(l, vertical) {
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + '</button>';
}
function mapLetters(letters, vertical) {
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');
}
var vertical = element.classList.contains('alphaPicker-vertical');
if (!vertical) {
element.classList.add('focuscontainer-x');
}
var html = '';
var letters;
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
var rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += '<div class="' + rowClassName + '">';
if (options.mode === 'keyboard') {
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>';
} else {
letters = ['#'];
html += mapLetters(letters, vertical).join('');
}
letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
html += mapLetters(letters, vertical).join('');
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 += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
html += '<div class="' + rowClassName + '">';
html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
} else {
html += '</div>';
}
element.innerHTML = html;
element.classList.add('focusable');
element.focus = focus;
}
function AlphaPicker(options) {
var self = this;
this.options = options;
var element = options.element;
var itemsContainer = options.itemsContainer;
var itemClass = options.itemClass;
var itemFocusValue;
var itemFocusTimeout;
function onItemFocusTimeout() {
itemFocusTimeout = null;
self.value(itemFocusValue);
}
var alphaFocusedElement;
var alphaFocusTimeout;
function onAlphaFocusTimeout() {
alphaFocusTimeout = null;
if (document.activeElement === alphaFocusedElement) {
var value = alphaFocusedElement.getAttribute('data-value');
self.value(value, true);
}
}
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
}
}));
}
}
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);
}
}
}
function onAlphaPickerFocusIn(e) {
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaPickerButton) {
alphaFocusedElement = alphaPickerButton;
alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600);
}
}
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);
}
}
}
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);
}
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;
});

View file

@ -1,17 +1,17 @@
define(['browser', 'css!./appfooter'], function (browser) { import 'css!./appFooter';
'use strict';
function render(options) { function render(options) {
var elem = document.createElement('div'); const elem = document.createElement('div');
elem.classList.add('appfooter'); elem.classList.add('appfooter');
document.body.appendChild(elem); document.body.appendChild(elem);
return elem; return elem;
} }
function appFooter(options) { class appFooter {
var self = this; constructor(options) {
const self = this;
self.element = render(options); self.element = render(options);
self.add = function (elem) { self.add = function (elem) {
@ -26,12 +26,11 @@ define(['browser', 'css!./appfooter'], function (browser) {
} }
}; };
} }
destroy() {
appFooter.prototype.destroy = function () { const self = this;
var self = this;
self.element = null; self.element = null;
}; }
}
return appFooter; export default appFooter;
});

File diff suppressed because it is too large Load diff

View file

@ -1,439 +1,410 @@
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) { import appSettings from 'appSettings';
'use strict'; import browser from 'browser';
import events from 'events';
import * as htmlMediaHelper from 'htmlMediaHelper';
import * as webSettings from 'webSettings';
import globalize from 'globalize';
function getBaseProfileOptions(item) { function getBaseProfileOptions(item) {
var disableHlsVideoAudioCodecs = []; const disableHlsVideoAudioCodecs = [];
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge || browser.msie) { if (browser.edge) {
disableHlsVideoAudioCodecs.push('mp3'); disableHlsVideoAudioCodecs.push('mp3');
}
disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push('opus');
} }
return { disableHlsVideoAudioCodecs.push('ac3');
enableMkvProgressive: false, disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs disableHlsVideoAudioCodecs.push('opus');
};
} }
function getDeviceProfileForWindowsUwp(item) { return {
return new Promise(function (resolve, reject) { enableMkvProgressive: false,
require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) { disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
var profileOptions = getBaseProfileOptions(item); };
profileOptions.supportsDts = uwpMediaCaps.supportsDTS(); }
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
profileOptions.audioChannels = uwpMediaCaps.getAudioChannels();
resolve(profileBuilder(profileOptions));
});
});
}
function getDeviceProfile(item, options) { function getDeviceProfile(item, options = {}) {
options = options || {}; return new Promise(function (resolve) {
import('browserdeviceprofile').then(({default: profileBuilder}) => {
let profile;
if (self.Windows) { if (window.NativeShell) {
return getDeviceProfileForWindowsUwp(item); profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
}
return new Promise(function (resolve) {
require(['browserdeviceprofile'], function (profileBuilder) {
var profile;
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);
}
resolve(profile);
});
});
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
function replaceAll(originalString, strReplace, strWith) {
var strReplace2 = escapeRegExp(strReplace);
var reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith);
}
function generateDeviceId() {
var keys = [];
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;
});
}
function getDeviceName() {
var deviceName;
deviceName = browser.tizen ? 'Samsung Smart TV' : browser.web0s ? 'LG Smart TV' : browser.operaTv ? 'Opera TV' : browser.xboxOne ? 'Xbox One' : browser.ps4 ? 'Sony PS4' : browser.chrome ? 'Chrome' : browser.edge ? 'Edge' : browser.firefox ? 'Firefox' : browser.msie ? 'Internet Explorer' : browser.opera ? 'Opera' : browser.safari ? 'Safari' : 'Web Browser';
if (browser.ipad) {
deviceName += ' iPad';
} else {
if (browser.iphone) {
deviceName += ' iPhone';
} else { } else {
if (browser.android) { const builderOpts = getBaseProfileOptions(item);
deviceName += ' Android'; builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats');
} profile = profileBuilder(builderOpts);
} }
}
return deviceName; resolve(profile);
});
});
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
}
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 Promise.resolve(result);
} }
function supportsVoiceInput() { return Promise.resolve(new Date().getTime());
if (!browser.tv) { }
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
}
function getDeviceId() {
const key = '_deviceId2';
const deviceId = appSettings.get(key);
if (deviceId) {
return Promise.resolve(deviceId);
}
return generateDeviceId().then(function (deviceId) {
appSettings.set(key, deviceId);
return deviceId;
});
}
function getDeviceName() {
let deviceName;
if (browser.tizen) {
deviceName = 'Samsung Smart TV';
} else if (browser.web0s) {
deviceName = 'LG Smart TV';
} else if (browser.operaTv) {
deviceName = 'Opera TV';
} else if (browser.xboxOne) {
deviceName = 'Xbox One';
} else if (browser.ps4) {
deviceName = 'Sony PS4';
} else if (browser.chrome) {
deviceName = 'Chrome';
} else if (browser.edgeChromium) {
deviceName = 'Edge Chromium';
} else if (browser.edge) {
deviceName = 'Edge';
} else if (browser.firefox) {
deviceName = 'Firefox';
} else if (browser.opera) {
deviceName = 'Opera';
} else if (browser.safari) {
deviceName = 'Safari';
} else {
deviceName = 'Web Browser';
}
if (browser.ipad) {
deviceName += ' iPad';
} else if (browser.iphone) {
deviceName += ' iPhone';
} else if (browser.android) {
deviceName += ' Android';
}
return deviceName;
}
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; return false;
} }
function supportsFullscreen() { const element = document.documentElement;
if (browser.tv) { return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
return false; }
}
var element = document.documentElement; function getDefaultLayout() {
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen; return 'desktop';
} }
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; return true;
} }
function supportsCue() { if (browser.mobile) {
try { return false;
var video = document.createElement('video');
var style = document.createElement('style');
style.textContent = 'video::cue {background: inherit}';
document.body.appendChild(style);
document.body.appendChild(video);
var 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() { return true;
if (isHidden) { }
isHidden = false;
console.debug('triggering app resume event'); function supportsCue() {
events.trigger(appHost, 'resume'); try {
} const video = document.createElement('video');
const style = document.createElement('style');
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');
} }
function onAppHidden() { if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
if (!isHidden) { features.push('filedownload');
isHidden = true;
console.debug('app is hidden');
}
} }
var supportedFeatures = function () { if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
var features = []; features.push('exit');
} else {
features.push('exitmenu');
features.push('plugins');
}
if (navigator.share) { if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push('sharing'); features.push('externallinks');
} features.push('externalpremium');
}
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) { if (!browser.operaTv) {
features.push('filedownload'); features.push('externallinkdisplay');
} }
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) { if (supportsVoiceInput()) {
features.push('exit'); 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 { } else {
features.push('exitmenu'); window.close();
features.push('plugins');
} }
} catch (err) {
console.error('error closing application: ' + err);
}
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) { let exitPromise;
features.push('externallinks');
features.push('externalpremium');
}
if (!browser.operaTv) { /**
features.push('externallinkdisplay'); * Ask user for exit
} */
function askForExit() {
if (supportsVoiceInput()) { if (exitPromise) {
features.push('voiceinput'); return;
}
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.msie && (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) {
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; import('actionsheet').then(({default: actionsheet}) => {
exitPromise = actionsheet.show({
/** title: globalize.translate('MessageConfirmAppExit'),
* Ask user for exit items: [
*/ {id: 'yes', name: globalize.translate('Yes')},
function askForExit() { {id: 'no', name: globalize.translate('No')}
if (exitPromise) { ]
return; }).then(function (value) {
} if (value === 'yes') {
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.0';
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(); doExit();
} }
}, }).finally(function () {
supports: function (command) { exitPromise = null;
if (window.NativeShell) { });
return window.NativeShell.AppHost.supports(command); });
} }
return -1 !== supportedFeatures.indexOf(command.toLowerCase()); let deviceId;
}, let deviceName;
preferVisualCards: browser.android || browser.chrome, const appName = 'Jellyfin Web';
moreIcon: browser.android ? 'more_vert' : 'more_horiz', const appVersion = '10.7.0';
getSyncProfile: getSyncProfile,
getDefaultLayout: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.getDefaultLayout();
}
return getDefaultLayout(); const appHost = {
}, getWindowState: function () {
getDeviceProfile: getDeviceProfile, return document.windowState || 'Normal';
init: function () { },
if (window.NativeShell) { setWindowState: function () {
return window.NativeShell.AppHost.init(); alert('setWindowState is not supported and should not be called');
} },
exit: function () {
deviceName = getDeviceName(); if (!!window.appMode && browser.tizen) {
getDeviceId().then(function (id) { askForExit();
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 { } else {
onAppVisible(); doExit();
}
},
supports: function (command) {
if (window.NativeShell) {
return window.NativeShell.AppHost.supports(command);
} }
}, false);
if (self.addEventListener) { return supportedFeatures.indexOf(command.toLowerCase()) !== -1;
self.addEventListener('focus', onAppVisible); },
self.addEventListener('blur', onAppHidden); 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();
}
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 {};
},
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);
}
} }
};
return appHost; 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);
}
export default appHost;

View file

@ -1,5 +1,10 @@
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) { import browser from 'browser';
'use strict'; import playbackManager from 'playbackManager';
import dom from 'dom';
import * as userSettings from 'userSettings';
import 'css!./backdrop';
/* eslint-disable indent */
function enableAnimation(elem) { function enableAnimation(elem) {
if (browser.slow) { if (browser.slow) {
@ -22,71 +27,70 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return true; return true;
} }
function Backdrop() { class Backdrop {
} load(url, parent, existingBackdropImage) {
const img = new Image();
const self = this;
Backdrop.prototype.load = function (url, parent, existingBackdropImage) { img.onload = () => {
var img = new Image(); if (self.isDestroyed) {
var self = this; return;
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 () { const backdropImage = document.createElement('div');
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { 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 once: true
}); });
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null; internalBackdrop(true);
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
}; };
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { img.src = url;
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 () { cancelAnimation() {
this.isDestroyed = true; const elem = this.currentAnimatingElement;
this.cancelAnimation(); if (elem) {
}; elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
}
}
var backdropContainer; destroy() {
this.isDestroyed = true;
this.cancelAnimation();
}
}
let backdropContainer;
function getBackdropContainer() { function getBackdropContainer() {
if (!backdropContainer) { if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer'); backdropContainer = document.querySelector('.backdropContainer');
@ -101,7 +105,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return backdropContainer; return backdropContainer;
} }
function clearBackdrop(clearAll) { export function clearBackdrop(clearAll) {
clearRotation(); clearRotation();
if (currentLoadingBackdrop) { if (currentLoadingBackdrop) {
@ -109,7 +113,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
currentLoadingBackdrop = null; currentLoadingBackdrop = null;
} }
var elem = getBackdropContainer(); const elem = getBackdropContainer();
elem.innerHTML = ''; elem.innerHTML = '';
if (clearAll) { if (clearAll) {
@ -119,7 +123,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
internalBackdrop(false); internalBackdrop(false);
} }
var backgroundContainer; let backgroundContainer;
function getBackgroundContainer() { function getBackgroundContainer() {
if (!backgroundContainer) { if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer'); backgroundContainer = document.querySelector('.backgroundContainer');
@ -135,31 +139,27 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
} }
} }
var hasInternalBackdrop; let hasInternalBackdrop;
function internalBackdrop(enabled) { function internalBackdrop(enabled) {
hasInternalBackdrop = enabled; hasInternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled(); setBackgroundContainerBackgroundEnabled();
} }
var hasExternalBackdrop; let hasExternalBackdrop;
function externalBackdrop(enabled) { export function externalBackdrop(enabled) {
hasExternalBackdrop = enabled; hasExternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled(); setBackgroundContainerBackgroundEnabled();
} }
function getRandom(min, max) { let currentLoadingBackdrop;
return Math.floor(Math.random() * (max - min) + min);
}
var currentLoadingBackdrop;
function setBackdropImage(url) { function setBackdropImage(url) {
if (currentLoadingBackdrop) { if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy(); currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null; currentLoadingBackdrop = null;
} }
var elem = getBackdropContainer(); const elem = getBackdropContainer();
var existingBackdropImage = elem.querySelector('.displayingBackdropImage'); const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) { if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) {
if (existingBackdropImage.getAttribute('data-url') === url) { if (existingBackdropImage.getAttribute('data-url') === url) {
@ -168,7 +168,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
existingBackdropImage.classList.remove('displayingBackdropImage'); existingBackdropImage.classList.remove('displayingBackdropImage');
} }
var instance = new Backdrop(); const instance = new Backdrop();
instance.load(url, elem, existingBackdropImage); instance.load(url, elem, existingBackdropImage);
currentLoadingBackdrop = instance; currentLoadingBackdrop = instance;
} }
@ -176,9 +176,9 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
function getItemImageUrls(item, imageOptions) { function getItemImageUrls(item, imageOptions) {
imageOptions = imageOptions || {}; imageOptions = imageOptions || {};
var apiClient = connectionManager.getApiClient(item.ServerId); const apiClient = window.connectionManager.getApiClient(item.ServerId);
if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { if (item.BackdropImageTags && item.BackdropImageTags.length > 0) {
return item.BackdropImageTags.map(function (imgTag, index) { return item.BackdropImageTags.map((imgTag, index) => {
return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, {
type: 'Backdrop', type: 'Backdrop',
tag: imgTag, tag: imgTag,
@ -189,7 +189,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
} }
if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) {
return item.ParentBackdropImageTags.map(function (imgTag, index) { return item.ParentBackdropImageTags.map((imgTag, index) => {
return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, {
type: 'Backdrop', type: 'Backdrop',
tag: imgTag, tag: imgTag,
@ -203,13 +203,13 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
} }
function getImageUrls(items, imageOptions) { function getImageUrls(items, imageOptions) {
var list = []; const list = [];
var onImg = function (img) { const onImg = img => {
list.push(img); list.push(img);
}; };
for (var i = 0, length = items.length; i < length; i++) { for (let i = 0, length = items.length; i < length; i++) {
var itemImages = getItemImageUrls(items[i], imageOptions); const itemImages = getItemImageUrls(items[i], imageOptions);
itemImages.forEach(onImg); itemImages.forEach(onImg);
} }
@ -229,7 +229,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
// If you don't care about the order of the elements inside // If you don't care about the order of the elements inside
// the array, you should sort both arrays here. // the array, you should sort both arrays here.
for (var i = 0; i < a.length; ++i) { for (let i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) { if (a[i] !== b[i]) {
return false; return false;
} }
@ -242,12 +242,12 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return userSettings.enableBackdrops(); return userSettings.enableBackdrops();
} }
var rotationInterval; let rotationInterval;
var currentRotatingImages = []; let currentRotatingImages = [];
var currentRotationIndex = -1; let currentRotationIndex = -1;
function setBackdrops(items, imageOptions, enableImageRotation) { export function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) { if (enabled()) {
var images = getImageUrls(items, imageOptions); const images = getImageUrls(items, imageOptions);
if (images.length) { if (images.length) {
startRotation(images, enableImageRotation); startRotation(images, enableImageRotation);
@ -279,7 +279,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return; return;
} }
var newIndex = currentRotationIndex + 1; let newIndex = currentRotationIndex + 1;
if (newIndex >= currentRotatingImages.length) { if (newIndex >= currentRotatingImages.length) {
newIndex = 0; newIndex = 0;
} }
@ -289,7 +289,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
} }
function clearRotation() { function clearRotation() {
var interval = rotationInterval; const interval = rotationInterval;
if (interval) { if (interval) {
clearInterval(interval); clearInterval(interval);
} }
@ -299,7 +299,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
currentRotationIndex = -1; currentRotationIndex = -1;
} }
function setBackdrop(url, imageOptions) { export function setBackdrop(url, imageOptions) {
if (url && typeof url !== 'string') { if (url && typeof url !== 'string') {
url = getImageUrls([url], imageOptions)[0]; url = getImageUrls([url], imageOptions)[0];
} }
@ -312,10 +312,11 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
} }
} }
return { /* eslint-enable indent */
setBackdrops: setBackdrops,
setBackdrop: setBackdrop, export default {
clear: clearBackdrop, setBackdrops: setBackdrops,
externalBackdrop: externalBackdrop setBackdrop: setBackdrop,
}; clearBackdrop: clearBackdrop,
}); externalBackdrop: externalBackdrop
};

View file

@ -1,56 +0,0 @@
define(['connectionManager'], function (connectionManager) {
return function () {
var self = this;
self.name = 'Backdrop ScreenSaver';
self.type = 'screensaver';
self.id = 'backdropscreensaver';
self.supportsAnonymous = false;
var currentSlideshow;
self.show = function () {
var query = {
ImageTypes: 'Backdrop',
EnableImageTypes: 'Backdrop',
IncludeItemTypes: 'Movie,Series,MusicArtist',
SortBy: 'Random',
Recursive: true,
Fields: 'Taglines',
ImageTypeLimit: 1,
StartIndex: 0,
Limit: 200
};
var apiClient = connectionManager.currentApiClient();
apiClient.getItems(apiClient.getCurrentUserId(), query).then(function (result) {
if (result.Items.length) {
require(['slideshow'], function (slideshow) {
var newSlideShow = new slideshow({
showTitle: true,
cover: true,
items: result.Items
});
newSlideShow.show();
currentSlideshow = newSlideShow;
});
}
});
};
self.hide = function () {
if (currentSlideshow) {
currentSlideshow.hide();
currentSlideshow = null;
}
};
};
});

View file

@ -167,8 +167,9 @@ button::-moz-focus-inner {
position: relative; position: relative;
background-clip: content-box !important; background-clip: content-box !important;
color: inherit; color: inherit;
}
/* This is only needed for scalable cards */ .cardScalable .cardImageContainer {
height: 100%; height: 100%;
contain: strict; contain: strict;
} }
@ -192,9 +193,14 @@ button::-moz-focus-inner {
/* Needed in case this is a button */ /* Needed in case this is a button */
display: block; display: block;
/* Needed in case this is a button */
margin: 0 !important; margin: 0 !important;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
font-family: inherit;
font-size: inherit;
/* Needed in safari */ /* Needed in safari */
height: 100%; height: 100%;
@ -203,27 +209,25 @@ button::-moz-focus-inner {
contain: strict; contain: strict;
} }
.cardContent-button { .defaultCardBackground {
border: 0 !important; display: flex;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
} }
.cardContent-button:not(.defaultCardBackground) { .cardContent:not(.defaultCardBackground) {
background-color: transparent; background-color: transparent;
} }
.cardBox:not(.visualCardBox) .cardPadder {
background-color: #242424;
}
.visualCardBox .cardContent { .visualCardBox .cardContent {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.cardContent-shadow { .cardContent-shadow,
.cardBox:not(.visualCardBox) .cardPadder {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37); box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
} }
@ -239,33 +243,13 @@ button::-moz-focus-inner {
border: none; 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 { .coveredImage {
background-size: cover; background-size: cover;
background-position: center center; background-position: center center;
} }
.coveredImage-noScale { .coveredImage.coveredImage-contain {
background-size: cover; background-size: contain;
} }
.cardFooter { .cardFooter {
@ -306,6 +290,10 @@ button::-moz-focus-inner {
text-align: left; text-align: left;
} }
.dialog .cardText {
text-overflow: initial;
}
.cardText-secondary { .cardText-secondary {
font-size: 86%; font-size: 86%;
} }
@ -368,6 +356,8 @@ button::-moz-focus-inner {
.cardDefaultText { .cardDefaultText {
white-space: normal; white-space: normal;
text-align: center; text-align: center;
font-size: 2em;
font-weight: bold;
} }
.cardImageContainer .cardImageIcon { .cardImageContainer .cardImageIcon {

View file

@ -7,7 +7,6 @@
import datetime from 'datetime'; import datetime from 'datetime';
import imageLoader from 'imageLoader'; import imageLoader from 'imageLoader';
import connectionManager from 'connectionManager';
import itemHelper from 'itemHelper'; import itemHelper from 'itemHelper';
import focusManager from 'focusManager'; import focusManager from 'focusManager';
import indicators from 'indicators'; import indicators from 'indicators';
@ -277,7 +276,7 @@ import 'programStyles';
*/ */
function getImageWidth(shape, screenWidth, isOrientationLandscape) { function getImageWidth(shape, screenWidth, isOrientationLandscape) {
const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape);
return Math.round(screenWidth / imagesPerRow) * 2; return Math.round(screenWidth / imagesPerRow);
} }
/** /**
@ -291,12 +290,10 @@ import 'programStyles';
const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items);
if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) {
const requestedShape = options.shape; const requestedShape = options.shape;
options.shape = null; options.shape = null;
if (primaryImageAspectRatio) { if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 3) { if (primaryImageAspectRatio >= 3) {
options.shape = 'banner'; options.shape = 'banner';
options.coverImage = true; options.coverImage = true;
@ -364,18 +361,16 @@ import 'programStyles';
let hasOpenRow; let hasOpenRow;
let hasOpenSection; let hasOpenSection;
let sectionTitleTagName = options.sectionTitleTagName || 'div'; const sectionTitleTagName = options.sectionTitleTagName || 'div';
let apiClient; let apiClient;
let lastServerId; let lastServerId;
for (let i = 0; i < items.length; i++) { for (const [i, item] of items.entries()) {
const serverId = item.ServerId || options.serverId;
let item = items[i];
let serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId) { if (serverId !== lastServerId) {
lastServerId = serverId; lastServerId = serverId;
apiClient = connectionManager.getApiClient(lastServerId); apiClient = window.connectionManager.getApiClient(lastServerId);
} }
if (options.indexBy) { if (options.indexBy) {
@ -396,7 +391,6 @@ import 'programStyles';
} }
if (newIndexValue !== currentIndexValue) { if (newIndexValue !== currentIndexValue) {
if (hasOpenRow) { if (hasOpenRow) {
html += '</div>'; html += '</div>';
hasOpenRow = false; hasOpenRow = false;
@ -404,7 +398,6 @@ import 'programStyles';
} }
if (hasOpenSection) { if (hasOpenSection) {
html += '</div>'; html += '</div>';
if (isVertical) { if (isVertical) {
@ -428,7 +421,6 @@ import 'programStyles';
} }
if (options.rows && itemsInRow === 0) { if (options.rows && itemsInRow === 0) {
if (hasOpenRow) { if (hasOpenRow) {
html += '</div>'; html += '</div>';
hasOpenRow = false; hasOpenRow = false;
@ -503,94 +495,49 @@ import 'programStyles';
const primaryImageAspectRatio = item.PrimaryImageAspectRatio; const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
let forceName = false; let forceName = false;
let imgUrl = null; let imgUrl = null;
let imgTag = null;
let coverImage = false; let coverImage = false;
let uiAspect = null; let uiAspect = null;
let imgType = null;
let itemId = null;
if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Thumb;
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
} else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) {
imgType = 'Banner';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Banner;
type: 'Banner',
maxWidth: width,
tag: item.ImageTags.Banner
});
} else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) {
imgType = 'Disc';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Disc;
type: 'Disc',
maxWidth: width,
tag: item.ImageTags.Disc
});
} else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) {
imgType = 'Logo';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Logo;
type: 'Logo',
maxWidth: width,
tag: item.ImageTags.Logo
});
} else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) {
imgType = 'Logo';
imgUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { imgTag = item.ParentLogoImageTag;
type: 'Logo', itemId = item.ParentLogoItemId;
maxWidth: width,
tag: item.ParentLogoImageTag
});
} else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, { imgTag = item.SeriesThumbImageTag;
type: 'Thumb', itemId = item.SeriesId;
maxWidth: width,
tag: item.SeriesThumbImageTag
});
} else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, { imgTag = item.ParentThumbImageTag;
type: 'Thumb', itemId = item.ParentThumbItemId;
maxWidth: width,
tag: item.ParentThumbImageTag
});
} else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) {
imgType = 'Backdrop';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.BackdropImageTags[0];
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
forceName = true; forceName = true;
} else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') {
imgType = 'Backdrop';
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { imgTag = item.ParentBackdropImageTags[0];
type: 'Backdrop', itemId = item.ParentBackdropItemId;
maxWidth: width, } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) {
tag: item.ParentBackdropImageTags[0] imgType = 'Primary';
}); imgTag = item.ImageTags.Primary;
} else if (item.ImageTags && item.ImageTags.Primary) {
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.ImageTags.Primary
});
if (options.preferThumb && options.showTitle !== false) { if (options.preferThumb && options.showTitle !== false) {
forceName = true; forceName = true;
} }
@ -601,18 +548,16 @@ import 'programStyles';
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
} }
} }
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.PrimaryImageTag) { } else if (item.PrimaryImageTag) {
imgType = 'Primary';
imgTag = item.PrimaryImageTag;
itemId = item.PrimaryImageItemId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.PrimaryImageTag
});
if (options.preferThumb && options.showTitle !== false) { if (options.preferThumb && options.showTitle !== false) {
forceName = true; forceName = true;
} }
@ -624,30 +569,15 @@ import 'programStyles';
} }
} }
} else if (item.ParentPrimaryImageTag) { } else if (item.ParentPrimaryImageTag) {
imgType = 'Primary';
imgUrl = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, { imgTag = item.ParentPrimaryImageTag;
type: 'Primary', itemId = item.ParentPrimaryImageItemId;
maxWidth: width,
tag: item.ParentPrimaryImageTag
});
} else if (item.SeriesPrimaryImageTag) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Primary',
maxWidth: width,
tag: item.SeriesPrimaryImageTag
});
} else if (item.AlbumId && item.AlbumPrimaryImageTag) { } else if (item.AlbumId && item.AlbumPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.AlbumId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.AlbumPrimaryImageTag
});
if (primaryImageAspectRatio) { if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape); uiAspect = getDesiredAspect(shape);
if (uiAspect) { if (uiAspect) {
@ -655,57 +585,46 @@ import 'programStyles';
} }
} }
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Thumb;
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
} else if (item.BackdropImageTags && item.BackdropImageTags.length) { } else if (item.BackdropImageTags && item.BackdropImageTags.length) {
imgType = 'Backdrop';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.BackdropImageTags[0];
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
} else if (item.ImageTags && item.ImageTags.Thumb) { } else if (item.ImageTags && item.ImageTags.Thumb) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.Id, { imgTag = item.ImageTags.Thumb;
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
} else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, { imgTag = item.SeriesThumbImageTag;
type: 'Thumb', itemId = item.SeriesId;
maxWidth: width,
tag: item.SeriesThumbImageTag
});
} else if (item.ParentThumbItemId && options.inheritThumb !== false) { } else if (item.ParentThumbItemId && options.inheritThumb !== false) {
imgType = 'Thumb';
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, { imgTag = item.ParentThumbImageTag;
type: 'Thumb', itemId = item.ParentThumbItemId;
maxWidth: width,
tag: item.ParentThumbImageTag
});
} else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) {
imgType = 'Backdrop';
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, { imgTag = item.ParentBackdropImageTags[0];
type: 'Backdrop', itemId = item.ParentBackdropItemId;
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
});
} }
if (!itemId) {
itemId = item.Id;
}
if (imgTag && imgType) {
imgUrl = apiClient.getScaledImageUrl(itemId, {
type: imgType,
maxHeight: height,
maxWidth: width,
tag: imgTag
});
}
const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
return { return {
imgUrl: imgUrl, imgUrl: imgUrl,
blurhash: (blurHashes[imgType] || {})[imgTag],
forceName: forceName, forceName: forceName,
coverImage: coverImage coverImage: coverImage
}; };
@ -736,7 +655,7 @@ import 'programStyles';
for (let i = 0; i < character.length; i++) { for (let i = 0; i < character.length; i++) {
sum += parseInt(character.charAt(i)); sum += parseInt(character.charAt(i));
} }
let index = String(sum).substr(-1); const index = String(sum).substr(-1);
return (index % numRandomColors) + 1; return (index % numRandomColors) + 1;
} else { } else {
@ -761,9 +680,8 @@ import 'programStyles';
let valid = 0; let valid = 0;
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
let currentCssClass = cssClass; let currentCssClass = cssClass;
let text = lines[i]; const text = lines[i];
if (valid > 0 && isOuterFooter) { if (valid > 0 && isOuterFooter) {
currentCssClass += ' cardText-secondary'; currentCssClass += ' cardText-secondary';
@ -788,8 +706,7 @@ import 'programStyles';
} }
if (forceLines) { 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) { while (valid < linesLength) {
html += "<div class='" + cssClass + "'>&nbsp;</div>"; html += "<div class='" + cssClass + "'>&nbsp;</div>";
@ -820,7 +737,6 @@ import 'programStyles';
let airTimeText = ''; let airTimeText = '';
if (item.StartDate) { if (item.StartDate) {
try { try {
let date = datetime.parseISO8601Date(item.StartDate); let date = datetime.parseISO8601Date(item.StartDate);
@ -867,9 +783,8 @@ import 'programStyles';
const showOtherText = isOuterFooter ? !overlayText : overlayText; const showOtherText = isOuterFooter ? !overlayText : overlayText;
if (isOuterFooter && options.cardLayout && layoutManager.mobile) { if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
if (options.cardFooterAside !== 'none') { if (options.cardFooterAside !== 'none') {
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_horiz"></span></button>'; html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
} }
} }
@ -882,9 +797,7 @@ import 'programStyles';
if (showOtherText) { if (showOtherText) {
if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) { if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (item.SeriesId) { if (item.SeriesId) {
lines.push(getTextActionButton({ lines.push(getTextActionButton({
Id: item.SeriesId, Id: item.SeriesId,
@ -897,15 +810,12 @@ import 'programStyles';
lines.push(item.SeriesName); lines.push(item.SeriesName);
} }
} else { } else {
if (isUsingLiveTvNaming(item)) { if (isUsingLiveTvNaming(item)) {
lines.push(item.Name); lines.push(item.Name);
if (!item.EpisodeTitle) { if (!item.EpisodeTitle) {
titleAdded = true; titleAdded = true;
} }
} else { } else {
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
@ -923,7 +833,6 @@ import 'programStyles';
} }
if (showMediaTitle) { if (showMediaTitle) {
const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, { const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle includeParentInfo: options.includeParentInfoInTitle
}); });
@ -940,7 +849,6 @@ import 'programStyles';
if (showOtherText) { if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) { if (options.showParentTitle && parentTitleUnderneath) {
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
item.AlbumArtists[0].Type = 'MusicArtist'; item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true; item.AlbumArtists[0].IsFolder = true;
@ -974,7 +882,6 @@ import 'programStyles';
} }
if (options.showPremiereDate) { if (options.showPremiereDate) {
if (item.PremiereDate) { if (item.PremiereDate) {
try { try {
lines.push(datetime.toLocaleDateString( lines.push(datetime.toLocaleDateString(
@ -983,7 +890,6 @@ import 'programStyles';
)); ));
} catch (err) { } catch (err) {
lines.push(''); lines.push('');
} }
} else { } else {
lines.push(''); lines.push('');
@ -991,14 +897,10 @@ import 'programStyles';
} }
if (options.showYear || options.showSeriesYear) { if (options.showYear || options.showSeriesYear) {
if (item.Type === 'Series') { if (item.Type === 'Series') {
if (item.Status === 'Continuing') { if (item.Status === 'Continuing') {
lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || '')); lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || ''));
} else { } else {
if (item.EndDate && item.ProductionYear) { if (item.EndDate && item.ProductionYear) {
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear(); const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear();
lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
@ -1012,9 +914,7 @@ import 'programStyles';
} }
if (options.showRuntime) { if (options.showRuntime) {
if (item.RunTimeTicks) { if (item.RunTimeTicks) {
lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks));
} else { } else {
lines.push(''); lines.push('');
@ -1022,14 +922,11 @@ import 'programStyles';
} }
if (options.showAirTime) { if (options.showAirTime) {
lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ''); lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || '');
} }
if (options.showChannelName) { if (options.showChannelName) {
if (item.ChannelId) { if (item.ChannelId) {
lines.push(getTextActionButton({ lines.push(getTextActionButton({
Id: item.ChannelId, Id: item.ChannelId,
@ -1046,7 +943,6 @@ import 'programStyles';
} }
if (options.showCurrentProgram && item.Type === 'TvChannel') { if (options.showCurrentProgram && item.Type === 'TvChannel') {
if (item.CurrentProgram) { if (item.CurrentProgram) {
lines.push(item.CurrentProgram.Name); lines.push(item.CurrentProgram.Name);
} else { } else {
@ -1055,7 +951,6 @@ import 'programStyles';
} }
if (options.showCurrentProgramTime && item.Type === 'TvChannel') { if (options.showCurrentProgramTime && item.Type === 'TvChannel') {
if (item.CurrentProgram) { if (item.CurrentProgram) {
lines.push(getAirTimeText(item.CurrentProgram, false, true) || ''); lines.push(getAirTimeText(item.CurrentProgram, false, true) || '');
} else { } else {
@ -1065,7 +960,6 @@ import 'programStyles';
if (options.showSeriesTimerTime) { if (options.showSeriesTimerTime) {
if (item.RecordAnyTime) { if (item.RecordAnyTime) {
lines.push(globalize.translate('Anytime')); lines.push(globalize.translate('Anytime'));
} else { } else {
lines.push(datetime.getDisplayTime(item.StartDate)); lines.push(datetime.getDisplayTime(item.StartDate));
@ -1091,6 +985,10 @@ import 'programStyles';
lines = []; lines = [];
} }
if (overlayText && showTitle) {
lines = [item.Name];
}
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
@ -1100,7 +998,6 @@ import 'programStyles';
} }
if (html) { if (html) {
if (!isOuterFooter || logoUrl || options.cardLayout) { if (!isOuterFooter || logoUrl || options.cardLayout) {
html = '<div class="' + footerClass + '">' + html; html = '<div class="' + footerClass + '">' + html;
@ -1142,31 +1039,25 @@ import 'programStyles';
* @returns {string} HTML markup for the item count indicator. * @returns {string} HTML markup for the item count indicator.
*/ */
function getItemCountsHtml(options, item) { function getItemCountsHtml(options, item) {
let counts = []; const counts = [];
let childText; let childText;
if (item.Type === 'Playlist') { if (item.Type === 'Playlist') {
childText = ''; childText = '';
if (item.RunTimeTicks) { if (item.RunTimeTicks) {
let minutes = item.RunTimeTicks / 600000000; let minutes = item.RunTimeTicks / 600000000;
minutes = minutes || 1; minutes = minutes || 1;
childText += globalize.translate('ValueMinutes', Math.round(minutes)); childText += globalize.translate('ValueMinutes', Math.round(minutes));
} else { } else {
childText += globalize.translate('ValueMinutes', 0); childText += globalize.translate('ValueMinutes', 0);
} }
counts.push(childText); counts.push(childText);
} else if (item.Type === 'Genre' || item.Type === 'Studio') { } else if (item.Type === 'Genre' || item.Type === 'Studio') {
if (item.MovieCount) { if (item.MovieCount) {
childText = item.MovieCount === 1 ? childText = item.MovieCount === 1 ?
globalize.translate('ValueOneMovie') : globalize.translate('ValueOneMovie') :
globalize.translate('ValueMovieCount', item.MovieCount); globalize.translate('ValueMovieCount', item.MovieCount);
@ -1175,7 +1066,6 @@ import 'programStyles';
} }
if (item.SeriesCount) { if (item.SeriesCount) {
childText = item.SeriesCount === 1 ? childText = item.SeriesCount === 1 ?
globalize.translate('ValueOneSeries') : globalize.translate('ValueOneSeries') :
globalize.translate('ValueSeriesCount', item.SeriesCount); globalize.translate('ValueSeriesCount', item.SeriesCount);
@ -1183,18 +1073,14 @@ import 'programStyles';
counts.push(childText); counts.push(childText);
} }
if (item.EpisodeCount) { if (item.EpisodeCount) {
childText = item.EpisodeCount === 1 ? childText = item.EpisodeCount === 1 ?
globalize.translate('ValueOneEpisode') : globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.EpisodeCount); globalize.translate('ValueEpisodeCount', item.EpisodeCount);
counts.push(childText); counts.push(childText);
} }
} else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') {
if (item.AlbumCount) { if (item.AlbumCount) {
childText = item.AlbumCount === 1 ? childText = item.AlbumCount === 1 ?
globalize.translate('ValueOneAlbum') : globalize.translate('ValueOneAlbum') :
globalize.translate('ValueAlbumCount', item.AlbumCount); globalize.translate('ValueAlbumCount', item.AlbumCount);
@ -1202,7 +1088,6 @@ import 'programStyles';
counts.push(childText); counts.push(childText);
} }
if (item.SongCount) { if (item.SongCount) {
childText = item.SongCount === 1 ? childText = item.SongCount === 1 ?
globalize.translate('ValueOneSong') : globalize.translate('ValueOneSong') :
globalize.translate('ValueSongCount', item.SongCount); globalize.translate('ValueSongCount', item.SongCount);
@ -1210,16 +1095,13 @@ import 'programStyles';
counts.push(childText); counts.push(childText);
} }
if (item.MusicVideoCount) { if (item.MusicVideoCount) {
childText = item.MusicVideoCount === 1 ? childText = item.MusicVideoCount === 1 ?
globalize.translate('ValueOneMusicVideo') : globalize.translate('ValueOneMusicVideo') :
globalize.translate('ValueMusicVideoCount', item.MusicVideoCount); globalize.translate('ValueMusicVideoCount', item.MusicVideoCount);
counts.push(childText); counts.push(childText);
} }
} else if (item.Type === 'Series') { } else if (item.Type === 'Series') {
childText = item.RecursiveItemCount === 1 ? childText = item.RecursiveItemCount === 1 ?
globalize.translate('ValueOneEpisode') : globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.RecursiveItemCount); globalize.translate('ValueEpisodeCount', item.RecursiveItemCount);
@ -1235,10 +1117,11 @@ import 'programStyles';
/** /**
* Imports the refresh indicator element. * Imports the refresh indicator element.
*/ */
function requireRefreshIndicator() { function importRefreshIndicator() {
if (!refreshIndicatorLoaded) { if (!refreshIndicatorLoaded) {
refreshIndicatorLoaded = true; refreshIndicatorLoaded = true;
require(['emby-itemrefreshindicator']); /* eslint-disable-next-line @babel/no-unused-expressions */
import('emby-itemrefreshindicator');
} }
} }
@ -1272,13 +1155,11 @@ import 'programStyles';
let shape = options.shape; let shape = options.shape;
if (shape === 'mixed') { if (shape === 'mixed') {
shape = null; shape = null;
const primaryImageAspectRatio = item.PrimaryImageAspectRatio; const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
if (primaryImageAspectRatio) { if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 1.33) { if (primaryImageAspectRatio >= 1.33) {
shape = 'mixedBackdrop'; shape = 'mixedBackdrop';
} else if (primaryImageAspectRatio > 0.71) { } else if (primaryImageAspectRatio > 0.71) {
@ -1321,6 +1202,7 @@ import 'programStyles';
const imgInfo = getCardImageUrl(item, apiClient, options, shape); const imgInfo = getCardImageUrl(item, apiClient, options, shape);
const imgUrl = imgInfo.imgUrl; const imgUrl = imgInfo.imgUrl;
const blurhash = imgInfo.blurhash;
const forceName = imgInfo.forceName; const forceName = imgInfo.forceName;
@ -1333,8 +1215,8 @@ import 'programStyles';
if (coveredImage) { if (coveredImage) {
cardImageContainerClass += ' coveredImage'; cardImageContainerClass += ' coveredImage';
if (item.MediaType === 'Photo' || item.Type === 'PhotoAlbum' || item.Type === 'Folder' || item.ProgramInfo || item.Type === 'Program' || item.Type === 'Recording') { if (item.Type === 'TvChannel') {
cardImageContainerClass += ' coveredImage-noScale'; cardImageContainerClass += ' coveredImage-contain';
} }
} }
@ -1369,7 +1251,6 @@ import 'programStyles';
} }
if (overlayText) { if (overlayText) {
logoUrl = null; logoUrl = null;
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
@ -1384,7 +1265,7 @@ import 'programStyles';
} }
const mediaSourceCount = item.MediaSourceCount || 1; const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1) { if (mediaSourceCount > 1 && options.disableIndicators !== true) {
innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>'; innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
} }
@ -1426,7 +1307,7 @@ import 'programStyles';
} }
if (options.overlayMoreButton) { if (options.overlayMoreButton) {
overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_horiz"></span></button>'; overlayButtons += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon more_vert"></span></button>';
} }
} }
@ -1440,58 +1321,60 @@ import 'programStyles';
let cardBoxClose = ''; let cardBoxClose = '';
let cardScalableClose = ''; let cardScalableClose = '';
let cardContentClass = 'cardContent'; const cardContentClass = 'cardContent';
if (!options.cardLayout) {
cardContentClass += ' cardContent-shadow'; let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
blurhashAttrib = 'data-blurhash="' + blurhash + '"';
} }
if (layoutManager.tv) { if (layoutManager.tv) {
// Don't use the IMG tag with safari because it puts a white border around it // Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">'); cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
cardImageContainerClose = '</div>'; cardImageContainerClose = '</div>';
} else { } else {
// Don't use the IMG tag with safari because it puts a white border around it // Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '">') : ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">'); cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerClose = '</button>'; cardImageContainerClose = '</button>';
} }
let cardScalableClass = 'cardScalable'; const cardScalableClass = 'cardScalable';
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainerOpen; cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>'; cardBoxClose = '</div>';
cardScalableClose = '</div>'; cardScalableClose = '</div>';
let indicatorsHtml = ''; if (options.disableIndicators !== true) {
let indicatorsHtml = '';
if (options.missingIndicator !== false) { if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item); indicatorsHtml += indicators.getMissingIndicator(item);
} }
indicatorsHtml += indicators.getSyncIndicator(item); indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item); indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item); indicatorsHtml += indicators.getTypeIndicator(item);
if (options.showGroupCount) { if (options.showGroupCount) {
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
} else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
}
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { if (item.Type === 'CollectionFolder' || item.CollectionType) {
minCount: 1 const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
}); indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
} else { importRefreshIndicator();
indicatorsHtml += indicators.getPlayedIndicatorHtml(item); }
}
if (item.Type === 'CollectionFolder' || item.CollectionType) { if (indicatorsHtml) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>'; }
requireRefreshIndicator();
}
if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
} }
if (!imgUrl) { if (!imgUrl) {
@ -1539,8 +1422,8 @@ import 'programStyles';
let additionalCardContent = ''; let additionalCardContent = '';
if (layoutManager.desktop) { 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 + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' 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 + '>';
@ -1550,9 +1433,10 @@ import 'programStyles';
* Generates HTML markup for the card overlay. * Generates HTML markup for the card overlay.
* @param {object} item - Item used to generate the card overlay. * @param {object} item - Item used to generate the card overlay.
* @param {string} action - Action assigned to the overlay. * @param {string} action - Action assigned to the overlay.
* @param {Array} options - Card builder options.
* @returns {string} HTML markup of the card overlay. * @returns {string} HTML markup of the card overlay.
*/ */
function getHoverMenuHtml(item, action) { function getHoverMenuHtml(item, action, options) {
let html = ''; let html = '';
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">'; html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
@ -1568,20 +1452,20 @@ import 'programStyles';
const userData = item.UserData || {}; const userData = item.UserData || {};
if (itemHelper.canMarkPlayed(item)) { if (itemHelper.canMarkPlayed(item)) {
require(['emby-playstatebutton']); /* eslint-disable-next-line @babel/no-unused-expressions */
import('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>'; 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)) { if (itemHelper.canRate(item)) {
const likes = userData.Likes == null ? '' : userData.Likes; const likes = userData.Likes == null ? '' : userData.Likes;
require(['emby-ratingbutton']); /* eslint-disable-next-line @babel/no-unused-expressions */
import('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>'; 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>';
} }
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_horiz"></span></button>'; html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
html += '</div>'; html += '</div>';
html += '</div>'; html += '</div>';
@ -1605,6 +1489,8 @@ import 'programStyles';
case 'MusicArtist': case 'MusicArtist':
case 'Person': case 'Person':
return '<span class="cardImageIcon material-icons person"></span>'; return '<span class="cardImageIcon material-icons person"></span>';
case 'Audio':
return '<span class="cardImageIcon material-icons audiotrack"></span>';
case 'Movie': case 'Movie':
return '<span class="cardImageIcon material-icons movie"></span>'; return '<span class="cardImageIcon material-icons movie"></span>';
case 'Series': case 'Series':
@ -1613,6 +1499,12 @@ import 'programStyles';
return '<span class="cardImageIcon material-icons book"></span>'; return '<span class="cardImageIcon material-icons book"></span>';
case 'Folder': case 'Folder':
return '<span class="cardImageIcon material-icons folder"></span>'; return '<span class="cardImageIcon material-icons folder"></span>';
case 'BoxSet':
return '<span class="cardImageIcon material-icons collections"></span>';
case 'Playlist':
return '<span class="cardImageIcon material-icons view_list"></span>';
case 'PhotoAlbum':
return '<span class="cardImageIcon material-icons photo_album"></span>';
} }
if (options && options.defaultCardImageIcon) { if (options && options.defaultCardImageIcon) {
@ -1646,7 +1538,6 @@ import 'programStyles';
const html = buildCardsHtmlInternal(items, options); const html = buildCardsHtmlInternal(items, options);
if (html) { if (html) {
if (options.itemsContainer.cardBuilderHtml !== html) { if (options.itemsContainer.cardBuilderHtml !== html) {
options.itemsContainer.innerHTML = html; options.itemsContainer.innerHTML = html;
@ -1659,7 +1550,6 @@ import 'programStyles';
imageLoader.lazyChildren(options.itemsContainer); imageLoader.lazyChildren(options.itemsContainer);
} else { } else {
options.itemsContainer.innerHTML = html; options.itemsContainer.innerHTML = html;
options.itemsContainer.cardBuilderHtml = null; options.itemsContainer.cardBuilderHtml = null;
} }
@ -1683,7 +1573,6 @@ import 'programStyles';
indicatorsElem = card.querySelector('.cardIndicators'); indicatorsElem = card.querySelector('.cardIndicators');
if (!indicatorsElem) { if (!indicatorsElem) {
const cardImageContainer = card.querySelector('.cardImageContainer'); const cardImageContainer = card.querySelector('.cardImageContainer');
indicatorsElem = document.createElement('div'); indicatorsElem = document.createElement('div');
indicatorsElem.classList.add('cardIndicators'); indicatorsElem.classList.add('cardIndicators');
@ -1707,11 +1596,9 @@ import 'programStyles';
let itemProgressBar = null; let itemProgressBar = null;
if (userData.Played) { if (userData.Played) {
playedIndicator = card.querySelector('.playedIndicator'); playedIndicator = card.querySelector('.playedIndicator');
if (!playedIndicator) { if (!playedIndicator) {
playedIndicator = document.createElement('div'); playedIndicator = document.createElement('div');
playedIndicator.classList.add('playedIndicator'); playedIndicator.classList.add('playedIndicator');
playedIndicator.classList.add('indicator'); playedIndicator.classList.add('indicator');
@ -1720,10 +1607,8 @@ import 'programStyles';
} }
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>'; playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
} else { } else {
playedIndicator = card.querySelector('.playedIndicator'); playedIndicator = card.querySelector('.playedIndicator');
if (playedIndicator) { if (playedIndicator) {
playedIndicator.parentNode.removeChild(playedIndicator); playedIndicator.parentNode.removeChild(playedIndicator);
} }
} }
@ -1731,7 +1616,6 @@ import 'programStyles';
countIndicator = card.querySelector('.countIndicator'); countIndicator = card.querySelector('.countIndicator');
if (!countIndicator) { if (!countIndicator) {
countIndicator = document.createElement('div'); countIndicator = document.createElement('div');
countIndicator.classList.add('countIndicator'); countIndicator.classList.add('countIndicator');
indicatorsElem = ensureIndicators(card, indicatorsElem); indicatorsElem = ensureIndicators(card, indicatorsElem);
@ -1739,10 +1623,8 @@ import 'programStyles';
} }
countIndicator.innerHTML = userData.UnplayedItemCount; countIndicator.innerHTML = userData.UnplayedItemCount;
} else if (enableCountIndicator) { } else if (enableCountIndicator) {
countIndicator = card.querySelector('.countIndicator'); countIndicator = card.querySelector('.countIndicator');
if (countIndicator) { if (countIndicator) {
countIndicator.parentNode.removeChild(countIndicator); countIndicator.parentNode.removeChild(countIndicator);
} }
} }
@ -1754,7 +1636,6 @@ import 'programStyles';
}); });
if (progressHtml) { if (progressHtml) {
itemProgressBar = card.querySelector('.itemProgressBar'); itemProgressBar = card.querySelector('.itemProgressBar');
if (!itemProgressBar) { if (!itemProgressBar) {
@ -1773,7 +1654,6 @@ import 'programStyles';
itemProgressBar.innerHTML = progressHtml; itemProgressBar.innerHTML = progressHtml;
} else { } else {
itemProgressBar = card.querySelector('.itemProgressBar'); itemProgressBar = card.querySelector('.itemProgressBar');
if (itemProgressBar) { if (itemProgressBar) {
itemProgressBar.parentNode.removeChild(itemProgressBar); itemProgressBar.parentNode.removeChild(itemProgressBar);
@ -1804,7 +1684,7 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]');
for (let i = 0, length = cells.length; i < length; i++) { for (let i = 0, length = cells.length; i < length; i++) {
let cell = cells[i]; const cell = cells[i];
const icon = cell.querySelector('.timerIndicator'); const icon = cell.querySelector('.timerIndicator');
if (!icon) { if (!icon) {
const indicatorsElem = ensureIndicators(cell); const indicatorsElem = ensureIndicators(cell);
@ -1823,8 +1703,8 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]');
for (let i = 0; i < cells.length; i++) { for (let i = 0; i < cells.length; i++) {
let cell = cells[i]; const cell = cells[i];
let icon = cell.querySelector('.timerIndicator'); const icon = cell.querySelector('.timerIndicator');
if (icon) { if (icon) {
icon.parentNode.removeChild(icon); icon.parentNode.removeChild(icon);
} }
@ -1841,8 +1721,8 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]');
for (let i = 0; i < cells.length; i++) { for (let i = 0; i < cells.length; i++) {
let cell = cells[i]; const cell = cells[i];
let icon = cell.querySelector('.timerIndicator'); const icon = cell.querySelector('.timerIndicator');
if (icon) { if (icon) {
icon.parentNode.removeChild(icon); icon.parentNode.removeChild(icon);
} }

View file

@ -1,13 +1,21 @@
define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browser'], function (datetime, imageLoader, connectionManager, layoutManager, browser) { /* eslint-disable indent */
'use strict';
var enableFocusTransform = !browser.slow && !browser.edge; /**
* Module for building cards from item data.
* @module components/cardBuilder/chaptercardbuilder
*/
function buildChapterCardsHtml(item, chapters, options) { import datetime from 'datetime';
import imageLoader from 'imageLoader';
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 // TODO move card creation code to Card component
var className = 'card itemAction chapterCard'; let className = 'card itemAction chapterCard';
if (layoutManager.tv) { if (layoutManager.tv) {
className += ' show-focus'; className += ' show-focus';
@ -17,38 +25,36 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
} }
} }
var mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || [];
var videoStream = mediaStreams.filter(function (i) { const videoStream = mediaStreams.filter(({Type}) => {
return i.Type === 'Video'; return Type === 'Video';
})[0] || {}; })[0] || {};
var shape = (options.backdropShape || 'backdrop'); let shape = (options.backdropShape || 'backdrop');
if (videoStream.Width && videoStream.Height) { if (videoStream.Width && videoStream.Height) {
if ((videoStream.Width / videoStream.Height) <= 1.2) { if ((videoStream.Width / videoStream.Height) <= 1.2) {
shape = (options.squareShape || 'square'); shape = (options.squareShape || 'square');
} }
} }
className += ' ' + shape + 'Card'; className += ` ${shape}Card`;
if (options.block || options.rows) { if (options.block || options.rows) {
className += ' block'; className += ' block';
} }
var html = ''; let html = '';
var itemsInRow = 0; let itemsInRow = 0;
var apiClient = connectionManager.getApiClient(item.ServerId); const apiClient = window.connectionManager.getApiClient(item.ServerId);
for (var i = 0, length = chapters.length; i < length; i++) {
for (let i = 0, length = chapters.length; i < length; i++) {
if (options.rows && itemsInRow === 0) { if (options.rows && itemsInRow === 0) {
html += '<div class="cardColumn">'; html += '<div class="cardColumn">';
} }
var chapter = chapters[i]; const chapter = chapters[i];
html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); html += buildChapterCard(item, apiClient, chapter, i, options, className, shape);
itemsInRow++; itemsInRow++;
@ -62,51 +68,45 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
return html; return html;
} }
function getImgUrl(item, chapter, index, maxWidth, apiClient) { function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) {
if (ImageTag) {
return apiClient.getScaledImageUrl(Id, {
if (chapter.ImageTag) { maxWidth: maxWidth,
tag: ImageTag,
return apiClient.getScaledImageUrl(item.Id, {
maxWidth: maxWidth * 2,
tag: chapter.ImageTag,
type: 'Chapter', type: 'Chapter',
index: index index
}); });
} }
return null; return null;
} }
function buildChapterCard(item, apiClient, chapter, index, options, className, shape) { function buildChapterCard(item, apiClient, chapter, index, {width, coverImage}, className, shape) {
const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient);
var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient); let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
if (coverImage) {
var cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer';
if (options.coverImage) {
cardImageContainerClass += ' coveredImage'; cardImageContainerClass += ' coveredImage';
} }
var dataAttributes = ' data-action="play" data-isfolder="' + item.IsFolder + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-type="' + item.Type + '" data-mediatype="' + item.MediaType + '" data-positionticks="' + chapter.StartPositionTicks + '"'; const dataAttributes = ` data-action="play" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-positionticks="${chapter.StartPositionTicks}"`;
var cardImageContainer = imgUrl ? ('<div class="' + cardImageContainerClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + '">'); let cardImageContainer = imgUrl ? (`<div class="${cardImageContainerClass} lazy" data-src="${imgUrl}">`) : (`<div class="${cardImageContainerClass}">`);
if (!imgUrl) { if (!imgUrl) {
cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>'; cardImageContainer += '<span class="material-icons cardImageIcon local_movies"></span>';
} }
var nameHtml = ''; let nameHtml = '';
nameHtml += '<div class="cardText">' + chapter.Name + '</div>'; nameHtml += `<div class="cardText">${chapter.Name}</div>`;
nameHtml += '<div class="cardText">' + datetime.getDisplayRunningTime(chapter.StartPositionTicks) + '</div>'; nameHtml += `<div class="cardText">${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}</div>`;
var cardBoxCssClass = 'cardBox'; const cardBoxCssClass = 'cardBox';
var cardScalableClass = 'cardScalable'; const cardScalableClass = 'cardScalable';
var 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 `<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;
} }
function buildChapterCards(item, chapters, options) { export function buildChapterCards(item, chapters, options) {
if (options.parentContainer) { if (options.parentContainer) {
// Abort if the container has been disposed // Abort if the container has been disposed
if (!document.body.contains(options.parentContainer)) { if (!document.body.contains(options.parentContainer)) {
@ -121,15 +121,16 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse
} }
} }
var html = buildChapterCardsHtml(item, chapters, options); const html = buildChapterCardsHtml(item, chapters, options);
options.itemsContainer.innerHTML = html; options.itemsContainer.innerHTML = html;
imageLoader.lazyChildren(options.itemsContainer); imageLoader.lazyChildren(options.itemsContainer);
} }
return { /* eslint-enable indent */
buildChapterCards: buildChapterCards
}; export default {
buildChapterCards: buildChapterCards
};
});

View file

@ -1,8 +1,13 @@
define(['cardBuilder'], function (cardBuilder) { /* eslint-disable indent */
'use strict';
function buildPeopleCards(items, options) { /**
* Module for building cards from item data.
* @module components/cardBuilder/peoplecardbuilder
*/
import cardBuilder from 'cardBuilder';
export function buildPeopleCards(items, options) {
options = Object.assign(options || {}, { options = Object.assign(options || {}, {
cardLayout: false, cardLayout: false,
centerText: true, centerText: true,
@ -15,8 +20,8 @@ define(['cardBuilder'], function (cardBuilder) {
cardBuilder.buildCards(items, options); cardBuilder.buildCards(items, options);
} }
return { /* eslint-enable indent */
buildPeopleCards: buildPeopleCards
};
}); export default {
buildPeopleCards: buildPeopleCards
};

View file

@ -1,34 +1,28 @@
define([], function() { class CastSenderApi {
'use strict'; load() {
if (window.appMode === 'cordova' || window.appMode === 'android') {
if (window.appMode === 'cordova' || window.appMode === 'android') { window.chrome = window.chrome || {};
return { return Promise.resolve();
load: function () { } else {
window.chrome = window.chrome || {}; let ccLoaded = false;
if (ccLoaded) {
return Promise.resolve(); return Promise.resolve();
} }
};
} else {
var ccLoaded = false;
return {
load: function () {
if (ccLoaded) {
return Promise.resolve();
}
return new Promise(function (resolve, reject) { return new Promise(function (resolve) {
var fileref = document.createElement('script'); const fileref = document.createElement('script');
fileref.setAttribute('type', 'text/javascript'); fileref.setAttribute('type', 'text/javascript');
fileref.onload = function () { fileref.onload = function () {
ccLoaded = true; ccLoaded = true;
resolve(); resolve();
}; };
fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js'); fileref.setAttribute('src', 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js');
document.querySelector('head').appendChild(fileref); document.querySelector('head').appendChild(fileref);
}); });
} }
};
} }
}); }
export default CastSenderApi;

View file

@ -1,21 +1,32 @@
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) { import dom from 'dom';
'use strict'; import dialogHelper from 'dialogHelper';
import loading from 'loading';
import globalize from 'globalize';
import actionsheet from 'actionsheet';
import 'emby-input';
import 'paper-icon-button-light';
import 'emby-button';
import 'listViewStyle';
import 'material-icons';
import 'formDialogStyle';
return function (options) { export default class channelMapper {
constructor(options) {
function mapChannel(button, channelId, providerChannelId) { function mapChannel(button, channelId, providerChannelId) {
loading.show(); loading.show();
var providerId = options.providerId; const providerId = options.providerId;
connectionManager.getApiClient(options.serverId).ajax({ window.connectionManager.getApiClient(options.serverId).ajax({
type: 'POST', type: 'POST',
url: ApiClient.getUrl('LiveTv/ChannelMappings'), url: ApiClient.getUrl('LiveTv/ChannelMappings'),
data: { data: JSON.stringify({
providerId: providerId, providerId: providerId,
tunerChannelId: channelId, tunerChannelId: channelId,
providerChannelId: providerChannelId providerChannelId: providerChannelId
}, }),
contentType: 'application/json',
dataType: 'json' dataType: 'json'
}).then(function (mapping) { }).then(mapping => {
var listItem = dom.parentWithClass(button, 'listItem'); const listItem = dom.parentWithClass(button, 'listItem');
button.setAttribute('data-providerid', mapping.ProviderChannelId); button.setAttribute('data-providerid', mapping.ProviderChannelId);
listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName); listItem.querySelector('.secondary').innerHTML = getMappingSecondaryName(mapping, currentMappingOptions.ProviderName);
loading.hide(); loading.hide();
@ -23,42 +34,42 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act
} }
function onChannelsElementClick(e) { function onChannelsElementClick(e) {
var btnMap = dom.parentWithClass(e.target, 'btnMap'); const btnMap = dom.parentWithClass(e.target, 'btnMap');
if (btnMap) { if (btnMap) {
var channelId = btnMap.getAttribute('data-id'); const channelId = btnMap.getAttribute('data-id');
var providerChannelId = btnMap.getAttribute('data-providerid'); const providerChannelId = btnMap.getAttribute('data-providerid');
var menuItems = currentMappingOptions.ProviderChannels.map(function (m) { const menuItems = currentMappingOptions.ProviderChannels.map(m => {
return { return {
name: m.Name, name: m.Name,
id: m.Id, id: m.Id,
selected: m.Id.toLowerCase() === providerChannelId.toLowerCase() selected: m.Id.toLowerCase() === providerChannelId.toLowerCase()
}; };
}).sort(function (a, b) { }).sort((a, b) => {
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
actionsheet.show({ actionsheet.show({
positionTo: btnMap, positionTo: btnMap,
items: menuItems items: menuItems
}).then(function (newChannelId) { }).then(newChannelId => {
mapChannel(btnMap, channelId, newChannelId); mapChannel(btnMap, channelId, newChannelId);
}); });
} }
} }
function getChannelMappingOptions(serverId, providerId) { function getChannelMappingOptions(serverId, providerId) {
var apiClient = connectionManager.getApiClient(serverId); const apiClient = window.connectionManager.getApiClient(serverId);
return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', { return apiClient.getJSON(apiClient.getUrl('LiveTv/ChannelMappingOptions', {
providerId: providerId providerId: providerId
})); }));
} }
function getMappingSecondaryName(mapping, providerName) { function getMappingSecondaryName(mapping, providerName) {
return (mapping.ProviderChannelName || '') + ' - ' + providerName; return `${mapping.ProviderChannelName || ''} - ${providerName}`;
} }
function getTunerChannelHtml(channel, providerName) { function getTunerChannelHtml(channel, providerName) {
var html = ''; let html = '';
html += '<div class="listItem">'; html += '<div class="listItem">';
html += '<span class="material-icons listItemIcon dvr"></span>'; html += '<span class="material-icons listItemIcon dvr"></span>';
html += '<div class="listItemBody two-line">'; html += '<div class="listItemBody two-line">';
@ -73,16 +84,16 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act
html += '</div>'; html += '</div>';
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>'; return html += '</div>';
} }
function getEditorHtml() { function getEditorHtml() {
var html = ''; let html = '';
html += '<div class="formDialogContent">'; html += '<div class="formDialogContent smoothScrollY">';
html += '<div class="dialogContentInner dialog-content-centered">'; html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">'; html += '<form style="margin:auto;">';
html += '<h1>' + globalize.translate('HeaderChannels') + '</h1>'; html += `<h1>${globalize.translate('Channels')}</h1>`;
html += '<div class="channels paperList">'; html += '<div class="channels paperList">';
html += '</div>'; html += '</div>';
html += '</form>'; html += '</form>';
@ -91,30 +102,29 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act
} }
function initEditor(dlg, options) { function initEditor(dlg, options) {
getChannelMappingOptions(options.serverId, options.providerId).then(function (result) { getChannelMappingOptions(options.serverId, options.providerId).then(result => {
currentMappingOptions = result; currentMappingOptions = result;
var channelsElement = dlg.querySelector('.channels'); const channelsElement = dlg.querySelector('.channels');
channelsElement.innerHTML = result.TunerChannels.map(function (channel) { channelsElement.innerHTML = result.TunerChannels.map(channel => {
return getTunerChannelHtml(channel, result.ProviderName); return getTunerChannelHtml(channel, result.ProviderName);
}).join(''); }).join('');
channelsElement.addEventListener('click', onChannelsElementClick); channelsElement.addEventListener('click', onChannelsElementClick);
}); });
} }
var currentMappingOptions; let currentMappingOptions;
var self = this;
self.show = function () { this.show = () => {
var dialogOptions = { const dialogOptions = {
removeOnClose: true removeOnClose: true
}; };
dialogOptions.size = 'small'; dialogOptions.size = 'small';
var dlg = dialogHelper.createDialog(dialogOptions); const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog'); dlg.classList.add('formDialog');
dlg.classList.add('ui-body-a'); dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a'); dlg.classList.add('background-theme-a');
var html = ''; let html = '';
var title = globalize.translate('MapChannels'); const title = globalize.translate('MapChannels');
html += '<div class="formDialogHeader">'; 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 += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">'; html += '<h3 class="formDialogHeaderTitle">';
@ -124,13 +134,13 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act
html += getEditorHtml(); html += getEditorHtml();
dlg.innerHTML = html; dlg.innerHTML = html;
initEditor(dlg, options); initEditor(dlg, options);
dlg.querySelector('.btnCancel').addEventListener('click', function () { dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
return new Promise(function (resolve, reject) { return new Promise(resolve => {
dlg.addEventListener('close', resolve); dlg.addEventListener('close', resolve);
dialogHelper.open(dlg); dialogHelper.open(dlg);
}); });
}; };
}; }
}); }

View file

@ -1,234 +0,0 @@
define(['events'], function (events) {
'use strict';
// LinkParser
//
// https://github.com/ravisorg/LinkParser
//
// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and
// IPv6, unrecognised TLDs, and more.
//
// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
// http://creativecommons.org/licenses/by-sa/4.0/
(function () {
// Original URL regex from the Android android.text.util.Linkify function, found here:
// http://stackoverflow.com/a/19696443
//
// However there were problems with it, most probably related to the fact it was
// written in 2007, and it's been highly modified.
//
// 1) I didn't like the fact that it was tied to specific TLDs, since new ones
// are being added all the time it wouldn't be reasonable to expect developer to
// be continually updating their regular expressions.
//
// 2) It didn't allow unicode characters in the domains which are now allowed in
// many languages, (including some IDN TLDs). Again these are constantly being
// added to and it doesn't seem reasonable to hard-code them. Note this ended up
// not being possible in standard JS due to the way it handles multibyte strings.
// It is possible using XRegExp, however a big performance hit results. Disabled
// for now.
//
// 3) It didn't allow for IPv6 hostnames
// IPv6 regex from http://stackoverflow.com/a/17871737
//
// 4) It was very poorly commented
//
// 5) It wasn't as smart as it could have been about what should be part of a
// URL and what should be part of human language.
var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)';
var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters)
+ "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters)
+ '\\@)';
// IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452
// by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License
// http://intermapper.com/
var ipv6 = '('
+ '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))'
+ '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
+ '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))'
+ '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))'
+ ')(%.+)?';
var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.'
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.'
+ '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])';
// This would have been a lot cleaner if JS RegExp supported conditionals...
var linkRegExpString =
// begin match for protocol / username / password / host
'(?:'
// ============================
// If we have a recognized protocol at the beginning of the URL, we're
// more relaxed about what we accept, because we assume the user wants
// this to be a URL, and we're not accidentally matching human language
+ protocols + '?'
// optional username:password@
+ credentials + '?'
// IP address (both v4 and v6)
+ '(?:'
// IPv6
+ ipv6
// IPv4
+ '|' + ipv4
+ ')'
// end match for protocol / username / password / host
+ ')'
// optional port number
+ '(?:\\:\\d{1,5})?'
// plus optional path and query params (no unicode allowed here?)
+ '(?:'
+ '\\/(?:'
// some characters we'll accept because it's unlikely human language
// would use them after a URL unless they were part of the url
+ '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])'
+ '|(?:\\%[a-f0-9]{2})'
// some characters are much more likely to be used AFTER a url and
// were not intended to be included in the url itself. Mostly end
// of sentence type things. It's also likely that the URL would
// still work if any of these characters were missing from the end
// because we parsed it incorrectly. For these characters to be accepted
// they must be followed by another character that we're reasonably
// sure is part of the url
+ "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))"
+ ')*'
+ '|\\b|\$'
+ ')';
// regex = XRegExp(regex,'gi');
var linkRegExp = RegExp(linkRegExpString, 'gi');
var protocolRegExp = RegExp('^' + protocols, 'i');
// if url doesn't begin with a known protocol, add http by default
function ensureProtocol(url) {
if (!url.match(protocolRegExp)) {
url = 'http://' + url;
}
return url;
}
// look for links in the text
var LinkParser = {
parse: function (text) {
var links = [];
var match;
// eslint-disable-next-line no-cond-assign
while (match = linkRegExp.exec(text)) {
console.debug(match);
var txt = match[0];
var pos = match.index;
var len = txt.length;
var url = ensureProtocol(text);
links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url });
}
return links;
}
};
window.LinkParser = LinkParser;
})();
var cache = {};
function isValidIpAddress(address) {
var links = LinkParser.parse(address);
return links.length == 1;
}
function isLocalIpAddress(address) {
address = address.toLowerCase();
if (address.indexOf('127.0.0.1') !== -1) {
return true;
}
if (address.indexOf('localhost') !== -1) {
return true;
}
return false;
}
function getServerAddress(apiClient) {
var serverAddress = apiClient.serverAddress();
if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) {
return Promise.resolve(serverAddress);
}
var cachedValue = getCachedValue(serverAddress);
if (cachedValue) {
return Promise.resolve(cachedValue);
}
return apiClient.getEndpointInfo().then(function (endpoint) {
if (endpoint.IsInNetwork) {
return apiClient.getPublicSystemInfo().then(function (info) {
var localAddress = info.LocalAddress;
if (!localAddress) {
console.debug('No valid local address returned, defaulting to external one');
localAddress = serverAddress;
}
addToCache(serverAddress, localAddress);
return localAddress;
});
} else {
addToCache(serverAddress, serverAddress);
return serverAddress;
}
});
}
function clearCache() {
cache = {};
}
function addToCache(key, value) {
cache[key] = {
value: value,
time: new Date().getTime()
};
}
function getCachedValue(key) {
var obj = cache[key];
if (obj && (new Date().getTime() - obj.time) < 180000) {
return obj.value;
}
return null;
}
events.on(ConnectionManager, 'localusersignedin', clearCache);
events.on(ConnectionManager, 'localusersignedout', clearCache);
return {
getServerAddress: getServerAddress
};
});

View file

@ -1,16 +1,30 @@
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) { import dom from 'dom';
'use strict'; import dialogHelper from 'dialogHelper';
import loading from 'loading';
import layoutManager from 'layoutManager';
import appRouter from 'appRouter';
import globalize from 'globalize';
import 'emby-checkbox';
import 'emby-input';
import 'paper-icon-button-light';
import 'emby-select';
import 'material-icons';
import 'css!./../formdialog';
import 'emby-button';
import 'flexStyles';
var currentServerId; /* eslint-disable indent */
let currentServerId;
function onSubmit(e) { function onSubmit(e) {
loading.show(); loading.show();
var panel = dom.parentWithClass(this, 'dialog'); const panel = dom.parentWithClass(this, 'dialog');
var collectionId = panel.querySelector('#selectCollectionToAddTo').value; const collectionId = panel.querySelector('#selectCollectionToAddTo').value;
var apiClient = connectionManager.getApiClient(currentServerId); const apiClient = window.connectionManager.getApiClient(currentServerId);
if (collectionId) { if (collectionId) {
addToCollection(apiClient, panel, collectionId); addToCollection(apiClient, panel, collectionId);
@ -23,8 +37,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} }
function createCollection(apiClient, dlg) { function createCollection(apiClient, dlg) {
const url = apiClient.getUrl('Collections', {
var url = apiClient.getUrl('Collections', {
Name: dlg.querySelector('#txtNewCollectionName').value, Name: dlg.querySelector('#txtNewCollectionName').value,
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
@ -36,27 +49,23 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
url: url, url: url,
dataType: 'json' dataType: 'json'
}).then(function (result) { }).then(result => {
loading.hide(); loading.hide();
var id = result.Id; const id = result.Id;
dlg.submitted = true; dlg.submitted = true;
dialogHelper.close(dlg); dialogHelper.close(dlg);
redirectToCollection(apiClient, id); redirectToCollection(apiClient, id);
}); });
} }
function redirectToCollection(apiClient, id) { function redirectToCollection(apiClient, id) {
appRouter.showItem(id, apiClient.serverId()); appRouter.showItem(id, apiClient.serverId());
} }
function addToCollection(apiClient, dlg, id) { function addToCollection(apiClient, dlg, id) {
const url = apiClient.getUrl(`Collections/${id}/Items`, {
var url = apiClient.getUrl('Collections/' + id + '/Items', {
Ids: dlg.querySelector('.fldSelectedItemIds').value || '' Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
}); });
@ -65,14 +74,13 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
type: 'POST', type: 'POST',
url: url url: url
}).then(function () { }).then(() => {
loading.hide(); loading.hide();
dlg.submitted = true; dlg.submitted = true;
dialogHelper.close(dlg); dialogHelper.close(dlg);
require(['toast'], function (toast) { import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageItemsAdded')); toast(globalize.translate('MessageItemsAdded'));
}); });
}); });
@ -83,14 +91,13 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} }
function populateCollections(panel) { function populateCollections(panel) {
loading.show(); loading.show();
var select = panel.querySelector('#selectCollectionToAddTo'); const select = panel.querySelector('#selectCollectionToAddTo');
panel.querySelector('.newCollectionInfo').classList.add('hide'); panel.querySelector('.newCollectionInfo').classList.add('hide');
var options = { const options = {
Recursive: true, Recursive: true,
IncludeItemTypes: 'BoxSet', IncludeItemTypes: 'BoxSet',
@ -98,16 +105,14 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
EnableTotalRecordCount: false EnableTotalRecordCount: false
}; };
var apiClient = connectionManager.getApiClient(currentServerId); const apiClient = window.connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) { apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
let html = '';
var html = ''; html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += '<option value="">' + globalize.translate('OptionNew') + '</option>'; html += result.Items.map(i => {
return `<option value="${i.Id}">${i.Name}</option>`;
html += result.Items.map(function (i) {
return '<option value="' + i.Id + '">' + i.Name + '</option>';
}); });
select.innerHTML = html; select.innerHTML = html;
@ -119,8 +124,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} }
function getEditorHtml() { function getEditorHtml() {
let html = '';
var html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">'; html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">'; html += '<div class="dialogContentInner dialog-content-centered">';
@ -134,27 +138,27 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
html += '<br/>'; html += '<br/>';
html += '<br/>'; html += '<br/>';
html += '<div class="selectContainer">'; 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>'; html += '</div>';
html += '<div class="newCollectionInfo">'; html += '<div class="newCollectionInfo">';
html += '<div class="inputContainer">'; html += '<div class="inputContainer">';
html += '<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="' + globalize.translate('LabelName') + '" />'; 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 class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
html += '</div>'; html += '</div>';
html += '<label class="checkboxContainer">'; html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />'; html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += '<span>' + globalize.translate('SearchForCollectionInternetMetadata') + '</span>'; html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
html += '</label>'; html += '</label>';
// newCollectionInfo // newCollectionInfo
html += '</div>'; html += '</div>';
html += '<div class="formDialogFooter">'; 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 += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />'; html += '<input type="hidden" class="fldSelectedItemIds" />';
@ -167,7 +171,6 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} }
function initEditor(content, items) { function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () { content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) { if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide'); content.querySelector('.newCollectionInfo').classList.add('hide');
@ -188,7 +191,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} else { } else {
content.querySelector('.fldSelectCollection').classList.add('hide'); content.querySelector('.fldSelectCollection').classList.add('hide');
var selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo'); const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
selectCollectionToAddTo.innerHTML = ''; selectCollectionToAddTo.innerHTML = '';
selectCollectionToAddTo.value = ''; selectCollectionToAddTo.value = '';
triggerChange(selectCollectionToAddTo); triggerChange(selectCollectionToAddTo);
@ -196,79 +199,70 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} }
function centerFocus(elem, horiz, on) { function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) { import('scrollHelper').then((scrollHelper) => {
var fn = on ? 'on' : 'off'; const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz); scrollHelper.centerFocus[fn](elem, horiz);
}); });
} }
function CollectionEditor() { export class showEditor {
constructor(options) {
const items = options.items || {};
currentServerId = options.serverId;
} const dialogOptions = {
removeOnClose: true,
CollectionEditor.prototype.show = function (options) { scrollY: false
};
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) { if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false); dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
} }
if (dlg.submitted) { const dlg = dialogHelper.createDialog(dialogOptions);
return Promise.resolve();
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 Promise.reject(); return dialogHelper.open(dlg).then(() => {
}); if (layoutManager.tv) {
}; centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
return CollectionEditor; if (dlg.submitted) {
}); return Promise.resolve();
}
return Promise.reject();
});
}
}
/* eslint-enable indent */
export default showEditor;

View file

@ -1,13 +1,16 @@
define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize) { import browser from 'browser';
'use strict'; import dialog from 'dialog';
import globalize from 'globalize';
/* eslint-disable indent */
export default (() => {
function replaceAll(str, find, replace) { function replaceAll(str, find, replace) {
return str.split(find).join(replace); return str.split(find).join(replace);
} }
if (browser.tv && window.confirm) { if (browser.tv && window.confirm) {
// Use the native confirm dialog // Use the native confirm dialog
return function (options) { return options => {
if (typeof options === 'string') { if (typeof options === 'string') {
options = { options = {
title: '', title: '',
@ -15,8 +18,8 @@ define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize)
}; };
} }
var text = replaceAll(options.text || '', '<br/>', '\n'); const text = replaceAll(options.text || '', '<br/>', '\n');
var result = confirm(text); const result = window.confirm(text);
if (result) { if (result) {
return Promise.resolve(); return Promise.resolve();
@ -26,8 +29,8 @@ define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize)
}; };
} else { } else {
// Use our own dialog // Use our own dialog
return function (text, title) { return (text, title) => {
var options; let options;
if (typeof text === 'string') { if (typeof text === 'string') {
options = { options = {
title: title, title: title,
@ -37,7 +40,7 @@ define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize)
options = text; options = text;
} }
var items = []; const items = [];
items.push({ items.push({
name: options.cancelText || globalize.translate('ButtonCancel'), name: options.cancelText || globalize.translate('ButtonCancel'),
@ -53,7 +56,7 @@ define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize)
options.buttons = items; options.buttons = items;
return dialog(options).then(function (result) { return dialog.show(options).then(result => {
if (result === 'ok') { if (result === 'ok') {
return Promise.resolve(); return Promise.resolve();
} }
@ -62,4 +65,5 @@ define(['browser', 'dialog', 'globalize'], function(browser, dialog, globalize)
}); });
}; };
} }
}); })();
/* eslint-enable indent */

View file

@ -1,57 +0,0 @@
define(['connectionManager', 'confirm', 'appRouter', 'globalize'], function (connectionManager, confirm, appRouter, globalize) {
'use strict';
function alertText(options) {
return new Promise(function (resolve, reject) {
require(['alert'], function (alert) {
alert(options).then(resolve, resolve);
});
});
}
function deleteItem(options) {
var item = options.item;
var itemId = item.Id;
var parentId = item.SeasonId || item.SeriesId || item.ParentId;
var serverId = item.ServerId;
var msg = globalize.translate('ConfirmDeleteItem');
var title = globalize.translate('HeaderDeleteItem');
var apiClient = connectionManager.getApiClient(item.ServerId);
return confirm({
title: title,
text: msg,
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(function () {
return apiClient.deleteItem(itemId).then(function () {
if (options.navigate) {
if (parentId) {
appRouter.showItem(parentId, serverId);
} else {
appRouter.goHome();
}
}
}, function (err) {
var result = function () {
return Promise.reject(err);
};
return alertText(globalize.translate('ErrorDeletingItem')).then(result, result);
});
});
}
return {
deleteItem: deleteItem
};
});

View file

@ -1,20 +1,30 @@
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) { import dialogHelper from 'dialogHelper';
'use strict'; import dom from 'dom';
import layoutManager from 'layoutManager';
import scrollHelper from 'scrollHelper';
import globalize from 'globalize';
import 'material-icons';
import 'emby-button';
import 'paper-icon-button-light';
import 'emby-input';
import 'formDialogStyle';
import 'flexStyles';
/* eslint-disable indent */
function showDialog(options, template) { function showDialog(options, template) {
const dialogOptions = {
var dialogOptions = {
removeOnClose: true, removeOnClose: true,
scrollY: false scrollY: false
}; };
var enableTvLayout = layoutManager.tv; const enableTvLayout = layoutManager.tv;
if (enableTvLayout) { if (enableTvLayout) {
dialogOptions.size = 'fullscreen'; dialogOptions.size = 'fullscreen';
} }
var dlg = dialogHelper.createDialog(dialogOptions); const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog'); dlg.classList.add('formDialog');
@ -22,7 +32,7 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
dlg.classList.add('align-items-center'); dlg.classList.add('align-items-center');
dlg.classList.add('justify-content-center'); dlg.classList.add('justify-content-center');
var formDialogContent = dlg.querySelector('.formDialogContent'); const formDialogContent = dlg.querySelector('.formDialogContent');
formDialogContent.classList.add('no-grow'); formDialogContent.classList.add('no-grow');
if (enableTvLayout) { if (enableTvLayout) {
@ -30,41 +40,36 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
formDialogContent.style['max-height'] = '60%'; formDialogContent.style['max-height'] = '60%';
scrollHelper.centerFocus.on(formDialogContent, false); scrollHelper.centerFocus.on(formDialogContent, false);
} else { } 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.classList.add('dialog-fullscreen-lowres');
} }
//dlg.querySelector('.btnCancel').addEventListener('click', function (e) {
// dialogHelper.close(dlg);
//});
if (options.title) { if (options.title) {
dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || ''; dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title || '';
} else { } else {
dlg.querySelector('.formDialogHeaderTitle').classList.add('hide'); dlg.querySelector('.formDialogHeaderTitle').classList.add('hide');
} }
var displayText = options.html || options.text || ''; const displayText = options.html || options.text || '';
dlg.querySelector('.text').innerHTML = displayText; dlg.querySelector('.text').innerHTML = displayText;
if (!displayText) { if (!displayText) {
dlg.querySelector('.dialogContentInner').classList.add('hide'); dlg.querySelector('.dialogContentInner').classList.add('hide');
} }
var i; let i;
var length; let length;
var html = ''; let html = '';
var hasDescriptions = false; let hasDescriptions = false;
for (i = 0, length = options.buttons.length; i < length; i++) { for (i = 0, length = options.buttons.length; i < length; i++) {
const item = options.buttons[i];
const autoFocus = i === 0 ? ' autofocus' : '';
var item = options.buttons[i]; let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
var autoFocus = i === 0 ? ' autofocus' : '';
var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize';
if (item.type) { if (item.type) {
buttonClass += ' button-' + item.type; buttonClass += ` button-${item.type}`;
} }
if (item.description) { if (item.description) {
@ -75,10 +80,10 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom'; 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) { 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>`;
} }
} }
@ -88,19 +93,18 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical');
} }
var dialogResult; let dialogResult;
function onButtonClick() { function onButtonClick() {
dialogResult = this.getAttribute('data-id'); dialogResult = this.getAttribute('data-id');
dialogHelper.close(dlg); dialogHelper.close(dlg);
} }
var buttons = dlg.querySelectorAll('.btnOption'); const buttons = dlg.querySelectorAll('.btnOption');
for (i = 0, length = buttons.length; i < length; i++) { for (i = 0, length = buttons.length; i < length; i++) {
buttons[i].addEventListener('click', onButtonClick); buttons[i].addEventListener('click', onButtonClick);
} }
return dialogHelper.open(dlg).then(function () { return dialogHelper.open(dlg).then(() => {
if (enableTvLayout) { if (enableTvLayout) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
} }
@ -113,9 +117,8 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
}); });
} }
return function (text, title) { export async function show(text, title) {
let options;
var options;
if (typeof text === 'string') { if (typeof text === 'string') {
options = { options = {
title: title, title: title,
@ -125,10 +128,13 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're
options = text; options = text;
} }
return new Promise(function (resolve, reject) { const { default: template } = await import('text!./dialog.template.html');
require(['text!./dialog.template.html'], function (template) { return new Promise((resolve, reject) => {
showDialog(options, template).then(resolve, reject); showDialog(options, template).then(resolve, reject);
});
}); });
}; }
});
/* eslint-enable indent */
export default {
show: show
};

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