1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge branch 'master' into feature-manually-add-subtitle

This commit is contained in:
Cameron 2020-11-21 20:32:46 +00:00 committed by GitHub
commit aea66e008a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
625 changed files with 60429 additions and 53288 deletions

View file

@ -0,0 +1,57 @@
jobs:
- job: Build
displayName: 'Build'
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Install Node'
inputs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Cache node_modules'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
env:
SKIP_PREPARE: '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: '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: 'Cache node_modules'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
env:
SKIP_PREPARE: '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'
@ -22,33 +25,40 @@ module.exports = {
'eslint:recommended', 'eslint:recommended',
// 'plugin:promise/recommended', // 'plugin:promise/recommended',
'plugin:import/errors', 'plugin:import/errors',
'plugin:import/warnings',
'plugin:eslint-comments/recommended', 'plugin:eslint-comments/recommended',
'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,
@ -68,17 +78,12 @@ module.exports = {
// Dependency globals // Dependency globals
'$': 'readonly', '$': 'readonly',
'jQuery': 'readonly', 'jQuery': 'readonly',
'requirejs': 'readonly',
// Jellyfin globals // Jellyfin globals
'ApiClient': 'writable', 'ApiClient': 'writable',
'AppInfo': 'writable',
'chrome': 'writable', 'chrome': 'writable',
'ConnectionManager': 'writable',
'DlnaProfilePage': 'writable', 'DlnaProfilePage': 'writable',
'Dashboard': 'writable',
'DashboardPage': 'writable', 'DashboardPage': 'writable',
'Emby': 'readonly', 'Emby': 'readonly',
'Events': 'writable',
'getParameterByName': 'writable', 'getParameterByName': 'writable',
'getWindowLocationSearch': 'writable', 'getWindowLocationSearch': 'writable',
'Globalize': 'writable', 'Globalize': 'writable',
@ -88,19 +93,17 @@ module.exports = {
'LinkParser': 'writable', 'LinkParser': 'writable',
'LiveTvHelpers': 'writable', 'LiveTvHelpers': 'writable',
'MetadataEditor': 'writable', 'MetadataEditor': 'writable',
'pageClassOn': 'writable',
'pageIdOn': 'writable',
'PlaylistViewer': 'writable', 'PlaylistViewer': 'writable',
'UserParentalControlPage': 'writable', 'UserParentalControlPage': 'writable',
'Windows': 'readonly' 'Windows': 'readonly'
}, },
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 +133,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 +194,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.

31
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,31 @@
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '30 7 * * 6'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

8
.gitignore vendored
View file

@ -1,14 +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 # log
yarn-error.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

1
debian/conffiles vendored Normal file
View file

@ -0,0 +1 @@
/usr/share/jellyfin/web/config.json

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-loader": "^8.0.6", "@babel/preset-env": "^7.12.1",
"browser-sync": "^2.26.7", "autoprefixer": "^9.8.6",
"babel-loader": "^8.2.1",
"browser-sync": "^2.26.13",
"clean-webpack-plugin": "^3.0.0", "clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^5.1.1", "confusing-browser-globals": "^1.0.10",
"css-loader": "^3.4.2", "copy-webpack-plugin": "^6.0.3",
"css-loader": "^5.0.1",
"cssnano": "^4.1.10", "cssnano": "^4.1.10",
"del": "^5.1.0", "del": "^6.0.0",
"eslint": "^6.8.0", "eslint": "^7.13.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", "expose-loader": "^1.0.1",
"file-loader": "^6.2.0",
"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",
@ -34,88 +38,75 @@
"gulp-mode": "^1.0.2", "gulp-mode": "^1.0.2",
"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": "^3.0.0",
"gulp-terser": "^1.2.0", "gulp-terser": "^1.4.1",
"html-webpack-plugin": "^4.3.0", "html-loader": "^1.1.0",
"html-webpack-plugin": "^4.5.0",
"lazypipe": "^1.0.2", "lazypipe": "^1.0.2",
"node-sass": "^4.13.1", "node-sass": "^5.0.0",
"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", "source-map-loader": "^1.1.1",
"stylelint": "^13.3.3", "style-loader": "^2.0.0",
"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.4.0",
"webpack-cli": "^3.3.10", "webpack-cli": "^4.0.0",
"webpack-concat-plugin": "^3.0.0",
"webpack-dev-server": "^3.11.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.1",
"workbox-webpack-plugin": "^5.1.4",
"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.7.0",
"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", "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", "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", "pdfjs-dist": "2.5.207",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"sass": "^1.29.0",
"sass-loader": "^10.0.5",
"screenfull": "^5.0.2", "screenfull": "^5.0.2",
"shaka-player": "^2.5.11", "sortablejs": "^1.12.0",
"sortablejs": "^1.10.2", "swiper": "^6.3.5",
"swiper": "^5.3.7",
"webcomponents.js": "^0.7.24", "webcomponents.js": "^0.7.24",
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.5.0",
"workbox-core": "^5.1.4",
"workbox-precaching": "^5.1.4"
}, },
"babel": { "babel": {
"presets": [ "presets": [
"@babel/preset-env" [
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
], ],
"overrides": [ "plugins": [
{ "@babel/plugin-proposal-class-properties",
"test": [ "@babel/plugin-proposal-private-methods"
"src/components/autoFocuser.js",
"src/components/cardbuilder/cardBuilder.js",
"src/components/filedownloader.js",
"src/components/images/imageLoader.js",
"src/components/lazyloader/lazyloader-intersectionobserver.js",
"src/components/playback/mediasession.js",
"src/components/sanatizefilename.js",
"src/components/scrollManager.js",
"src/components/subtitleuploader/subtitleuploader.js",
"src/scripts/dfnshelper.js",
"src/scripts/dom.js",
"src/scripts/filesystem.js",
"src/scripts/imagehelper.js",
"src/scripts/inputManager.js",
"src/components/deletehelper.js",
"src/components/actionsheet/actionsheet.js",
"src/components/playmenu.js",
"src/components/indicators/indicators.js",
"src/scripts/keyboardnavigation.js",
"src/scripts/settings/appSettings.js",
"src/scripts/settings/userSettings.js",
"src/scripts/settings/webSettings.js"
],
"plugins": [
"@babel/plugin-transform-modules-amd"
]
}
] ]
}, },
"browserslist": [ "browserslist": [
@ -123,7 +114,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",
@ -131,14 +122,15 @@
"Chrome 53", "Chrome 53",
"Chrome 56", "Chrome 56",
"Chrome 63", "Chrome 63",
"Edge 18",
"Firefox ESR" "Firefox ESR"
], ],
"scripts": { "scripts": {
"serve": "gulp serve --development", "start": "yarn serve",
"prepare": "gulp --production", "serve": "webpack serve --config webpack.dev.js",
"build:development": "gulp --development", "prepare": "./scripts/prepare.sh",
"build:production": "gulp --production", "build:development": "webpack --config webpack.dev.js",
"build:standalone": "gulp standalone --development", "build:production": "webpack --config webpack.prod.js",
"lint": "eslint \".\"", "lint": "eslint \".\"",
"stylelint": "stylelint \"src/**/*.css\"" "stylelint": "stylelint \"src/**/*.css\""
} }

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

5
scripts/prepare.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
if [ -z "${SKIP_PREPARE}" ]; then
webpack --config webpack.prod.js
fi

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

@ -1,37 +0,0 @@
html {
font-family: "Noto Sans", sans-serif;
font-size: 93%;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
h1,
h2,
h3 {
font-family: "Noto Sans", sans-serif;
}
h1 {
font-weight: 400;
font-size: 1.8em;
}
h2 {
font-weight: 400;
font-size: 1.5em;
}
h3 {
font-weight: 400;
font-size: 1.17em;
}
.layout-tv {
font-size: 130%;
}
.layout-mobile {
font-size: 90%;
}

34
src/assets/css/fonts.scss Normal file
View file

@ -0,0 +1,34 @@
@mixin font($weight: null, $size: null) {
font-family: "Noto Sans", sans-serif;
font-weight: $weight;
font-size: $size;
}
html {
@include font;
text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
h1 {
@include font(400, 1.8em);
}
h2 {
@include font(400, 1.5em);
}
h3 {
@include font(400, 1.17em);
}
.layout-tv {
/* Per WebOS and Tizen guidelines, fonts must be 20px minimum.
This takes the 16px baseline and multiplies it by 1.25 to get 20px. */
font-size: 125%;
}
.layout-mobile {
font-size: 90%;
}

View file

@ -1,31 +0,0 @@
h1 {
font-weight: 400;
font-size: 1.8em;
}
.layout-desktop h1 {
font-size: 2em;
}
h2 {
font-weight: 400;
font-size: 1.5em;
}
h3 {
font-weight: 400;
font-size: 1.17em;
}
@media all and (min-height: 720px) {
html {
font-size: 20px;
}
}
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
@media all and (min-height: 1000px) {
html {
font-size: 27px;
}
}

View file

@ -0,0 +1,31 @@
@mixin header-font($size: null) {
font-weight: 400;
font-size: $size;
}
html {
@media all and (min-height: 720px) {
font-size: 20px;
}
/* This is supposed to be 1080p, but had to reduce the min height to account for possible browser chrome */
@media all and (min-height: 1000px) {
font-size: 27px;
}
}
h1 {
@include header-font(1.8em);
.layout-desktop & {
font-size: 2em;
}
}
h2 {
@include header-font(1.8em);
}
h3 {
@include header-font(1.17em);
}

View file

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

3
src/assets/css/ios.scss Normal file
View file

@ -0,0 +1,3 @@
html {
font-size: 82% !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

@ -2,8 +2,8 @@
padding-bottom: 15em; padding-bottom: 15em;
} }
@media all and (min-width: 62.5em) { #guideTab {
#guideTab { @media all and (min-width: 62.5em) {
padding-left: 0.5em; padding-left: 0.5em;
} }
} }

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

@ -1,10 +1,21 @@
body, @mixin fullpage {
html {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%; height: 100%;
} }
html {
@include fullpage;
line-height: 1.35;
}
body {
@include fullpage;
overflow-x: hidden;
background-color: transparent !important;
-webkit-font-smoothing: antialiased;
}
.clipForScreenReader { .clipForScreenReader {
clip: rect(1px, 1px, 1px, 1px); clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%); clip-path: inset(50%);
@ -18,7 +29,7 @@ html {
.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 {
@ -30,26 +41,12 @@ html {
contain: strict; contain: strict;
} }
html {
line-height: 1.35;
}
.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;
} }
body {
overflow-x: hidden;
background-color: transparent !important;
-webkit-font-smoothing: antialiased;
}
.mainAnimatedPage { .mainAnimatedPage {
contain: style size !important; contain: style size !important;
} }
@ -62,7 +59,7 @@ body {
overflow-y: hidden !important; overflow-y: hidden !important;
} }
div[data-role=page] { div[data-role="page"] {
outline: 0; outline: 0;
} }
@ -75,10 +72,10 @@ div[data-role=page] {
padding-left: 0.15em; padding-left: 0.15em;
font-weight: 400; font-weight: 400;
white-space: normal !important; white-space: normal !important;
}
.fieldDescription + .fieldDescription { + .fieldDescription {
margin-top: 0.3em; margin-top: 0.3em;
}
} }
.content-primary, .content-primary,
@ -89,9 +86,14 @@ div[data-role=page] {
padding-bottom: 5em !important; padding-bottom: 5em !important;
} }
@media all and (min-width: 50em) { .readOnlyContent {
.readOnlyContent, @media all and (min-width: 50em) {
form { max-width: 54em;
}
}
form {
@media all and (min-width: 50em) {
max-width: 54em; max-width: 54em;
} }
} }
@ -111,12 +113,39 @@ div[data-role=page] {
.headroom { .headroom {
will-change: transform; will-change: transform;
transition: transform 200ms linear; transition: transform 200ms linear;
&--pinned {
transform: translateY(0%);
}
&--unpinned {
transform: translateY(-100%);
}
} }
.headroom--pinned { .drawerContent {
transform: translateY(0%); /* make sure the bottom of the drawer is visible when music is playing */
padding-bottom: 4em;
} }
.headroom--unpinned { .force-scroll {
transform: translateY(-100%); 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

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

View file

@ -0,0 +1,4 @@
export default {
isNativeApp: false
};

View file

@ -0,0 +1,82 @@
import { ConnectionManager, Credentials, ApiClient, Events } from 'jellyfin-apiclient';
import { appHost } from './apphost';
import Dashboard from '../scripts/clientUtils';
import AppInfo from './AppInfo';
import { setUserInfo } from '../scripts/settings/userSettings';
class ServerConnections extends ConnectionManager {
constructor() {
super(...arguments);
this.localApiClient = null;
Events.on(this, 'localusersignedout', function () {
setUserInfo(null, null);
});
}
initApiClient() {
if (!AppInfo.isNativeApp) {
console.debug('creating ApiClient singleton');
const apiClient = new ApiClient(
Dashboard.serverAddress(),
appHost.appName(),
appHost.appVersion(),
appHost.deviceName(),
appHost.deviceId()
);
apiClient.enableAutomaticNetworking = false;
apiClient.manualAddressOnly = true;
this.addApiClient(apiClient);
this.setLocalApiClient(apiClient);
console.debug('loaded ApiClient singleton');
}
}
setLocalApiClient(apiClient) {
if (apiClient) {
this.localApiClient = apiClient;
window.ApiClient = apiClient;
}
}
getLocalApiClient() {
return this.localApiClient;
}
currentApiClient() {
let apiClient = this.getLocalApiClient();
if (!apiClient) {
const server = this.getLastUsedServer();
if (server) {
apiClient = this.getApiClient(server.Id);
}
}
return apiClient;
}
onLocalUserSignedIn(user) {
const apiClient = this.getApiClient(user.ServerId);
this.setLocalApiClient(apiClient);
return setUserInfo(user.Id, apiClient);
}
}
const credentials = new Credentials();
const capabilities = Dashboard.capabilities(appHost);
export default new ServerConnections(
credentials,
appHost.appName(),
appHost.appVersion(),
appHost.deviceName(),
appHost.deviceId(),
capabilities);

View file

@ -0,0 +1,98 @@
/* eslint-disable indent */
/**
* Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule
*/
import dialogHelper from '../dialogHelper/dialogHelper';
import datetime from '../../scripts/datetime';
import globalize from '../../scripts/globalize';
import '../../elements/emby-select/emby-select';
import '../../elements/emby-button/paper-icon-button-light';
import '../formdialog.css';
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('./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

@ -1,30 +1,22 @@
import dialogHelper from 'dialogHelper'; import dialogHelper from '../dialogHelper/dialogHelper';
import layoutManager from 'layoutManager'; import layoutManager from '../layoutManager';
import globalize from 'globalize'; import globalize from '../../scripts/globalize';
import dom from 'dom'; import dom from '../../scripts/dom';
import 'emby-button'; import '../../elements/emby-button/emby-button';
import 'css!./actionsheet'; import './actionSheet.css';
import 'material-icons'; import 'material-design-icons-iconfont';
import 'scrollStyles'; import '../../assets/css/scrollstyles.css';
import 'listViewStyle'; import '../../components/listview/listview.css';
function getOffsets(elems) { function getOffsets(elems) {
const results = [];
let results = [];
if (!document) { if (!document) {
return results; return results;
} }
let box; for (const elem of elems) {
for (let elem of elems) { const box = elem.getBoundingClientRect();
// 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.push({ results.push({
top: box.top, top: box.top,
@ -38,12 +30,11 @@ function getOffsets(elems) {
} }
function getPosition(options, dlg) { function getPosition(options, dlg) {
const windowSize = dom.getWindowSize(); const windowSize = dom.getWindowSize();
const windowHeight = windowSize.innerHeight; const windowHeight = windowSize.innerHeight;
const windowWidth = windowSize.innerWidth; const windowWidth = windowSize.innerWidth;
let pos = getOffsets([options.positionTo])[0]; const pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') { if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2; pos.top += (pos.height || 0) / 2;
@ -80,19 +71,18 @@ function getPosition(options, dlg) {
} }
function centerFocus(elem, horiz, on) { function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) { import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off'; const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz); scrollHelper.centerFocus[fn](elem, horiz);
}); });
} }
export function show(options) { export function show(options) {
// items // items
// positionTo // positionTo
// showCancel // showCancel
// title // title
let dialogOptions = { const dialogOptions = {
removeOnClose: true, removeOnClose: true,
enableHistory: options.enableHistory, enableHistory: options.enableHistory,
scrollY: false scrollY: false
@ -105,7 +95,6 @@ export function show(options) {
isFullscreen = true; isFullscreen = true;
dialogOptions.autoFocus = true; dialogOptions.autoFocus = true;
} else { } else {
dialogOptions.modal = false; dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation; dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation; dialogOptions.exitAnimation = options.exitAnimation;
@ -114,7 +103,7 @@ export function show(options) {
dialogOptions.autoFocus = false; dialogOptions.autoFocus = false;
} }
let dlg = dialogHelper.createDialog(dialogOptions); const dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) { if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen'); dlg.classList.add('actionsheet-fullscreen');
@ -140,10 +129,9 @@ export function show(options) {
} }
let renderIcon = false; let renderIcon = false;
let icons = []; const icons = [];
let itemIcon; let itemIcon;
for (let item of options.items) { for (const item of options.items) {
itemIcon = item.icon || (item.selected ? 'check' : null); itemIcon = item.icon || (item.selected ? 'check' : null);
if (itemIcon) { if (itemIcon) {
@ -153,7 +141,9 @@ export function show(options) {
} }
if (layoutManager.tv) { 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>'; 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 // If any items have an icon, give them all an icon just to make sure they're all lined up evenly
@ -166,7 +156,6 @@ export function show(options) {
} }
if (options.title) { if (options.title) {
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>'; html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
} }
if (options.text) { if (options.text) {
@ -197,10 +186,11 @@ export function show(options) {
menuItemClass += ' actionsheet-xlargeFont'; menuItemClass += ' actionsheet-xlargeFont';
} }
for (let [i, item] of options.items.entries()) { // '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) { if (item.divider) {
html += '<div class="actionsheetDivider"></div>'; html += '<div class="actionsheetDivider"></div>';
continue; continue;
} }
@ -214,7 +204,7 @@ export function show(options) {
itemIcon = icons[i]; itemIcon = icons[i];
if (itemIcon) { if (itemIcon) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ' + itemIcon + '"></span>'; html += `<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons ${itemIcon}"></span>`;
} else if (renderIcon && !center) { } else if (renderIcon && !center) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>'; html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
} }
@ -226,13 +216,13 @@ export function show(options) {
html += '</div>'; html += '</div>';
if (item.secondaryText) { if (item.secondaryText) {
html += '<div class="listItemBodyText secondary">' + item.secondaryText + '</div>'; html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
} }
html += '</div>'; html += '</div>';
if (item.asideText) { if (item.asideText) {
html += '<div class="listItemAside actionSheetItemAsideText">' + item.asideText + '</div>'; html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
} }
html += '</button>'; html += '</button>';
@ -240,7 +230,7 @@ export function show(options) {
if (options.showCancel) { if (options.showCancel) {
html += '<div class="buttons">'; html += '<div class="buttons">';
html += '<button is="emby-button" type="button" class="btnCloseActionSheet">' + globalize.translate('ButtonCancel') + '</button>'; html += `<button is="emby-button" type="button" class="btnCloseActionSheet">${globalize.translate('ButtonCancel')}</button>`;
html += '</div>'; html += '</div>';
} }
html += '</div>'; html += '</div>';
@ -251,15 +241,13 @@ export function show(options) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true); centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
} }
let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet'); const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) { if (btnCloseActionSheet) {
btnCloseActionSheet.addEventListener('click', function () { btnCloseActionSheet.addEventListener('click', function () {
dialogHelper.close(dlg); dialogHelper.close(dlg);
}); });
} }
// Seeing an issue in some non-chrome browsers where this is requiring a double click
//var eventName = browser.firefox ? 'mousedown' : 'click';
let selectedId; let selectedId;
let timeout; let timeout;
@ -270,26 +258,20 @@ export function show(options) {
} }
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
let isResolved; let isResolved;
dlg.addEventListener('click', function (e) { dlg.addEventListener('click', function (e) {
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem'); const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
if (actionSheetMenuItem) { if (actionSheetMenuItem) {
selectedId = actionSheetMenuItem.getAttribute('data-id'); selectedId = actionSheetMenuItem.getAttribute('data-id');
if (options.resolveOnClick) { if (options.resolveOnClick) {
if (options.resolveOnClick.indexOf) { if (options.resolveOnClick.indexOf) {
if (options.resolveOnClick.indexOf(selectedId) !== -1) { if (options.resolveOnClick.indexOf(selectedId) !== -1) {
resolve(selectedId); resolve(selectedId);
isResolved = true; isResolved = true;
} }
} else { } else {
resolve(selectedId); resolve(selectedId);
isResolved = true; isResolved = true;
@ -298,11 +280,9 @@ export function show(options) {
dialogHelper.close(dlg); dialogHelper.close(dlg);
} }
}); });
dlg.addEventListener('close', function () { dlg.addEventListener('close', function () {
if (layoutManager.tv) { if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false); centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
} }

View file

@ -1,13 +1,23 @@
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 'jellyfin-apiclient';
'use strict'; import globalize from '../scripts/globalize';
import dom from '../scripts/dom';
import * as datefns from 'date-fns';
import dfnshelper from '../scripts/dfnshelper';
import serverNotifications from '../scripts/serverNotifications';
import '../elements/emby-button/emby-button';
import './listview/listview.css';
import ServerConnections from './ServerConnections';
import alert from './alert';
/* eslint-disable indent */
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 +44,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 +61,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 +85,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 +102,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 +110,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,43 +129,43 @@ define(['events', 'globalize', 'dom', 'date-fns', 'dfnshelper', 'userSettings',
} }
function showItemOverview(item) { function showItemOverview(item) {
require(['alert'], function (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 = ServerConnections.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'); ServerConnections.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);
} }
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,17 @@
define(['browser', 'dialog', 'globalize'], function (browser, dialog, globalize) {
'use strict'; import browser from '../scripts/browser';
import dialog from './dialog/dialog';
import globalize from '../scripts/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 +24,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 +34,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 +44,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 '../../scripts/dom';
import './style.css';
import '../../elements/emby-button/paper-icon-button-light';
import 'material-design-icons-iconfont';
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

@ -12,7 +12,6 @@
.alphaPicker-fixed { .alphaPicker-fixed {
position: fixed; position: fixed;
bottom: 5.5em; bottom: 5.5em;
z-index: 999999;
} }
.alphaPickerRow { .alphaPickerRow {

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 './appFooter.css';
'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 new appFooter({});
});

File diff suppressed because it is too large Load diff

View file

@ -1,439 +1,412 @@
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
'use strict';
function getBaseProfileOptions(item) { import appSettings from '../scripts/settings/appSettings';
var disableHlsVideoAudioCodecs = []; import browser from '../scripts/browser';
import { Events } from 'jellyfin-apiclient';
import * as htmlMediaHelper from '../components/htmlMediaHelper';
import * as webSettings from '../scripts/settings/webSettings';
import globalize from '../scripts/globalize';
import profileBuilder from '../scripts/browserDeviceProfile';
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) { function getBaseProfileOptions(item) {
if (browser.edge || browser.msie) { const disableHlsVideoAudioCodecs = [];
disableHlsVideoAudioCodecs.push('mp3');
}
disableHlsVideoAudioCodecs.push('ac3'); if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
disableHlsVideoAudioCodecs.push('eac3'); if (browser.edge) {
disableHlsVideoAudioCodecs.push('opus'); disableHlsVideoAudioCodecs.push('mp3');
} }
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) {
let profile;
if (self.Windows) { if (window.NativeShell) {
return getDeviceProfileForWindowsUwp(item); profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else {
const builderOpts = getBaseProfileOptions(item);
profile = profileBuilder(builderOpts);
} }
return new Promise(function (resolve) { resolve(profile);
require(['browserdeviceprofile'], function (profileBuilder) { });
var profile; }
if (window.NativeShell) { function escapeRegExp(str) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder); return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
} else { }
var builderOpts = getBaseProfileOptions(item);
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
profile = profileBuilder(builderOpts);
}
resolve(profile); function replaceAll(originalString, strReplace, strWith) {
}); const strReplace2 = escapeRegExp(strReplace);
}); const reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith);
}
function generateDeviceId() {
const keys = [];
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), window.btoa) {
const result = replaceAll(btoa(keys.join('|')), '=', '1');
return result;
} }
function escapeRegExp(str) { return new Date().getTime();
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); }
}
function replaceAll(originalString, strReplace, strWith) { function getDeviceId() {
var strReplace2 = escapeRegExp(strReplace); if (!deviceId) {
var reg = new RegExp(strReplace2, 'ig'); const key = '_deviceId2';
return originalString.replace(reg, strWith);
}
function generateDeviceId() { deviceId = appSettings.get(key);
var keys = [];
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) { if (!deviceId) {
var result = replaceAll(btoa(keys.join('|')), '=', '1'); deviceId = generateDeviceId();
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); appSettings.set(key, deviceId);
return deviceId; }
});
} }
function getDeviceName() { return deviceId;
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';
function getDeviceName() {
if (!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) { if (browser.ipad) {
deviceName += ' iPad'; deviceName += ' iPad';
} else { } else if (browser.iphone) {
if (browser.iphone) { deviceName += ' iPhone';
deviceName += ' iPhone'; } else if (browser.android) {
} else { deviceName += ' Android';
if (browser.android) {
deviceName += ' Android';
}
}
} }
return deviceName;
} }
function supportsVoiceInput() { return deviceName;
if (!browser.tv) { }
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
}
function supportsVoiceInput() {
if (!browser.tv) {
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
}
return false;
}
function supportsFullscreen() {
if (browser.tv) {
return false; 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('../components/actionSheet/actionSheet').then((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(); export 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();
}
return {
deviceId: getDeviceId(),
deviceName: getDeviceName()
};
},
deviceName: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : getDeviceName();
},
deviceId: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : getDeviceId();
},
appName: function () {
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
},
appVersion: function () {
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
},
getPushTokenInfo: function () {
return {};
},
setUserScalable: function (scalable) {
if (!browser.tv) {
const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
document.querySelector('meta[name=viewport]').setAttribute('content', att);
}
} }
};
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);
}
// load app host on module load
appHost.init();

View file

@ -5,8 +5,8 @@
* @module components/autoFocuser * @module components/autoFocuser
*/ */
import focusManager from 'focusManager'; import focusManager from './focusManager';
import layoutManager from 'layoutManager'; import layoutManager from './layoutManager';
/** /**
* Previously selected element. * Previously selected element.

View file

@ -1,5 +1,11 @@
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) { import browser from '../../scripts/browser';
'use strict'; import { playbackManager } from '../playback/playbackmanager';
import dom from '../../scripts/dom';
import * as userSettings from '../../scripts/settings/userSettings';
import './backdrop.css';
import ServerConnections from '../ServerConnections';
/* eslint-disable indent */
function enableAnimation(elem) { function enableAnimation(elem) {
if (browser.slow) { if (browser.slow) {
@ -22,71 +28,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 +106,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 +114,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 +124,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 +140,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 +169,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 +177,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 = ServerConnections.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 +190,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 +204,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 +230,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 +243,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 +280,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 +290,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 +300,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 +313,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
};

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