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 comic-reader

This commit is contained in:
dkanada 2020-08-28 23:01:01 +09:00
commit d92eceb45f
530 changed files with 43583 additions and 42733 deletions

View file

@ -0,0 +1,63 @@
jobs:
- job: Build
displayName: 'Build'
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Install Node'
inputs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Check Cache'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
displayName: 'Build Production'
condition: eq(variables['BuildConfiguration'], 'production')
- script: 'yarn build:standalone'
displayName: 'Build Standalone'
condition: eq(variables['BuildConfiguration'], 'standalone')
- script: 'test -d dist'
displayName: 'Check Build'
- script: 'mv dist jellyfin-web'
displayName: 'Rename Directory'
- task: ArchiveFiles@2
displayName: 'Archive Directory'
inputs:
rootFolderOrFile: 'jellyfin-web'
includeRootFolder: true
archiveFile: 'jellyfin-web-$(BuildConfiguration)'
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
inputs:
targetPath: '$(Build.SourcesDirectory)/jellyfin-web-$(BuildConfiguration).zip'
artifactName: 'jellyfin-web-$(BuildConfiguration)'

View file

@ -0,0 +1,29 @@
jobs:
- job: Lint
displayName: 'Lint'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Install Node'
inputs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Check Cache'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn run lint --quiet'
displayName: 'Run ESLint'
- script: 'yarn run stylelint'
displayName: 'Run Stylelint'

View file

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

View file

@ -12,94 +12,6 @@ pr:
- '*'
jobs:
- job: Build
displayName: 'Build'
strategy:
matrix:
Development:
BuildConfiguration: development
Production:
BuildConfiguration: production
Standalone:
BuildConfiguration: standalone
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Install Node'
inputs:
versionSpec: '12.x'
- task: Cache@2
displayName: 'Check Cache'
inputs:
key: 'yarn | yarn.lock'
path: 'node_modules'
cacheHitVar: CACHE_RESTORED
- script: 'yarn install --frozen-lockfile'
displayName: 'Install Dependencies'
condition: ne(variables.CACHE_RESTORED, 'true')
- script: 'yarn build:development'
displayName: 'Build Development'
condition: eq(variables['BuildConfiguration'], 'development')
- script: 'yarn build:production'
displayName: 'Build 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'
- template: azure-pipelines-build.yml
- template: azure-pipelines-lint.yml
- template: azure-pipelines-package.yml

View file

@ -2,4 +2,4 @@ version: 1
update_configs:
- package_manager: "javascript"
directory: "/"
update_schedule: "weekly"
update_schedule: "live"

View file

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

View file

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

View file

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

4
.github/CODEOWNERS vendored
View file

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

View file

@ -1,23 +1,20 @@
---
name: Bug report
about: Create a bug report
title: ''
name: Bug Report
about: You have noticed a general issue or regression, and would like to report it
labels: bug
assignees: ''
---
**Describe the bug**
**Describe The Bug**
<!-- A clear and concise description of what the bug is. -->
**To Reproduce**
**Steps To Reproduce**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
**Expected Behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
@ -27,9 +24,9 @@ assignees: ''
<!-- If applicable, add screenshots to help explain your problem. -->
**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]
- 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. -->

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.

5
.gitignore vendored
View file

@ -1,6 +1,3 @@
# config
config.json
# npm
dist
web
@ -10,5 +7,5 @@ node_modules
.idea
.vscode
#log
# log
yarn-error.log

View file

@ -35,7 +35,9 @@
- [Thibault Nocchi](https://github.com/ThibaultNocchi)
- [MrTimscampi](https://github.com/MrTimscampi)
- [Sarab Singh](https://github.com/sarab97)
- [GuilhermeHideki](https://github.com/GuilhermeHideki)
- [Andrei Oanca](https://github.com/OancaAndrei)
- [Cromefire_](https://github.com/cromefire)
# Emby Contributors

View file

@ -44,6 +44,7 @@ Jellyfin Web is the frontend used for most of the clients available for end user
### Dependencies
- [Node.js](https://nodejs.org/en/download/)
- [Yarn 1.22.4](https://classic.yarnpkg.com/en/docs/install)
- Gulp-cli

View file

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

View file

@ -4,6 +4,7 @@
set -o errexit
set -o pipefail
set -o xtrace
usage() {
echo -e "bump_version - increase the shared version and generate changelogs"
@ -23,10 +24,7 @@ build_file="./build.yaml"
new_version="$1"
# Parse the version from shared version file
old_version="$(
grep "appVersion" ${shared_version_file} | head -1 \
| sed -E 's/var appVersion = "([0-9\.]+)";/\1/'
)"
old_version="$( grep "appVersion" ${shared_version_file} | head -1 | sed -E "s/var appVersion = '([0-9\.]+)';/\1/" | tr -d '[:space:]' )"
echo "Old version in appHost is: $old_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}" )"
sed -i "s/${old_version_sed}/${new_version_sed}/g" ${shared_version_file}
old_version="$(
grep "version:" ${build_file} \
| sed -E 's/version: "([0-9\.]+[-a-z0-9]*)"/\1/'
)"
echo "Old version in ${build_file}: $old_version`"
old_version="$( grep "version:" ${build_file} | 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
old_version_sed="$( sed 's/\./\\./g' <<<"${old_version}" )" # Escape the '.' chars
@ -54,7 +49,7 @@ fi
debian_changelog_file="debian/changelog"
debian_changelog_temp="$( mktemp )"
# 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}
@ -65,15 +60,15 @@ cat ${debian_changelog_file} >> ${debian_changelog_temp}
mv ${debian_changelog_temp} ${debian_changelog_file}
# 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_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
cp ${fedora_spec_file} ${fedora_spec_temp_dir}/
pushd ${fedora_spec_temp_dir}
# 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
sed -i "s/${old_version_sed}/${new_version_sed}/g" xx00
# 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}
# 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

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
* New upstream version 10.6.0; release changelog at https://github.com/jellyfin/jellyfin-web/releases/tag/v10.6.0

View file

@ -1,7 +1,9 @@
FROM centos:7
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@ -9,19 +11,19 @@ ENV IS_DOCKER=YES
# Prepare CentOS environment
RUN yum update -y \
&& yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
&& yum install -y epel-release \
&& yum install -y @buildsys-build rpmdevtools git yum-plugins-core nodejs-yarn autoconf automake glibc-devel
# Install recent NodeJS and Yarn
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 \
&& yum install -y yarn
&& rpm -i https://rpm.nodesource.com/pub_10.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
&& yum install -y yarn
# 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"]

View file

@ -1,7 +1,9 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@ -10,16 +12,16 @@ ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y debhelper mmv npm git
&& apt-get install -y debhelper mmv npm git
# Prepare Yarn
RUN npm install -g yarn
# 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"]

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
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@ -9,13 +11,13 @@ ENV IS_DOCKER=YES
# Prepare Fedora environment
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
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"]

View file

@ -1,16 +1,17 @@
FROM debian:10
# Docker build arguments
ARG SOURCE_DIR=/jellyfin
ARG ARTIFACT_DIR=/dist
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
ENV DEB_BUILD_OPTIONS=noddebs
ENV IS_DOCKER=YES
# Prepare Debian build environment
RUN apt-get update \
&& apt-get install -y mmv npm git
&& apt-get install -y mmv npm git
# Prepare Yarn
RUN npm install -g yarn
@ -18,8 +19,8 @@ RUN npm install -g yarn
# Link to build script
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"]

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
#= Portable .NET DLL .tar.gz
set -o errexit
set -o xtrace
# Move to source directory
# move to source directory
pushd ${SOURCE_DIR}
# Get version
version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )"
# get version
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
mv dist/ jellyfin-web_${version}
mv dist jellyfin-web_${version}
tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version}
rm -rf dist/
rm -rf dist
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}/
# move the artifacts
mkdir -p ${ARTIFACT_DIR}
mv jellyfin[-_]*.tar.gz ${ARTIFACT_DIR}
if [[ ${IS_DOCKER} == YES ]]; then
chown -Rc $(stat -c %u:%g ${ARTIFACT_DIR}) ${ARTIFACT_DIR}

View file

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

View file

@ -181,16 +181,15 @@ function copy(query) {
.pipe(browserSync.stream());
}
function copyIndex() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(dest('dist/'))
.pipe(browserSync.stream());
}
function injectBundle() {
return src(options.injectBundle.query, { base: './src/' })
.pipe(inject(
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), { relative: true }
src(['src/scripts/apploader.js'], { read: false }, { base: './src/' }), {
relative: true,
transform: function (filepath) {
return `<script src="${filepath}" defer></script>`;
}
}
))
.pipe(dest('dist/'))
.pipe(browserSync.stream());
@ -200,6 +199,6 @@ function build(standalone) {
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.serve = series(exports.standalone, serve);

View file

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

33
scripts/duplicates.py Normal file
View file

@ -0,0 +1,33 @@
import sys
import os
import json
# load every string in the source language
# print all duplicate values to a file
cwd = os.getcwd()
source = cwd + '/../src/strings/en-us.json'
reverse = {}
duplicates = {}
with open(source) as en:
strings = json.load(en)
for key, value in strings.items():
if value not in reverse:
reverse[value] = [key]
else:
reverse[value].append(key)
for key, value in reverse.items():
if len(value) > 1:
duplicates[key] = value
print('LENGTH: ' + str(len(duplicates)))
with open('duplicates.txt', 'w') as out:
for item in duplicates:
out.write(json.dumps(item) + ': ')
out.write(json.dumps(duplicates[item]) + '\n')
out.close()
print('DONE')

View file

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

View file

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

View file

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

View file

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

View file

@ -24,10 +24,6 @@
padding-top: 7em !important;
}
.layout-mobile .libraryPage {
padding-top: 4em !important;
}
.itemDetailPage {
padding-top: 0 !important;
}
@ -164,6 +160,7 @@
display: flex;
flex-direction: column;
contain: layout style paint;
transition: background ease-in-out 0.5s;
}
.hiddenViewMenuBar .skinHeader {
@ -178,6 +175,10 @@
width: 100%;
}
.layout-tv .sectionTabs {
width: 55%;
}
.selectedMediaFolder {
background-color: #f2f2f2 !important;
}
@ -235,12 +236,6 @@
text-align: center;
}
.layout-desktop .searchTabButton,
.layout-mobile .searchTabButton,
.layout-tv .headerSearchButton {
display: none !important;
}
.mainDrawer-scrollContainer {
padding-bottom: 10vh;
}
@ -272,7 +267,7 @@
}
}
@media all and (max-width: 84em) {
@media all and (max-width: 100em) {
.withSectionTabs .headerTop {
padding-bottom: 0.55em;
}
@ -280,9 +275,13 @@
.sectionTabs {
font-size: 83.5%;
}
.layout-tv .sectionTabs {
width: 100%;
}
}
@media all and (min-width: 84em) {
@media all and (min-width: 100em) {
.headerTop {
padding: 0.8em 0.8em;
}
@ -438,12 +437,14 @@
background-repeat: no-repeat;
background-position: center;
background-attachment: fixed;
height: 50vh;
height: 40vh;
position: relative;
animation: backdrop-fadein 800ms ease-in normal both;
}
.layout-mobile .itemBackdrop {
background-attachment: scroll;
height: 26.5vh;
}
.layout-desktop .itemBackdrop::after,
@ -463,10 +464,20 @@
.detailPageContent {
display: flex;
flex-direction: column;
padding-left: 2%;
padding-left: 32.45vw;
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-tv .noBackdrop .detailPageContent {
margin-top: 2.5em;
@ -477,6 +488,10 @@
margin-top: 0;
}
.detailSectionContent a {
color: inherit;
}
.personBackdrop {
background-size: contain;
}
@ -495,7 +510,23 @@
.parentName {
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 {
@ -503,8 +534,6 @@
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
-webkit-flex-wrap: wrap;
flex-wrap: wrap;
margin: 1em 0;
}
@ -520,6 +549,35 @@
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 {
display: flex;
flex-direction: column;
@ -546,6 +604,19 @@
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 {
display: flex;
align-items: center;
@ -556,7 +627,7 @@
.layout-mobile .detailPagePrimaryContainer {
display: block;
position: relative;
top: 0;
padding: 0.5em 3.3% 0.5em;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
@ -566,13 +637,14 @@
padding-left: 32.45vw;
}
.layout-desktop .detailSticky,
.layout-tv .detailSticky {
.layout-desktop .detailRibbon,
.layout-tv .detailRibbon {
margin-top: -7.2em;
height: 7.2em;
}
.layout-desktop .noBackdrop .detailSticky,
.layout-tv .noBackdrop .detailSticky {
.layout-desktop .noBackdrop .detailRibbon,
.layout-tv .noBackdrop .detailRibbon {
margin-top: 0;
}
@ -584,6 +656,9 @@
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
min-width: 0;
max-width: 100%;
overflow: hidden;
}
.layout-mobile .infoText {
@ -594,12 +669,29 @@
margin: 1.25em 0;
}
.detailImageContainer {
position: relative;
margin-top: -25vh;
.layout-mobile .detailPageSecondaryContainer {
margin: 1em 0;
}
.layout-mobile .detailImageContainer {
display: none;
}
.detailImageContainer .card {
position: absolute;
top: 50%;
float: left;
width: 25vw;
z-index: 3;
transform: translateY(-50%);
}
.detailImageContainer .card.backdropCard {
top: 35%;
}
.detailImageContainer .card.squareCard {
top: 40%;
}
.layout-desktop .noBackdrop .detailImageContainer,
@ -612,11 +704,11 @@
}
.detailLogo {
width: 30vw;
height: 25vh;
width: 25vw;
height: 16vh;
position: absolute;
top: 10vh;
right: 20vw;
right: 25vw;
background-size: contain;
}
@ -641,7 +733,8 @@ div.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 {
@ -655,14 +748,19 @@ div.itemDetailGalleryLink.defaultCardBackground {
position: relative;
}
.layout-desktop .detailPageWrapperContainer,
.layout-tv .detailPageWrapperContainer {
margin-top: 7.2em;
.layout-desktop .itemBackdrop,
.layout-tv .itemBackdrop {
height: 40vh;
}
.layout-tv #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer,
.layout-desktop #itemDetailPage:not(.noBackdrop) .detailPagePrimaryContainer {
padding-left: 3.3%;
.layout-desktop .detailPageWrapperContainer,
.layout-tv .detailPageWrapperContainer {
margin-top: 0.1em;
}
.layout-desktop .detailImageContainer .card,
.layout-tv .detailImageContainer .card {
top: 10%;
}
.btnPlaySimple {
@ -678,12 +776,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
.emby-button.detailFloatingButton {
position: absolute;
background-color: rgba(0, 0, 0, 0.5) !important;
z-index: 1;
top: 50%;
left: 50%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 3;
top: 100%;
left: 90%;
margin: -2.2em 0 0 -2.2em;
padding: 0.4em !important;
padding: 0.4em;
color: rgba(255, 255, 255, 0.76);
}
@ -693,16 +791,12 @@ div.itemDetailGalleryLink.defaultCardBackground {
@media all and (max-width: 62.5em) {
.parentName {
margin-bottom: 1em;
margin-bottom: 0;
}
.itemDetailPage {
padding-top: 0 !important;
}
.detailimg-hidemobile {
display: none;
}
}
@media all and (min-width: 31.25em) {
@ -797,9 +891,9 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
.detailImageProgressContainer {
position: absolute;
bottom: 0;
width: 22.786458333333332vw;
margin-top: -0.4vw;
width: 100%;
}
.detailButton-text {
@ -818,21 +912,7 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
}
@media all and (min-width: 62.5em) {
.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;
}
@media all and (min-width: 100em) {
.detailFloatingButton {
display: none !important;
}
@ -866,6 +946,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
}
}
.detailVerticalSection .emby-scrollbuttons {
padding-top: 0.4em;
}
.layout-tv .detailVerticalSection {
margin-bottom: 3.4em !important;
}
@ -954,6 +1038,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
margin-bottom: 2.7em;
}
.layout-mobile .verticalSection-extrabottompadding {
margin-bottom: 1em;
}
.sectionTitleButton,
.sectionTitleIconButton {
margin-right: 0 !important;
@ -979,7 +1067,13 @@ div.itemDetailGalleryLink.defaultCardBackground {
div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
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 {
@ -1132,6 +1226,12 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
.trackSelections .selectContainer .selectLabel {
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 {
@ -1144,3 +1244,21 @@ div:not(.sectionTitleContainer-cards) > .sectionTitle-cards {
margin-top: 0;
font-size: 1.4em;
}
.overview-controls {
display: flex;
justify-content: flex-end;
}
.detail-clamp-text {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 12;
-webkit-box-orient: vertical;
}
@media all and (min-width: 40em) {
.detail-clamp-text {
-webkit-line-clamp: 6;
}
}

View file

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

View file

@ -5,6 +5,12 @@ html {
height: 100%;
}
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
user-select: none;
}
.clipForScreenReader {
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
@ -18,7 +24,7 @@ html {
.material-icons {
/* Fix font ligatures on older WebOS versions */
-webkit-font-feature-settings: "liga";
font-feature-settings: "liga";
}
.backgroundContainer {
@ -34,16 +40,6 @@ html {
line-height: 1.35;
}
.layout-mobile,
.layout-tv {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
body {
overflow-x: hidden;
background-color: transparent !important;
@ -121,6 +117,11 @@ div[data-role=page] {
transform: translateY(-100%);
}
.drawerContent {
/* make sure the bottom of the drawer is visible when music is playing */
padding-bottom: 4em;
}
.force-scroll {
overflow-y: scroll;
}
@ -128,3 +129,17 @@ div[data-role=page] {
.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

@ -7,7 +7,6 @@
}
.osdPoster img,
.pageContainer,
.videoOsdBottom {
bottom: 0;
left: 0;
@ -248,11 +247,6 @@
animation: spin 4s linear infinite;
}
.pageContainer {
top: 0;
position: fixed;
}
@media all and (max-width: 30em) {
.btnFastForward,
.btnRewind,

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

View file

@ -2,160 +2,159 @@
* 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;
});
const _define = window.define;
// fetch
var fetch = require('whatwg-fetch');
const fetch = require('whatwg-fetch');
_define('fetch', function() {
return fetch;
});
// Blurhash
const blurhash = require('blurhash');
_define('blurhash', function() {
return blurhash;
});
// query-string
var query = require('query-string');
const query = require('query-string');
_define('queryString', function() {
return query;
});
// flvjs
var flvjs = require('flv.js/dist/flv').default;
const flvjs = require('flv.js/dist/flv').default;
_define('flvjs', function() {
return flvjs;
});
// jstree
var jstree = require('jstree');
const jstree = require('jstree');
require('jstree/dist/themes/default/style.css');
_define('jstree', function() {
return jstree;
});
// jquery
var jquery = require('jquery');
const jquery = require('jquery');
_define('jQuery', function() {
return jquery;
});
// hlsjs
var hlsjs = require('hls.js');
const hlsjs = require('hls.js');
_define('hlsjs', function() {
return hlsjs;
});
// howler
var howler = require('howler');
const howler = require('howler');
_define('howler', function() {
return howler;
});
// resize-observer-polyfill
var resize = require('resize-observer-polyfill').default;
const 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');
const swiper = require('swiper/js/swiper');
require('swiper/css/swiper.min.css');
_define('swiper', function() {
return swiper;
});
// sortable
var sortable = require('sortablejs').default;
const sortable = require('sortablejs').default;
_define('sortable', function() {
return sortable;
});
// webcomponents
var webcomponents = require('webcomponents.js/webcomponents-lite');
const webcomponents = require('webcomponents.js/webcomponents-lite');
_define('webcomponents', function() {
return webcomponents;
});
// libass-wasm
var libassWasm = require('libass-wasm');
const libassWasm = require('libass-wasm');
_define('JavascriptSubtitlesOctopus', function() {
return libassWasm;
});
// material-icons
var materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css');
const materialIcons = require('material-design-icons-iconfont/dist/material-design-icons.css');
_define('material-icons', function() {
return materialIcons;
});
// noto font
var noto = require('jellyfin-noto');
const noto = require('jellyfin-noto');
_define('jellyfin-noto', function () {
return noto;
});
const epubjs = require('epubjs');
_define('epubjs', function () {
return epubjs;
});
// page.js
var page = require('page');
const page = require('page');
_define('page', function() {
return page;
});
// core-js
var polyfill = require('@babel/polyfill/dist/polyfill');
const polyfill = require('@babel/polyfill/dist/polyfill');
_define('polyfill', function () {
return polyfill;
});
// domtokenlist-shim
var classlist = require('classlist.js');
const classlist = require('classlist.js');
_define('classlist-polyfill', function () {
return classlist;
});
// Date-FNS
var dateFns = require('date-fns');
const dateFns = require('date-fns');
_define('date-fns', function () {
return dateFns;
});
var dateFnsLocale = require('date-fns/locale');
const dateFnsLocale = require('date-fns/locale');
_define('date-fns/locale', function () {
return dateFnsLocale;
});
var fast_text_encoding = require('fast-text-encoding');
const fast_text_encoding = require('fast-text-encoding');
_define('fast-text-encoding', function () {
return fast_text_encoding;
});
// intersection-observer
var intersection_observer = require('intersection-observer');
const intersection_observer = require('intersection-observer');
_define('intersection-observer', function () {
return intersection_observer;
});
// screenfull
var screenfull = require('screenfull');
const screenfull = require('screenfull');
_define('screenfull', function () {
return screenfull;
});
// headroom.js
var headroom = require('headroom.js/dist/headroom');
const headroom = require('headroom.js/dist/headroom');
_define('headroom', function () {
return headroom;
});
// apiclient
var apiclient = require('jellyfin-apiclient');
const apiclient = require('jellyfin-apiclient');
_define('apiclient', function () {
return apiclient.ApiClient;

View file

@ -1,9 +1,20 @@
define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-button-light', 'formDialogStyle'], function (dialogHelper, datetime, globalize) {
'use strict';
/* eslint-disable indent */
/**
* Module for controlling user parental control from.
* @module components/accessSchedule/accessSchedule
*/
import dialogHelper from 'dialogHelper';
import datetime from 'datetime';
import globalize from 'globalize';
import 'emby-select';
import 'paper-icon-button-light';
import 'formDialogStyle';
function getDisplayTime(hours) {
var minutes = 0;
var pct = hours % 1;
let minutes = 0;
const pct = hours % 1;
if (pct) {
minutes = parseInt(60 * pct);
@ -13,32 +24,32 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt
}
function populateHours(context) {
var html = '';
let html = '';
for (var i = 0; i < 24; i++) {
html += '<option value="' + i + '">' + getDisplayTime(i) + '</option>';
for (let i = 0; i < 24; i++) {
html += `<option value="${i}">${getDisplayTime(i)}</option>`;
}
html += '<option value="24">' + getDisplayTime(0) + '</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 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) {
var updatedSchedule = {
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('ErrorMessageStartHourGreaterThanEnd'));
return void alert(globalize.translate('ErrorStartHourGreaterThanEnd'));
}
context.submitted = true;
@ -46,44 +57,41 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt
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();
export function show(options) {
return new Promise((resolve, reject) => {
import('text!./accessSchedule.template.html').then(({default: template}) => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
size: 'small'
});
dlg.classList.add('formDialog');
let html = '';
html += globalize.translateHtml(template);
dlg.innerHTML = html;
populateHours(dlg);
loadSchedule(dlg, options.schedule);
dialogHelper.open(dlg);
dlg.addEventListener('close', () => {
if (dlg.submitted) {
resolve(options.schedule);
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
dlg.querySelector('form').addEventListener('submit', event => {
submitSchedule(dlg, options);
event.preventDefault();
return false;
});
});
}
};
});
});
}
/* eslint-enable indent */
export default {
show: show
};

View file

@ -1,5 +1,5 @@
<div class="formDialogHeader">
<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>
</button>
<h3 class="formDialogHeaderTitle">
@ -12,13 +12,13 @@
<div class="selectContainer">
<select is="emby-select" id="selectDay" label="${LabelAccessDay}">
<option value="Sunday">${OptionSunday}</option>
<option value="Monday">${OptionMonday}</option>
<option value="Tuesday">${OptionTuesday}</option>
<option value="Wednesday">${OptionWednesday}</option>
<option value="Thursday">${OptionThursday}</option>
<option value="Friday">${OptionFriday}</option>
<option value="Saturday">${OptionSaturday}</option>
<option value="Sunday">${Sunday}</option>
<option value="Monday">${Monday}</option>
<option value="Tuesday">${Tuesday}</option>
<option value="Wednesday">${Wednesday}</option>
<option value="Thursday">${Thursday}</option>
<option value="Friday">${Friday}</option>
<option value="Saturday">${Saturday}</option>
<option value="Everyday">${OptionEveryday}</option>
<option value="Weekday">${OptionWeekdays}</option>
<option value="Weekend">${OptionWeekends}</option>
@ -33,7 +33,7 @@
<div class="formDialogFooter">
<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">
<span>${ButtonAdd}</span>
<span>${Add}</span>
</button>
</div>
</form>

View file

@ -9,22 +9,14 @@ import 'scrollStyles';
import 'listViewStyle';
function getOffsets(elems) {
let results = [];
const results = [];
if (!document) {
return results;
}
let box;
for (let elem of elems) {
// 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 };
}
for (const elem of elems) {
const box = elem.getBoundingClientRect();
results.push({
top: box.top,
@ -38,12 +30,11 @@ function getOffsets(elems) {
}
function getPosition(options, dlg) {
const windowSize = dom.getWindowSize();
const windowHeight = windowSize.innerHeight;
const windowWidth = windowSize.innerWidth;
let pos = getOffsets([options.positionTo])[0];
const pos = getOffsets([options.positionTo])[0];
if (options.positionY !== 'top') {
pos.top += (pos.height || 0) / 2;
@ -80,19 +71,18 @@ function getPosition(options, dlg) {
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
import('scrollHelper').then(({default: scrollHelper}) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export function show(options) {
// items
// positionTo
// showCancel
// title
let dialogOptions = {
const dialogOptions = {
removeOnClose: true,
enableHistory: options.enableHistory,
scrollY: false
@ -105,7 +95,6 @@ export function show(options) {
isFullscreen = true;
dialogOptions.autoFocus = true;
} else {
dialogOptions.modal = false;
dialogOptions.entryAnimation = options.entryAnimation;
dialogOptions.exitAnimation = options.exitAnimation;
@ -114,7 +103,7 @@ export function show(options) {
dialogOptions.autoFocus = false;
}
let dlg = dialogHelper.createDialog(dialogOptions);
const dlg = dialogHelper.createDialog(dialogOptions);
if (isFullscreen) {
dlg.classList.add('actionsheet-fullscreen');
@ -140,10 +129,9 @@ export function show(options) {
}
let renderIcon = false;
let icons = [];
const icons = [];
let itemIcon;
for (let item of options.items) {
for (const item of options.items) {
itemIcon = item.icon || (item.selected ? 'check' : null);
if (itemIcon) {
@ -153,7 +141,9 @@ export function show(options) {
}
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
@ -166,7 +156,6 @@ export function show(options) {
}
if (options.title) {
html += '<h1 class="actionSheetTitle">' + options.title + '</h1>';
}
if (options.text) {
@ -202,7 +191,6 @@ export function show(options) {
const item = options.items[i];
if (item.divider) {
html += '<div class="actionsheetDivider"></div>';
continue;
}
@ -216,7 +204,7 @@ export function show(options) {
itemIcon = icons[i];
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) {
html += '<span class="actionsheetMenuItemIcon listItemIcon listItemIcon-transparent material-icons check" style="visibility:hidden;"></span>';
}
@ -228,13 +216,13 @@ export function show(options) {
html += '</div>';
if (item.secondaryText) {
html += '<div class="listItemBodyText secondary">' + item.secondaryText + '</div>';
html += `<div class="listItemBodyText secondary">${item.secondaryText}</div>`;
}
html += '</div>';
if (item.asideText) {
html += '<div class="listItemAside actionSheetItemAsideText">' + item.asideText + '</div>';
html += `<div class="listItemAside actionSheetItemAsideText">${item.asideText}</div>`;
}
html += '</button>';
@ -242,7 +230,7 @@ export function show(options) {
if (options.showCancel) {
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>';
@ -253,15 +241,13 @@ export function show(options) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, true);
}
let btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
const btnCloseActionSheet = dlg.querySelector('.btnCloseActionSheet');
if (btnCloseActionSheet) {
btnCloseActionSheet.addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
// Seeing an issue in some non-chrome browsers where this is requiring a double click
//var eventName = browser.firefox ? 'mousedown' : 'click';
let selectedId;
let timeout;
@ -272,26 +258,20 @@ export function show(options) {
}
return new Promise(function (resolve, reject) {
let isResolved;
dlg.addEventListener('click', function (e) {
const actionSheetMenuItem = dom.parentWithClass(e.target, 'actionSheetMenuItem');
if (actionSheetMenuItem) {
selectedId = actionSheetMenuItem.getAttribute('data-id');
if (options.resolveOnClick) {
if (options.resolveOnClick.indexOf) {
if (options.resolveOnClick.indexOf(selectedId) !== -1) {
resolve(selectedId);
isResolved = true;
}
} else {
resolve(selectedId);
isResolved = true;
@ -300,11 +280,9 @@ export function show(options) {
dialogHelper.close(dlg);
}
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.actionSheetScroller'), false, false);
}

View file

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

View file

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

View file

@ -1,11 +1,22 @@
define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-button-light', 'material-icons'], function (focusManager, layoutManager, dom) {
'use strict';
/* eslint-disable indent */
var selectedButtonClass = 'alphaPickerButton-selected';
/**
* Module alphaPicker.
* @module components/alphaPicker/alphaPicker
*/
import focusManager from 'focusManager';
import layoutManager from 'layoutManager';
import dom from 'dom';
import 'css!./style.css';
import 'paper-icon-button-light';
import 'material-icons';
const selectedButtonClass = 'alphaPickerButton-selected';
function focus() {
var scope = this;
var selected = scope.querySelector('.' + selectedButtonClass);
const scope = this;
const selected = scope.querySelector(`.${selectedButtonClass}`);
if (selected) {
focusManager.focus(selected);
@ -15,8 +26,7 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
}
function getAlphaPickerButtonClassName(vertical) {
var alphaPickerButtonClassName = 'alphaPickerButton';
let alphaPickerButtonClassName = 'alphaPickerButton';
if (layoutManager.tv) {
alphaPickerButtonClassName += ' alphaPickerButton-tv';
@ -30,44 +40,42 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
}
function getLetterButton(l, vertical) {
return '<button data-value="' + l + '" class="' + getAlphaPickerButtonClassName(vertical) + '">' + l + '</button>';
return `<button data-value="${l}" class="${getAlphaPickerButtonClassName(vertical)}">${l}</button>`;
}
function mapLetters(letters, vertical) {
return letters.map(function (l) {
return letters.map(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');
const vertical = element.classList.contains('alphaPicker-vertical');
if (!vertical) {
element.classList.add('focuscontainer-x');
}
var html = '';
var letters;
let html = '';
let letters;
var alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical);
var rowClassName = 'alphaPickerRow';
let rowClassName = 'alphaPickerRow';
if (vertical) {
rowClassName += ' alphaPickerRow-vertical';
}
html += '<div class="' + rowClassName + '">';
html += `<div class="${rowClassName}">`;
if (options.mode === 'keyboard') {
html += '<button data-value=" " is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>';
html += `<button data-value=" " is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon space_bar"></span></button>`;
} else {
letters = ['#'];
html += mapLetters(letters, vertical).join('');
@ -77,11 +85,11 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
html += mapLetters(letters, vertical).join('');
if (options.mode === 'keyboard') {
html += '<button data-value="backspace" is="paper-icon-button-light" class="' + alphaPickerButtonClassName + '"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>';
html += `<button data-value="backspace" is="paper-icon-button-light" class="${alphaPickerButtonClassName}"><span class="material-icons alphaPickerButtonIcon backspace"></span></button>`;
html += '</div>';
letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
html += '<div class="' + rowClassName + '">';
html += `<div class="${rowClassName}">`;
html += '<br/>';
html += mapLetters(letters, vertical).join('');
html += '</div>';
@ -95,227 +103,211 @@ define(['focusManager', 'layoutManager', 'dom', 'css!./style.css', 'paper-icon-b
element.focus = focus;
}
function AlphaPicker(options) {
export class AlphaPicker {
constructor(options) {
const self = this;
var self = this;
this.options = options;
this.options = options;
var element = options.element;
var itemsContainer = options.itemsContainer;
var itemClass = options.itemClass;
const element = options.element;
const itemsContainer = options.itemsContainer;
const itemClass = options.itemClass;
var itemFocusValue;
var itemFocusTimeout;
let itemFocusValue;
let 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 onItemFocusTimeout() {
itemFocusTimeout = null;
self.value(itemFocusValue);
}
}
function onAlphaPickerInKeyboardModeClick(e) {
let alphaFocusedElement;
let alphaFocusTimeout;
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
function onAlphaFocusTimeout() {
alphaFocusTimeout = null;
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 {
if (document.activeElement === alphaFocusedElement) {
const value = alphaFocusedElement.getAttribute('data-value');
self.value(value, true);
}
}
}
function onAlphaPickerFocusIn(e) {
function onAlphaPickerInKeyboardModeClick(e) {
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
if (alphaPickerButton) {
const value = alphaPickerButton.getAttribute('data-value');
element.dispatchEvent(new CustomEvent('alphavalueclicked', {
cancelable: false,
detail: {
value
}
}));
}
}
var alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
function onAlphaPickerClick(e) {
const 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);
if (alphaPickerButton) {
const value = alphaPickerButton.getAttribute('data-value');
if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) {
this.value(null, true);
} else {
this.value(value, true);
}
itemFocusTimeout = setTimeout(onItemFocusTimeout, 100);
}
}
}
self.enabled = function (enabled) {
if (enabled) {
if (itemsContainer) {
itemsContainer.addEventListener('focus', onItemsFocusIn, true);
function onAlphaPickerFocusIn(e) {
if (alphaFocusTimeout) {
clearTimeout(alphaFocusTimeout);
alphaFocusTimeout = null;
}
if (options.mode === 'keyboard') {
element.addEventListener('click', onAlphaPickerInKeyboardModeClick);
}
const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton');
if (options.valueChangeEvent !== 'click') {
element.addEventListener('focus', onAlphaPickerFocusIn, true);
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 {
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 (itemsContainer) {
itemsContainer.removeEventListener('focus', onItemsFocusIn, true);
}
if (btn && btn !== selected) {
btn.classList.add(selectedButtonClass);
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);
}
}
if (selected && selected !== btn) {
} else {
this._currentValue = value;
selected = element.querySelector(`.${selectedButtonClass}`);
if (selected) {
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;
}
if (applyValue) {
element.dispatchEvent(new CustomEvent('alphavaluechanged', {
cancelable: false,
detail: {
value: value
}
}));
on(name, fn) {
const element = this.options.element;
element.addEventListener(name, fn);
}
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'));
off(name, fn) {
const element = this.options.element;
element.removeEventListener(name, fn);
}
return values;
};
visible(visible) {
const element = this.options.element;
element.style.visibility = visible ? 'visible' : 'hidden';
}
AlphaPicker.prototype.focus = function () {
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'));
}
var element = this.options.element;
focusManager.autoFocus(element, true);
};
return values;
}
AlphaPicker.prototype.destroy = function () {
focus() {
const element = this.options.element;
focusManager.autoFocus(element, true);
}
var element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
};
destroy() {
const element = this.options.element;
this.enabled(false);
element.classList.remove('focuscontainer-x');
this.options = null;
}
}
return AlphaPicker;
});
/* eslint-enable indent */
export default AlphaPicker;

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,438 +1,412 @@
define(['appSettings', 'browser', 'events', 'htmlMediaHelper', 'webSettings', 'globalize'], function (appSettings, browser, events, htmlMediaHelper, webSettings, globalize) {
'use strict';
import appSettings from 'appSettings';
import browser from 'browser';
import events from 'events';
import * as htmlMediaHelper from 'htmlMediaHelper';
import * as webSettings from 'webSettings';
import globalize from 'globalize';
function getBaseProfileOptions(item) {
var disableHlsVideoAudioCodecs = [];
function getBaseProfileOptions(item) {
const disableHlsVideoAudioCodecs = [];
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge || browser.msie) {
disableHlsVideoAudioCodecs.push('mp3');
}
disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push('opus');
if (item && htmlMediaHelper.enableHlsJsPlayer(item.RunTimeTicks, item.MediaType)) {
if (browser.edge) {
disableHlsVideoAudioCodecs.push('mp3');
}
return {
enableMkvProgressive: false,
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
};
disableHlsVideoAudioCodecs.push('ac3');
disableHlsVideoAudioCodecs.push('eac3');
disableHlsVideoAudioCodecs.push('opus');
}
function getDeviceProfileForWindowsUwp(item) {
return new Promise(function (resolve, reject) {
require(['browserdeviceprofile', 'environments/windows-uwp/mediacaps'], function (profileBuilder, uwpMediaCaps) {
var profileOptions = getBaseProfileOptions(item);
profileOptions.supportsDts = uwpMediaCaps.supportsDTS();
profileOptions.supportsTrueHd = uwpMediaCaps.supportsDolby();
profileOptions.audioChannels = uwpMediaCaps.getAudioChannels();
resolve(profileBuilder(profileOptions));
});
});
}
return {
enableMkvProgressive: false,
disableHlsVideoAudioCodecs: disableHlsVideoAudioCodecs
};
}
function getDeviceProfile(item, options) {
options = options || {};
function getDeviceProfile(item, options = {}) {
return new Promise(function (resolve) {
import('browserdeviceprofile').then(({default: profileBuilder}) => {
let profile;
if (self.Windows) {
return getDeviceProfileForWindowsUwp(item);
}
return new Promise(function (resolve) {
require(['browserdeviceprofile'], function (profileBuilder) {
var profile;
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else {
var builderOpts = getBaseProfileOptions(item);
builderOpts.enableSsaRender = (item && !options.isRetry && 'allcomplexformats' !== appSettings.get('subtitleburnin'));
profile = profileBuilder(builderOpts);
}
resolve(profile);
});
});
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
function replaceAll(originalString, strReplace, strWith) {
var strReplace2 = escapeRegExp(strReplace);
var reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith);
}
function generateDeviceId() {
var keys = [];
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), self.btoa) {
var result = replaceAll(btoa(keys.join('|')), '=', '1');
return Promise.resolve(result);
}
return Promise.resolve(new Date().getTime());
}
function getDeviceId() {
var key = '_deviceId2';
var deviceId = appSettings.get(key);
if (deviceId) {
return Promise.resolve(deviceId);
}
return generateDeviceId().then(function (deviceId) {
appSettings.set(key, deviceId);
return deviceId;
});
}
function getDeviceName() {
var deviceName;
deviceName = browser.tizen ? 'Samsung Smart TV' : browser.web0s ? 'LG Smart TV' : browser.operaTv ? 'Opera TV' : browser.xboxOne ? 'Xbox One' : browser.ps4 ? 'Sony PS4' : browser.chrome ? 'Chrome' : browser.edge ? 'Edge' : browser.firefox ? 'Firefox' : browser.msie ? 'Internet Explorer' : browser.opera ? 'Opera' : browser.safari ? 'Safari' : 'Web Browser';
if (browser.ipad) {
deviceName += ' iPad';
} else {
if (browser.iphone) {
deviceName += ' iPhone';
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getDeviceProfile(profileBuilder);
} else {
if (browser.android) {
deviceName += ' Android';
}
const builderOpts = getBaseProfileOptions(item);
builderOpts.enableSsaRender = (item && !options.isRetry && appSettings.get('subtitleburnin') !== 'allcomplexformats');
profile = profileBuilder(builderOpts);
}
}
return deviceName;
resolve(profile);
});
});
}
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$1');
}
function replaceAll(originalString, strReplace, strWith) {
const strReplace2 = escapeRegExp(strReplace);
const reg = new RegExp(strReplace2, 'ig');
return originalString.replace(reg, strWith);
}
function generateDeviceId() {
const keys = [];
if (keys.push(navigator.userAgent), keys.push(new Date().getTime()), window.btoa) {
const result = replaceAll(btoa(keys.join('|')), '=', '1');
return Promise.resolve(result);
}
function supportsVoiceInput() {
if (!browser.tv) {
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
}
return Promise.resolve(new Date().getTime());
}
function getDeviceId() {
const key = '_deviceId2';
const deviceId = appSettings.get(key);
if (deviceId) {
return Promise.resolve(deviceId);
}
return generateDeviceId().then(function (deviceId) {
appSettings.set(key, deviceId);
return deviceId;
});
}
function getDeviceName() {
var deviceName;
if (browser.tizen) {
deviceName = 'Samsung Smart TV';
} else if (browser.web0s) {
deviceName = 'LG Smart TV';
} else if (browser.operaTv) {
deviceName = 'Opera TV';
} else if (browser.xboxOne) {
deviceName = 'Xbox One';
} else if (browser.ps4) {
deviceName = 'Sony PS4';
} else if (browser.chrome) {
deviceName = 'Chrome';
} else if (browser.edgeChromium) {
deviceName = 'Edge Chromium';
} else if (browser.edge) {
deviceName = 'Edge';
} else if (browser.firefox) {
deviceName = 'Firefox';
} else if (browser.opera) {
deviceName = 'Opera';
} else if (browser.safari) {
deviceName = 'Safari';
} else {
deviceName = 'Web Browser';
}
if (browser.ipad) {
deviceName += ' iPad';
} else if (browser.iphone) {
deviceName += ' iPhone';
} else if (browser.android) {
deviceName += ' Android';
}
return deviceName;
}
function supportsVoiceInput() {
if (!browser.tv) {
return window.SpeechRecognition || window.webkitSpeechRecognition || window.mozSpeechRecognition || window.oSpeechRecognition || window.msSpeechRecognition;
}
return false;
}
function supportsFullscreen() {
if (browser.tv) {
return false;
}
function supportsFullscreen() {
if (browser.tv) {
return false;
}
const element = document.documentElement;
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
}
var element = document.documentElement;
return (element.requestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen || element.msRequestFullscreen) || document.createElement('video').webkitEnterFullscreen;
}
function getSyncProfile() {
return new Promise(function (resolve) {
require(['browserdeviceprofile', 'appSettings'], function (profileBuilder, appSettings) {
var profile;
if (window.NativeShell) {
profile = window.NativeShell.AppHost.getSyncProfile(profileBuilder, appSettings);
} else {
profile = profileBuilder();
profile.MaxStaticMusicBitrate = appSettings.maxStaticMusicBitrate();
}
resolve(profile);
});
});
}
function getDefaultLayout() {
return 'desktop';
}
function supportsHtmlMediaAutoplay() {
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
return true;
}
if (browser.mobile) {
return false;
}
function getDefaultLayout() {
return 'desktop';
}
function supportsHtmlMediaAutoplay() {
if (browser.edgeUwp || browser.tizen || browser.web0s || browser.orsay || browser.operaTv || browser.ps4 || browser.xboxOne) {
return true;
}
function supportsCue() {
try {
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;
}
if (browser.mobile) {
return false;
}
function onAppVisible() {
if (isHidden) {
isHidden = false;
console.debug('triggering app resume event');
events.trigger(appHost, 'resume');
}
return true;
}
function supportsCue() {
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;
console.debug('triggering app resume event');
events.trigger(appHost, 'resume');
}
}
function onAppHidden() {
if (!isHidden) {
isHidden = true;
console.debug('app is hidden');
}
}
const supportedFeatures = function () {
const features = [];
if (navigator.share) {
features.push('sharing');
}
function onAppHidden() {
if (!isHidden) {
isHidden = true;
console.debug('app is hidden');
}
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('filedownload');
}
var supportedFeatures = function () {
var features = [];
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push('exit');
} else {
features.push('exitmenu');
features.push('plugins');
}
if (navigator.share) {
features.push('sharing');
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push('externallinks');
features.push('externalpremium');
}
if (!browser.edgeUwp && !browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('filedownload');
}
if (!browser.operaTv) {
features.push('externallinkdisplay');
}
if (browser.operaTv || browser.tizen || browser.orsay || browser.web0s) {
features.push('exit');
if (supportsVoiceInput()) {
features.push('voiceinput');
}
if (supportsHtmlMediaAutoplay()) {
features.push('htmlaudioautoplay');
features.push('htmlvideoautoplay');
}
if (browser.edgeUwp) {
features.push('sync');
}
if (supportsFullscreen()) {
features.push('fullscreenchange');
}
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
features.push('physicalvolumecontrol');
}
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('remotecontrol');
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
features.push('remotevideo');
}
features.push('displaylanguage');
features.push('otherapppromotions');
features.push('displaymode');
features.push('targetblank');
features.push('screensaver');
webSettings.getMultiServer().then(enabled => {
if (enabled) features.push('multiserver');
});
if (!browser.orsay && (browser.firefox || browser.ps4 || browser.edge || supportsCue())) {
features.push('subtitleappearancesettings');
}
if (!browser.orsay) {
features.push('subtitleburnsettings');
}
if (!browser.tv && !browser.ps4 && !browser.xboxOne) {
features.push('fileinput');
}
if (browser.chrome || browser.edgeChromium) {
features.push('chromecast');
}
return features;
}();
/**
* Do exit according to platform
*/
function doExit() {
try {
if (window.NativeShell) {
window.NativeShell.AppHost.exit();
} else if (browser.tizen) {
tizen.application.getCurrentApplication().exit();
} else if (browser.web0s) {
webOS.platformBack();
} else {
features.push('exitmenu');
features.push('plugins');
window.close();
}
} catch (err) {
console.error('error closing application: ' + err);
}
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.ps4) {
features.push('externallinks');
features.push('externalpremium');
}
let exitPromise;
if (!browser.operaTv) {
features.push('externallinkdisplay');
}
if (supportsVoiceInput()) {
features.push('voiceinput');
}
if (supportsHtmlMediaAutoplay()) {
features.push('htmlaudioautoplay');
features.push('htmlvideoautoplay');
}
if (browser.edgeUwp) {
features.push('sync');
}
if (supportsFullscreen()) {
features.push('fullscreenchange');
}
if (browser.chrome || browser.edge && !browser.slow) {
if (!browser.noAnimation && !browser.edgeUwp && !browser.xboxOne) {
features.push('imageanalysis');
}
}
if (browser.tv || browser.xboxOne || browser.ps4 || browser.mobile) {
features.push('physicalvolumecontrol');
}
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
features.push('remotecontrol');
}
if (!browser.operaTv && !browser.tizen && !browser.orsay && !browser.web0s && !browser.edgeUwp) {
features.push('remotevideo');
}
features.push('displaylanguage');
features.push('otherapppromotions');
features.push('displaymode');
features.push('targetblank');
features.push('screensaver');
webSettings.enableMultiServer().then(enabled => {
if (enabled) features.push('multiserver');
});
if (!browser.orsay && !browser.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);
}
/**
* Ask user for exit
*/
function askForExit() {
if (exitPromise) {
return;
}
var exitPromise;
/**
* Ask user for exit
*/
function askForExit() {
if (exitPromise) {
return;
}
require(['actionsheet'], function (actionsheet) {
exitPromise = actionsheet.show({
title: globalize.translate('MessageConfirmAppExit'),
items: [
{id: 'yes', name: globalize.translate('Yes')},
{id: 'no', name: globalize.translate('No')}
]
}).then(function (value) {
if (value === 'yes') {
doExit();
}
}).finally(function () {
exitPromise = null;
});
});
}
var deviceId;
var deviceName;
var appName = 'Jellyfin Web';
var appVersion = '10.6.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 {
import('actionsheet').then(({default: 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();
}
},
supports: function (command) {
if (window.NativeShell) {
return window.NativeShell.AppHost.supports(command);
}
}).finally(function () {
exitPromise = null;
});
});
}
return -1 !== supportedFeatures.indexOf(command.toLowerCase());
},
preferVisualCards: browser.android || browser.chrome,
getSyncProfile: getSyncProfile,
getDefaultLayout: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.getDefaultLayout();
}
let deviceId;
let deviceName;
const appName = 'Jellyfin Web';
const appVersion = '10.7.0';
return getDefaultLayout();
},
getDeviceProfile: getDeviceProfile,
init: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.init();
}
deviceName = getDeviceName();
getDeviceId().then(function (id) {
deviceId = id;
});
},
deviceName: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName;
},
deviceId: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId;
},
appName: function () {
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
},
appVersion: function () {
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
},
getPushTokenInfo: function () {
return {};
},
setThemeColor: function (color) {
var metaThemeColor = document.querySelector('meta[name=theme-color]');
if (metaThemeColor) {
metaThemeColor.setAttribute('content', color);
}
},
setUserScalable: function (scalable) {
if (!browser.tv) {
var att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
document.querySelector('meta[name=viewport]').setAttribute('content', att);
}
}
};
var isHidden = false;
var hidden;
var visibilityChange;
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
document.addEventListener(visibilityChange, function () {
/* eslint-disable-next-line compat/compat */
if (document[hidden]) {
onAppHidden();
const appHost = {
getWindowState: function () {
return document.windowState || 'Normal';
},
setWindowState: function () {
alert('setWindowState is not supported and should not be called');
},
exit: function () {
if (!!window.appMode && browser.tizen) {
askForExit();
} else {
onAppVisible();
doExit();
}
},
supports: function (command) {
if (window.NativeShell) {
return window.NativeShell.AppHost.supports(command);
}
}, false);
if (self.addEventListener) {
self.addEventListener('focus', onAppVisible);
self.addEventListener('blur', onAppHidden);
return supportedFeatures.indexOf(command.toLowerCase()) !== -1;
},
preferVisualCards: browser.android || browser.chrome,
getDefaultLayout: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.getDefaultLayout();
}
return getDefaultLayout();
},
getDeviceProfile: getDeviceProfile,
init: function () {
if (window.NativeShell) {
return window.NativeShell.AppHost.init();
}
deviceName = getDeviceName();
getDeviceId().then(function (id) {
deviceId = id;
});
},
deviceName: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceName() : deviceName;
},
deviceId: function () {
return window.NativeShell ? window.NativeShell.AppHost.deviceId() : deviceId;
},
appName: function () {
return window.NativeShell ? window.NativeShell.AppHost.appName() : appName;
},
appVersion: function () {
return window.NativeShell ? window.NativeShell.AppHost.appVersion() : appVersion;
},
getPushTokenInfo: function () {
return {};
},
setUserScalable: function (scalable) {
if (!browser.tv) {
const att = scalable ? 'width=device-width, initial-scale=1, minimum-scale=1, user-scalable=yes' : 'width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no';
document.querySelector('meta[name=viewport]').setAttribute('content', att);
}
}
};
return appHost;
});
let isHidden = false;
let hidden;
let visibilityChange;
if (typeof document.hidden !== 'undefined') { /* eslint-disable-line compat/compat */
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
document.addEventListener(visibilityChange, function () {
/* eslint-disable-next-line compat/compat */
if (document[hidden]) {
onAppHidden();
} else {
onAppVisible();
}
}, false);
if (window.addEventListener) {
window.addEventListener('focus', onAppVisible);
window.addEventListener('blur', onAppHidden);
}
export default appHost;

View file

@ -1,5 +1,11 @@
define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings', 'css!./backdrop'], function (browser, connectionManager, playbackManager, dom, userSettings) {
'use strict';
import browser from 'browser';
import connectionManager from 'connectionManager';
import playbackManager from 'playbackManager';
import dom from 'dom';
import * as userSettings from 'userSettings';
import 'css!./backdrop';
/* eslint-disable indent */
function enableAnimation(elem) {
if (browser.slow) {
@ -22,71 +28,70 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return true;
}
function Backdrop() {
}
class Backdrop {
load(url, parent, existingBackdropImage) {
const img = new Image();
const self = this;
Backdrop.prototype.load = function (url, parent, existingBackdropImage) {
var img = new Image();
var self = this;
img.onload = function () {
if (self.isDestroyed) {
return;
}
var backdropImage = document.createElement('div');
backdropImage.classList.add('backdropImage');
backdropImage.classList.add('displayingBackdropImage');
backdropImage.style.backgroundImage = "url('" + url + "')";
backdropImage.setAttribute('data-url', url);
backdropImage.classList.add('backdropImageFadeIn');
parent.appendChild(backdropImage);
if (!enableAnimation(backdropImage)) {
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
img.onload = () => {
if (self.isDestroyed) {
return;
}
internalBackdrop(true);
return;
}
var onAnimationComplete = function () {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
const backdropImage = document.createElement('div');
backdropImage.classList.add('backdropImage');
backdropImage.classList.add('displayingBackdropImage');
backdropImage.style.backgroundImage = `url('${url}')`;
backdropImage.setAttribute('data-url', url);
backdropImage.classList.add('backdropImageFadeIn');
parent.appendChild(backdropImage);
if (!enableAnimation(backdropImage)) {
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
internalBackdrop(true);
return;
}
const onAnimationComplete = () => {
dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null;
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
if (backdropImage === self.currentAnimatingElement) {
self.currentAnimatingElement = null;
}
if (existingBackdropImage && existingBackdropImage.parentNode) {
existingBackdropImage.parentNode.removeChild(existingBackdropImage);
}
internalBackdrop(true);
};
dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, {
once: true
});
internalBackdrop(true);
};
img.src = url;
};
Backdrop.prototype.cancelAnimation = function () {
var elem = this.currentAnimatingElement;
if (elem) {
elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
img.src = url;
}
};
Backdrop.prototype.destroy = function () {
this.isDestroyed = true;
this.cancelAnimation();
};
cancelAnimation() {
const elem = this.currentAnimatingElement;
if (elem) {
elem.classList.remove('backdropImageFadeIn');
this.currentAnimatingElement = null;
}
}
var backdropContainer;
destroy() {
this.isDestroyed = true;
this.cancelAnimation();
}
}
let backdropContainer;
function getBackdropContainer() {
if (!backdropContainer) {
backdropContainer = document.querySelector('.backdropContainer');
@ -101,7 +106,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return backdropContainer;
}
function clearBackdrop(clearAll) {
export function clearBackdrop(clearAll) {
clearRotation();
if (currentLoadingBackdrop) {
@ -109,7 +114,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
currentLoadingBackdrop = null;
}
var elem = getBackdropContainer();
const elem = getBackdropContainer();
elem.innerHTML = '';
if (clearAll) {
@ -119,7 +124,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
internalBackdrop(false);
}
var backgroundContainer;
let backgroundContainer;
function getBackgroundContainer() {
if (!backgroundContainer) {
backgroundContainer = document.querySelector('.backgroundContainer');
@ -135,31 +140,27 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
}
}
var hasInternalBackdrop;
let hasInternalBackdrop;
function internalBackdrop(enabled) {
hasInternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
var hasExternalBackdrop;
function externalBackdrop(enabled) {
let hasExternalBackdrop;
export function externalBackdrop(enabled) {
hasExternalBackdrop = enabled;
setBackgroundContainerBackgroundEnabled();
}
function getRandom(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
var currentLoadingBackdrop;
let currentLoadingBackdrop;
function setBackdropImage(url) {
if (currentLoadingBackdrop) {
currentLoadingBackdrop.destroy();
currentLoadingBackdrop = null;
}
var elem = getBackdropContainer();
var existingBackdropImage = elem.querySelector('.displayingBackdropImage');
const elem = getBackdropContainer();
const existingBackdropImage = elem.querySelector('.displayingBackdropImage');
if (existingBackdropImage && 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');
}
var instance = new Backdrop();
const instance = new Backdrop();
instance.load(url, elem, existingBackdropImage);
currentLoadingBackdrop = instance;
}
@ -176,9 +177,9 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
function getItemImageUrls(item, imageOptions) {
imageOptions = imageOptions || {};
var apiClient = connectionManager.getApiClient(item.ServerId);
const apiClient = connectionManager.getApiClient(item.ServerId);
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, {
type: 'Backdrop',
tag: imgTag,
@ -189,7 +190,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
}
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, {
type: 'Backdrop',
tag: imgTag,
@ -203,13 +204,13 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
}
function getImageUrls(items, imageOptions) {
var list = [];
var onImg = function (img) {
const list = [];
const onImg = img => {
list.push(img);
};
for (var i = 0, length = items.length; i < length; i++) {
var itemImages = getItemImageUrls(items[i], imageOptions);
for (let i = 0, length = items.length; i < length; i++) {
const itemImages = getItemImageUrls(items[i], imageOptions);
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
// 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]) {
return false;
}
@ -242,12 +243,12 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return userSettings.enableBackdrops();
}
var rotationInterval;
var currentRotatingImages = [];
var currentRotationIndex = -1;
function setBackdrops(items, imageOptions, enableImageRotation) {
let rotationInterval;
let currentRotatingImages = [];
let currentRotationIndex = -1;
export function setBackdrops(items, imageOptions, enableImageRotation) {
if (enabled()) {
var images = getImageUrls(items, imageOptions);
const images = getImageUrls(items, imageOptions);
if (images.length) {
startRotation(images, enableImageRotation);
@ -279,7 +280,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
return;
}
var newIndex = currentRotationIndex + 1;
let newIndex = currentRotationIndex + 1;
if (newIndex >= currentRotatingImages.length) {
newIndex = 0;
}
@ -289,7 +290,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
}
function clearRotation() {
var interval = rotationInterval;
const interval = rotationInterval;
if (interval) {
clearInterval(interval);
}
@ -299,7 +300,7 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
currentRotationIndex = -1;
}
function setBackdrop(url, imageOptions) {
export function setBackdrop(url, imageOptions) {
if (url && typeof url !== 'string') {
url = getImageUrls([url], imageOptions)[0];
}
@ -312,10 +313,11 @@ define(['browser', 'connectionManager', 'playbackManager', 'dom', 'userSettings'
}
}
return {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clear: clearBackdrop,
externalBackdrop: externalBackdrop
};
});
/* eslint-enable indent */
export default {
setBackdrops: setBackdrops,
setBackdrop: setBackdrop,
clearBackdrop: clearBackdrop,
externalBackdrop: externalBackdrop
};

View file

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

View file

@ -167,8 +167,9 @@ button::-moz-focus-inner {
position: relative;
background-clip: content-box !important;
color: inherit;
}
/* This is only needed for scalable cards */
.cardScalable .cardImageContainer {
height: 100%;
contain: strict;
}
@ -192,9 +193,14 @@ button::-moz-focus-inner {
/* Needed in case this is a button */
display: block;
/* Needed in case this is a button */
margin: 0 !important;
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
font-family: inherit;
font-size: inherit;
/* Needed in safari */
height: 100%;
@ -203,19 +209,12 @@ button::-moz-focus-inner {
contain: strict;
}
.cardContent-button {
border: 0 !important;
padding: 0 !important;
cursor: pointer;
color: inherit;
width: 100%;
vertical-align: middle;
font-family: inherit;
font-size: inherit;
.cardContent:not(.defaultCardBackground) {
background-color: transparent;
}
.cardContent-button:not(.defaultCardBackground) {
background-color: transparent;
.cardBox:not(.visualCardBox) .cardPadder {
background-color: #242424;
}
.visualCardBox .cardContent {
@ -223,7 +222,8 @@ button::-moz-focus-inner {
border-bottom-right-radius: 0;
}
.cardContent-shadow {
.cardContent-shadow,
.cardBox:not(.visualCardBox) .cardPadder {
box-shadow: 0 0.0725em 0.29em 0 rgba(0, 0, 0, 0.37);
}
@ -239,33 +239,13 @@ button::-moz-focus-inner {
border: none;
}
.cardImage-img {
max-height: 100%;
max-width: 100%;
/* This is simply for lazy image purposes, to ensure the image is visible sooner when scrolling */
min-height: 70%;
min-width: 70%;
margin: auto;
}
.coveredImage-img {
width: 100%;
height: 100%;
}
.coveredImage-noscale-img {
max-height: none;
max-width: none;
}
.coveredImage {
background-size: cover;
background-position: center center;
}
.coveredImage-noScale {
background-size: cover;
.coveredImage.coveredImage-contain {
background-size: contain;
}
.cardFooter {
@ -372,6 +352,8 @@ button::-moz-focus-inner {
.cardDefaultText {
white-space: normal;
text-align: center;
font-size: 2em;
font-weight: bold;
}
.cardImageContainer .cardImageIcon {

View file

@ -277,7 +277,7 @@ import 'programStyles';
*/
function getImageWidth(shape, screenWidth, isOrientationLandscape) {
const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape);
return Math.round(screenWidth / imagesPerRow) * 2;
return Math.round(screenWidth / imagesPerRow);
}
/**
@ -291,12 +291,10 @@ import 'programStyles';
const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items);
if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) {
const requestedShape = options.shape;
options.shape = null;
if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 3) {
options.shape = 'banner';
options.coverImage = true;
@ -364,14 +362,12 @@ import 'programStyles';
let hasOpenRow;
let hasOpenSection;
let sectionTitleTagName = options.sectionTitleTagName || 'div';
const sectionTitleTagName = options.sectionTitleTagName || 'div';
let apiClient;
let lastServerId;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let serverId = item.ServerId || options.serverId;
for (const [i, item] of items.entries()) {
const serverId = item.ServerId || options.serverId;
if (serverId !== lastServerId) {
lastServerId = serverId;
@ -396,7 +392,6 @@ import 'programStyles';
}
if (newIndexValue !== currentIndexValue) {
if (hasOpenRow) {
html += '</div>';
hasOpenRow = false;
@ -404,7 +399,6 @@ import 'programStyles';
}
if (hasOpenSection) {
html += '</div>';
if (isVertical) {
@ -428,7 +422,6 @@ import 'programStyles';
}
if (options.rows && itemsInRow === 0) {
if (hasOpenRow) {
html += '</div>';
hasOpenRow = false;
@ -503,94 +496,49 @@ import 'programStyles';
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
let forceName = false;
let imgUrl = null;
let imgTag = null;
let coverImage = false;
let uiAspect = null;
let imgType = null;
let itemId = null;
if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Banner',
maxWidth: width,
tag: item.ImageTags.Banner
});
imgType = 'Banner';
imgTag = item.ImageTags.Banner;
} else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Disc',
maxWidth: width,
tag: item.ImageTags.Disc
});
imgType = 'Disc';
imgTag = item.ImageTags.Disc;
} else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Logo',
maxWidth: width,
tag: item.ImageTags.Logo
});
imgType = 'Logo';
imgTag = item.ImageTags.Logo;
} else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) {
imgUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, {
type: 'Logo',
maxWidth: width,
tag: item.ParentLogoImageTag
});
imgType = 'Logo';
imgTag = item.ParentLogoImageTag;
itemId = item.ParentLogoItemId;
} else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Thumb',
maxWidth: width,
tag: item.SeriesThumbImageTag
});
imgType = 'Thumb';
imgTag = item.SeriesThumbImageTag;
itemId = item.SeriesId;
} else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') {
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: 'Thumb',
maxWidth: width,
tag: item.ParentThumbImageTag
});
imgType = 'Thumb';
imgTag = item.ParentThumbImageTag;
itemId = item.ParentThumbItemId;
} else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.BackdropImageTags[0];
forceName = true;
} else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') {
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: 'Backdrop',
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
});
} else if (item.ImageTags && item.ImageTags.Primary) {
imgType = 'Backdrop';
imgTag = item.ParentBackdropImageTags[0];
itemId = item.ParentBackdropItemId;
} else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) {
imgType = 'Primary';
imgTag = item.ImageTags.Primary;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.ImageTags.Primary
});
if (options.preferThumb && options.showTitle !== false) {
forceName = true;
}
@ -601,18 +549,16 @@ import 'programStyles';
coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2;
}
}
} else if (item.SeriesPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.PrimaryImageTag) {
imgType = 'Primary';
imgTag = item.PrimaryImageTag;
itemId = item.PrimaryImageItemId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.PrimaryImageItemId || item.Id || item.ItemId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.PrimaryImageTag
});
if (options.preferThumb && options.showTitle !== false) {
forceName = true;
}
@ -624,30 +570,15 @@ import 'programStyles';
}
}
} else if (item.ParentPrimaryImageTag) {
imgUrl = apiClient.getScaledImageUrl(item.ParentPrimaryImageItemId, {
type: 'Primary',
maxWidth: width,
tag: item.ParentPrimaryImageTag
});
} else if (item.SeriesPrimaryImageTag) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Primary',
maxWidth: width,
tag: item.SeriesPrimaryImageTag
});
imgType = 'Primary';
imgTag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
imgType = 'Primary';
imgTag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null;
imgUrl = apiClient.getScaledImageUrl(item.AlbumId, {
type: 'Primary',
maxHeight: height,
maxWidth: width,
tag: item.AlbumPrimaryImageTag
});
if (primaryImageAspectRatio) {
uiAspect = getDesiredAspect(shape);
if (uiAspect) {
@ -655,57 +586,46 @@ import 'programStyles';
}
}
} else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if (item.BackdropImageTags && item.BackdropImageTags.length) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Backdrop',
maxWidth: width,
tag: item.BackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.BackdropImageTags[0];
} else if (item.ImageTags && item.ImageTags.Thumb) {
imgUrl = apiClient.getScaledImageUrl(item.Id, {
type: 'Thumb',
maxWidth: width,
tag: item.ImageTags.Thumb
});
imgType = 'Thumb';
imgTag = item.ImageTags.Thumb;
} else if (item.SeriesThumbImageTag && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.SeriesId, {
type: 'Thumb',
maxWidth: width,
tag: item.SeriesThumbImageTag
});
imgType = 'Thumb';
imgTag = item.SeriesThumbImageTag;
itemId = item.SeriesId;
} else if (item.ParentThumbItemId && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.ParentThumbItemId, {
type: 'Thumb',
maxWidth: width,
tag: item.ParentThumbImageTag
});
imgType = 'Thumb';
imgTag = item.ParentThumbImageTag;
itemId = item.ParentThumbItemId;
} else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) {
imgUrl = apiClient.getScaledImageUrl(item.ParentBackdropItemId, {
type: 'Backdrop',
maxWidth: width,
tag: item.ParentBackdropImageTags[0]
});
imgType = 'Backdrop';
imgTag = item.ParentBackdropImageTags[0];
itemId = item.ParentBackdropItemId;
}
if (!itemId) {
itemId = item.Id;
}
if (imgTag && imgType) {
imgUrl = apiClient.getScaledImageUrl(itemId, {
type: imgType,
maxHeight: height,
maxWidth: width,
tag: imgTag
});
}
const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {};
return {
imgUrl: imgUrl,
blurhash: (blurHashes[imgType] || {})[imgTag],
forceName: forceName,
coverImage: coverImage
};
@ -736,7 +656,7 @@ import 'programStyles';
for (let i = 0; i < character.length; i++) {
sum += parseInt(character.charAt(i));
}
let index = String(sum).substr(-1);
const index = String(sum).substr(-1);
return (index % numRandomColors) + 1;
} else {
@ -761,9 +681,8 @@ import 'programStyles';
let valid = 0;
for (let i = 0; i < lines.length; i++) {
let currentCssClass = cssClass;
let text = lines[i];
const text = lines[i];
if (valid > 0 && isOuterFooter) {
currentCssClass += ' cardText-secondary';
@ -788,8 +707,7 @@ import 'programStyles';
}
if (forceLines) {
let linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length);
while (valid < linesLength) {
html += "<div class='" + cssClass + "'>&nbsp;</div>";
@ -820,7 +738,6 @@ import 'programStyles';
let airTimeText = '';
if (item.StartDate) {
try {
let date = datetime.parseISO8601Date(item.StartDate);
@ -867,7 +784,6 @@ import 'programStyles';
const showOtherText = isOuterFooter ? !overlayText : overlayText;
if (isOuterFooter && options.cardLayout && layoutManager.mobile) {
if (options.cardFooterAside !== 'none') {
html += '<button is="paper-icon-button-light" class="itemAction btnCardOptions cardText-secondary" data-action="menu"><span class="material-icons more_vert"></span></button>';
}
@ -882,9 +798,7 @@ import 'programStyles';
if (showOtherText) {
if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) {
if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) {
if (item.SeriesId) {
lines.push(getTextActionButton({
Id: item.SeriesId,
@ -897,15 +811,12 @@ import 'programStyles';
lines.push(item.SeriesName);
}
} else {
if (isUsingLiveTvNaming(item)) {
lines.push(item.Name);
if (!item.EpisodeTitle) {
titleAdded = true;
}
} else {
const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || '';
@ -923,7 +834,6 @@ import 'programStyles';
}
if (showMediaTitle) {
const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, {
includeParentInfo: options.includeParentInfoInTitle
});
@ -940,7 +850,6 @@ import 'programStyles';
if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) {
if (isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) {
item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true;
@ -974,7 +883,6 @@ import 'programStyles';
}
if (options.showPremiereDate) {
if (item.PremiereDate) {
try {
lines.push(datetime.toLocaleDateString(
@ -983,7 +891,6 @@ import 'programStyles';
));
} catch (err) {
lines.push('');
}
} else {
lines.push('');
@ -991,14 +898,10 @@ import 'programStyles';
}
if (options.showYear || options.showSeriesYear) {
if (item.Type === 'Series') {
if (item.Status === 'Continuing') {
lines.push(globalize.translate('SeriesYearToPresent', item.ProductionYear || ''));
} else {
if (item.EndDate && item.ProductionYear) {
const endYear = datetime.parseISO8601Date(item.EndDate).getFullYear();
lines.push(item.ProductionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear)));
@ -1012,9 +915,7 @@ import 'programStyles';
}
if (options.showRuntime) {
if (item.RunTimeTicks) {
lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks));
} else {
lines.push('');
@ -1022,14 +923,11 @@ import 'programStyles';
}
if (options.showAirTime) {
lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || '');
}
if (options.showChannelName) {
if (item.ChannelId) {
lines.push(getTextActionButton({
Id: item.ChannelId,
@ -1046,7 +944,6 @@ import 'programStyles';
}
if (options.showCurrentProgram && item.Type === 'TvChannel') {
if (item.CurrentProgram) {
lines.push(item.CurrentProgram.Name);
} else {
@ -1055,7 +952,6 @@ import 'programStyles';
}
if (options.showCurrentProgramTime && item.Type === 'TvChannel') {
if (item.CurrentProgram) {
lines.push(getAirTimeText(item.CurrentProgram, false, true) || '');
} else {
@ -1065,7 +961,6 @@ import 'programStyles';
if (options.showSeriesTimerTime) {
if (item.RecordAnyTime) {
lines.push(globalize.translate('Anytime'));
} else {
lines.push(datetime.getDisplayTime(item.StartDate));
@ -1091,6 +986,10 @@ import 'programStyles';
lines = [];
}
if (overlayText && showTitle) {
lines = [item.Name];
}
const addRightTextMargin = isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile;
html += getCardTextLines(lines, cssClass, !options.overlayText, isOuterFooter, options.cardLayout, addRightTextMargin, options.lines);
@ -1100,7 +999,6 @@ import 'programStyles';
}
if (html) {
if (!isOuterFooter || logoUrl || options.cardLayout) {
html = '<div class="' + footerClass + '">' + html;
@ -1142,31 +1040,25 @@ import 'programStyles';
* @returns {string} HTML markup for the item count indicator.
*/
function getItemCountsHtml(options, item) {
let counts = [];
const counts = [];
let childText;
if (item.Type === 'Playlist') {
childText = '';
if (item.RunTimeTicks) {
let minutes = item.RunTimeTicks / 600000000;
minutes = minutes || 1;
childText += globalize.translate('ValueMinutes', Math.round(minutes));
} else {
childText += globalize.translate('ValueMinutes', 0);
}
counts.push(childText);
} else if (item.Type === 'Genre' || item.Type === 'Studio') {
if (item.MovieCount) {
childText = item.MovieCount === 1 ?
globalize.translate('ValueOneMovie') :
globalize.translate('ValueMovieCount', item.MovieCount);
@ -1175,7 +1067,6 @@ import 'programStyles';
}
if (item.SeriesCount) {
childText = item.SeriesCount === 1 ?
globalize.translate('ValueOneSeries') :
globalize.translate('ValueSeriesCount', item.SeriesCount);
@ -1183,18 +1074,14 @@ import 'programStyles';
counts.push(childText);
}
if (item.EpisodeCount) {
childText = item.EpisodeCount === 1 ?
globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.EpisodeCount);
counts.push(childText);
}
} else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') {
if (item.AlbumCount) {
childText = item.AlbumCount === 1 ?
globalize.translate('ValueOneAlbum') :
globalize.translate('ValueAlbumCount', item.AlbumCount);
@ -1202,7 +1089,6 @@ import 'programStyles';
counts.push(childText);
}
if (item.SongCount) {
childText = item.SongCount === 1 ?
globalize.translate('ValueOneSong') :
globalize.translate('ValueSongCount', item.SongCount);
@ -1210,16 +1096,13 @@ import 'programStyles';
counts.push(childText);
}
if (item.MusicVideoCount) {
childText = item.MusicVideoCount === 1 ?
globalize.translate('ValueOneMusicVideo') :
globalize.translate('ValueMusicVideoCount', item.MusicVideoCount);
counts.push(childText);
}
} else if (item.Type === 'Series') {
childText = item.RecursiveItemCount === 1 ?
globalize.translate('ValueOneEpisode') :
globalize.translate('ValueEpisodeCount', item.RecursiveItemCount);
@ -1235,10 +1118,11 @@ import 'programStyles';
/**
* Imports the refresh indicator element.
*/
function requireRefreshIndicator() {
function importRefreshIndicator() {
if (!refreshIndicatorLoaded) {
refreshIndicatorLoaded = true;
require(['emby-itemrefreshindicator']);
/* eslint-disable-next-line @babel/no-unused-expressions */
import('emby-itemrefreshindicator');
}
}
@ -1272,13 +1156,11 @@ import 'programStyles';
let shape = options.shape;
if (shape === 'mixed') {
shape = null;
const primaryImageAspectRatio = item.PrimaryImageAspectRatio;
if (primaryImageAspectRatio) {
if (primaryImageAspectRatio >= 1.33) {
shape = 'mixedBackdrop';
} else if (primaryImageAspectRatio > 0.71) {
@ -1321,6 +1203,7 @@ import 'programStyles';
const imgInfo = getCardImageUrl(item, apiClient, options, shape);
const imgUrl = imgInfo.imgUrl;
const blurhash = imgInfo.blurhash;
const forceName = imgInfo.forceName;
@ -1333,8 +1216,8 @@ import 'programStyles';
if (coveredImage) {
cardImageContainerClass += ' coveredImage';
if (item.MediaType === 'Photo' || item.Type === 'PhotoAlbum' || item.Type === 'Folder' || item.ProgramInfo || item.Type === 'Program' || item.Type === 'Recording') {
cardImageContainerClass += ' coveredImage-noScale';
if (item.Type === 'TvChannel') {
cardImageContainerClass += ' coveredImage-contain';
}
}
@ -1369,7 +1252,6 @@ import 'programStyles';
}
if (overlayText) {
logoUrl = null;
footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter';
@ -1384,7 +1266,7 @@ import 'programStyles';
}
const mediaSourceCount = item.MediaSourceCount || 1;
if (mediaSourceCount > 1) {
if (mediaSourceCount > 1 && options.disableIndicators !== true) {
innerCardFooter += '<div class="mediaSourceIndicator">' + mediaSourceCount + '</div>';
}
@ -1440,58 +1322,60 @@ import 'programStyles';
let cardBoxClose = '';
let cardScalableClose = '';
let cardContentClass = 'cardContent';
if (!options.cardLayout) {
cardContentClass += ' cardContent-shadow';
const cardContentClass = 'cardContent';
let blurhashAttrib = '';
if (blurhash && blurhash.length > 0) {
blurhashAttrib = 'data-blurhash="' + blurhash + '"';
}
if (layoutManager.tv) {
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '">') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
cardImageContainerOpen = imgUrl ? ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + ' lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<div class="' + cardImageContainerClass + ' ' + cardContentClass + '">');
cardImageContainerClose = '</div>';
} else {
// Don't use the IMG tag with safari because it puts a white border around it
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '">') : ('<button data-action="' + action + '" class="cardContent-button ' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerOpen = imgUrl ? ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction lazy" data-src="' + imgUrl + '" ' + blurhashAttrib + '>') : ('<button data-action="' + action + '" class="' + cardImageContainerClass + ' ' + cardContentClass + ' itemAction">');
cardImageContainerClose = '</button>';
}
let cardScalableClass = 'cardScalable';
const cardScalableClass = 'cardScalable';
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardImageContainerOpen = '<div class="' + cardBoxClass + '"><div class="' + cardScalableClass + '"><div class="cardPadder cardPadder-' + shape + '"></div>' + cardImageContainerOpen;
cardBoxClose = '</div>';
cardScalableClose = '</div>';
let indicatorsHtml = '';
if (options.disableIndicators !== true) {
let indicatorsHtml = '';
if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item);
}
if (options.missingIndicator !== false) {
indicatorsHtml += indicators.getMissingIndicator(item);
}
indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getSyncIndicator(item);
indicatorsHtml += indicators.getTimerIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item);
indicatorsHtml += indicators.getTypeIndicator(item);
if (options.showGroupCount) {
if (options.showGroupCount) {
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
} else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
}
indicatorsHtml += indicators.getChildCountIndicatorHtml(item, {
minCount: 1
});
} else {
indicatorsHtml += indicators.getPlayedIndicatorHtml(item);
}
if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
importRefreshIndicator();
}
if (item.Type === 'CollectionFolder' || item.CollectionType) {
const refreshClass = item.RefreshProgress ? '' : ' class="hide"';
indicatorsHtml += '<div is="emby-itemrefreshindicator"' + refreshClass + ' data-progress="' + (item.RefreshProgress || 0) + '" data-status="' + item.RefreshStatus + '"></div>';
requireRefreshIndicator();
}
if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
if (indicatorsHtml) {
cardImageContainerOpen += '<div class="cardIndicators">' + indicatorsHtml + '</div>';
}
}
if (!imgUrl) {
@ -1539,8 +1423,8 @@ import 'programStyles';
let additionalCardContent = '';
if (layoutManager.desktop) {
additionalCardContent += getHoverMenuHtml(item, action);
if (layoutManager.desktop && !options.disableHoverMenu) {
additionalCardContent += getHoverMenuHtml(item, action, options);
}
return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + ' data-prefix="' + prefix + '" class="' + className + '">' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + '</' + tagName + '>';
@ -1550,9 +1434,10 @@ import 'programStyles';
* Generates HTML markup for the card overlay.
* @param {object} item - Item used to generate the card overlay.
* @param {string} action - Action assigned to the overlay.
* @param {Array} options - Card builder options.
* @returns {string} HTML markup of the card overlay.
*/
function getHoverMenuHtml(item, action) {
function getHoverMenuHtml(item, action, options) {
let html = '';
html += '<div class="cardOverlayContainer itemAction" data-action="' + action + '">';
@ -1568,20 +1453,20 @@ import 'programStyles';
const userData = item.UserData || {};
if (itemHelper.canMarkPlayed(item)) {
require(['emby-playstatebutton']);
/* eslint-disable-next-line @babel/no-unused-expressions */
import('emby-playstatebutton');
html += '<button is="emby-playstatebutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-played="' + (userData.Played) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check"></span></button>';
}
if (itemHelper.canRate(item)) {
const likes = userData.Likes == null ? '' : userData.Likes;
require(['emby-ratingbutton']);
/* eslint-disable-next-line @babel/no-unused-expressions */
import('emby-ratingbutton');
html += '<button is="emby-ratingbutton" type="button" data-action="none" class="' + btnCssClass + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite"></span></button>';
}
html += '<button is="paper-icon-button-light" class="' + btnCssClass + '" data-action="menu"><span class="material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover more_vert"></span></button>';
html += '</div>';
html += '</div>';
@ -1605,6 +1490,8 @@ import 'programStyles';
case 'MusicArtist':
case 'Person':
return '<span class="cardImageIcon material-icons person"></span>';
case 'Audio':
return '<span class="cardImageIcon material-icons audiotrack"></span>';
case 'Movie':
return '<span class="cardImageIcon material-icons movie"></span>';
case 'Series':
@ -1613,6 +1500,12 @@ import 'programStyles';
return '<span class="cardImageIcon material-icons book"></span>';
case 'Folder':
return '<span class="cardImageIcon material-icons folder"></span>';
case 'BoxSet':
return '<span class="cardImageIcon material-icons collections"></span>';
case 'Playlist':
return '<span class="cardImageIcon material-icons view_list"></span>';
case 'PhotoAlbum':
return '<span class="cardImageIcon material-icons photo_album"></span>';
}
if (options && options.defaultCardImageIcon) {
@ -1646,7 +1539,6 @@ import 'programStyles';
const html = buildCardsHtmlInternal(items, options);
if (html) {
if (options.itemsContainer.cardBuilderHtml !== html) {
options.itemsContainer.innerHTML = html;
@ -1659,7 +1551,6 @@ import 'programStyles';
imageLoader.lazyChildren(options.itemsContainer);
} else {
options.itemsContainer.innerHTML = html;
options.itemsContainer.cardBuilderHtml = null;
}
@ -1683,7 +1574,6 @@ import 'programStyles';
indicatorsElem = card.querySelector('.cardIndicators');
if (!indicatorsElem) {
const cardImageContainer = card.querySelector('.cardImageContainer');
indicatorsElem = document.createElement('div');
indicatorsElem.classList.add('cardIndicators');
@ -1707,11 +1597,9 @@ import 'programStyles';
let itemProgressBar = null;
if (userData.Played) {
playedIndicator = card.querySelector('.playedIndicator');
if (!playedIndicator) {
playedIndicator = document.createElement('div');
playedIndicator.classList.add('playedIndicator');
playedIndicator.classList.add('indicator');
@ -1720,10 +1608,8 @@ import 'programStyles';
}
playedIndicator.innerHTML = '<span class="material-icons indicatorIcon check"></span>';
} else {
playedIndicator = card.querySelector('.playedIndicator');
if (playedIndicator) {
playedIndicator.parentNode.removeChild(playedIndicator);
}
}
@ -1731,7 +1617,6 @@ import 'programStyles';
countIndicator = card.querySelector('.countIndicator');
if (!countIndicator) {
countIndicator = document.createElement('div');
countIndicator.classList.add('countIndicator');
indicatorsElem = ensureIndicators(card, indicatorsElem);
@ -1739,10 +1624,8 @@ import 'programStyles';
}
countIndicator.innerHTML = userData.UnplayedItemCount;
} else if (enableCountIndicator) {
countIndicator = card.querySelector('.countIndicator');
if (countIndicator) {
countIndicator.parentNode.removeChild(countIndicator);
}
}
@ -1754,7 +1637,6 @@ import 'programStyles';
});
if (progressHtml) {
itemProgressBar = card.querySelector('.itemProgressBar');
if (!itemProgressBar) {
@ -1773,7 +1655,6 @@ import 'programStyles';
itemProgressBar.innerHTML = progressHtml;
} else {
itemProgressBar = card.querySelector('.itemProgressBar');
if (itemProgressBar) {
itemProgressBar.parentNode.removeChild(itemProgressBar);
@ -1804,7 +1685,7 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]');
for (let i = 0, length = cells.length; i < length; i++) {
let cell = cells[i];
const cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
if (!icon) {
const indicatorsElem = ensureIndicators(cell);
@ -1823,8 +1704,8 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]');
for (let i = 0; i < cells.length; i++) {
let cell = cells[i];
let icon = cell.querySelector('.timerIndicator');
const cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
if (icon) {
icon.parentNode.removeChild(icon);
}
@ -1841,8 +1722,8 @@ import 'programStyles';
const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]');
for (let i = 0; i < cells.length; i++) {
let cell = cells[i];
let icon = cell.querySelector('.timerIndicator');
const cell = cells[i];
const icon = cell.querySelector('.timerIndicator');
if (icon) {
icon.parentNode.removeChild(icon);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,31 @@
define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (dom, dialogHelper, loading, appHost, layoutManager, connectionManager, appRouter, globalize) {
'use strict';
import dom from 'dom';
import dialogHelper from 'dialogHelper';
import loading from 'loading';
import layoutManager from 'layoutManager';
import connectionManager from 'connectionManager';
import appRouter from 'appRouter';
import globalize from 'globalize';
import 'emby-checkbox';
import 'emby-input';
import 'paper-icon-button-light';
import 'emby-select';
import 'material-icons';
import 'css!./../formdialog';
import 'emby-button';
import 'flexStyles';
var currentServerId;
/* eslint-disable indent */
let currentServerId;
function onSubmit(e) {
loading.show();
var panel = dom.parentWithClass(this, 'dialog');
const panel = dom.parentWithClass(this, 'dialog');
var collectionId = panel.querySelector('#selectCollectionToAddTo').value;
const collectionId = panel.querySelector('#selectCollectionToAddTo').value;
var apiClient = connectionManager.getApiClient(currentServerId);
const apiClient = connectionManager.getApiClient(currentServerId);
if (collectionId) {
addToCollection(apiClient, panel, collectionId);
@ -23,8 +38,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
}
function createCollection(apiClient, dlg) {
var url = apiClient.getUrl('Collections', {
const url = apiClient.getUrl('Collections', {
Name: dlg.querySelector('#txtNewCollectionName').value,
IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked,
@ -36,27 +50,23 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
url: url,
dataType: 'json'
}).then(function (result) {
}).then(result => {
loading.hide();
var id = result.Id;
const id = result.Id;
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToCollection(apiClient, id);
});
}
function redirectToCollection(apiClient, id) {
appRouter.showItem(id, apiClient.serverId());
}
function addToCollection(apiClient, dlg, id) {
var url = apiClient.getUrl('Collections/' + id + '/Items', {
const url = apiClient.getUrl(`Collections/${id}/Items`, {
Ids: dlg.querySelector('.fldSelectedItemIds').value || ''
});
@ -65,14 +75,13 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
type: 'POST',
url: url
}).then(function () {
}).then(() => {
loading.hide();
dlg.submitted = true;
dialogHelper.close(dlg);
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageItemsAdded'));
});
});
@ -83,14 +92,13 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
}
function populateCollections(panel) {
loading.show();
var select = panel.querySelector('#selectCollectionToAddTo');
const select = panel.querySelector('#selectCollectionToAddTo');
panel.querySelector('.newCollectionInfo').classList.add('hide');
var options = {
const options = {
Recursive: true,
IncludeItemTypes: 'BoxSet',
@ -98,16 +106,14 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
EnableTotalRecordCount: false
};
var apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) {
const apiClient = connectionManager.getApiClient(currentServerId);
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
let html = '';
var html = '';
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += '<option value="">' + globalize.translate('OptionNew') + '</option>';
html += result.Items.map(function (i) {
return '<option value="' + i.Id + '">' + i.Name + '</option>';
html += result.Items.map(i => {
return `<option value="${i.Id}">${i.Name}</option>`;
});
select.innerHTML = html;
@ -119,8 +125,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
}
function getEditorHtml() {
var html = '';
let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
@ -134,27 +139,27 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
html += '<br/>';
html += '<br/>';
html += '<div class="selectContainer">';
html += '<select is="emby-select" label="' + globalize.translate('LabelCollection') + '" id="selectCollectionToAddTo" autofocus></select>';
html += `<select is="emby-select" label="${globalize.translate('LabelCollection')}" id="selectCollectionToAddTo" autofocus></select>`;
html += '</div>';
html += '</div>';
html += '<div class="newCollectionInfo">';
html += '<div class="inputContainer">';
html += '<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="' + globalize.translate('LabelName') + '" />';
html += '<div class="fieldDescription">' + globalize.translate('NewCollectionNameExample') + '</div>';
html += `<input is="emby-input" type="text" id="txtNewCollectionName" required="required" label="${globalize.translate('LabelName')}" />`;
html += `<div class="fieldDescription">${globalize.translate('NewCollectionNameExample')}</div>`;
html += '</div>';
html += '<label class="checkboxContainer">';
html += '<input is="emby-checkbox" type="checkbox" id="chkEnableInternetMetadata" />';
html += '<span>' + globalize.translate('SearchForCollectionInternetMetadata') + '</span>';
html += `<span>${globalize.translate('SearchForCollectionInternetMetadata')}</span>`;
html += '</label>';
// newCollectionInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += '<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">' + globalize.translate('ButtonOk') + '</button>';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
@ -167,7 +172,6 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
}
function initEditor(content, items) {
content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newCollectionInfo').classList.add('hide');
@ -188,7 +192,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
} else {
content.querySelector('.fldSelectCollection').classList.add('hide');
var selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo');
selectCollectionToAddTo.innerHTML = '';
selectCollectionToAddTo.value = '';
triggerChange(selectCollectionToAddTo);
@ -196,79 +200,70 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
import('scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function CollectionEditor() {
export class showEditor {
constructor(options) {
const items = options.items || {};
currentServerId = options.serverId;
}
CollectionEditor.prototype.show = function (options) {
var items = options.items || {};
currentServerId = options.serverId;
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
var title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
if (appHost.supports('externallinks')) {
html += '<a is="emby-linkbutton" class="button-link btnHelp flex align-items-center" href="https://web.archive.org/web/20181216120305/https://github.com/MediaBrowser/Wiki/wiki/Collections" target="_blank" style="margin-left:auto;margin-right:.5em;padding:.25em;" title="' + globalize.translate('Help') + '"><span class="material-icons info"></span><span style="margin-left:.25em;">' + globalize.translate('Help') + '</span></a>';
}
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(function () {
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
if (dlg.submitted) {
return Promise.resolve();
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection');
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml();
dlg.innerHTML = html;
initEditor(dlg, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return Promise.reject();
});
};
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
return CollectionEditor;
});
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
}
}
/* eslint-enable indent */
export default showEditor;

View file

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

View file

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

View file

@ -4,12 +4,8 @@
<div class="formDialogContent smoothScrollY">
<div class="dialogContentInner dialog-content-centered" style="padding-top:1em;padding-bottom: 1em; text-align: center;">
<div class="text">
</div>
<div class="text"></div>
</div>
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="padding-bottom: 1.5em;">
</div>
<div class="formDialogFooter formDialogFooter-clear formDialogFooter-flex" style="margin:1em"></div>

View file

@ -1,10 +1,17 @@
define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 'dom', 'css!./dialoghelper.css', 'scrollStyles'], function (appRouter, focusManager, browser, layoutManager, inputManager, dom) {
'use strict';
import appRouter from 'appRouter';
import focusManager from 'focusManager';
import browser from 'browser';
import layoutManager from 'layoutManager';
import inputManager from 'inputManager';
import dom from 'dom';
import 'css!./dialoghelper.css';
import 'scrollStyles';
var globalOnOpenCallback;
/* eslint-disable indent */
let globalOnOpenCallback;
function enableAnimation() {
// too slow
if (browser.tv) {
return false;
@ -14,7 +21,6 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function removeCenterFocus(dlg) {
if (layoutManager.tv) {
if (dlg.classList.contains('scrollX')) {
centerFocus(dlg, true, false);
@ -25,9 +31,8 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function tryRemoveElement(elem) {
var parentNode = elem.parentNode;
const parentNode = elem.parentNode;
if (parentNode) {
// Seeing crashes in edge webview
try {
parentNode.removeChild(elem);
@ -38,15 +43,13 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function DialogHashHandler(dlg, hash, resolve) {
var self = this;
const self = this;
self.originalUrl = window.location.href;
var activeElement = document.activeElement;
var removeScrollLockOnClose = false;
const activeElement = document.activeElement;
let removeScrollLockOnClose = false;
function onHashChange(e) {
var isBack = self.originalUrl === window.location.href;
const isBack = self.originalUrl === window.location.href;
if (isBack || !isOpened(dlg)) {
window.removeEventListener('popstate', onHashChange);
@ -59,7 +62,6 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function onBackCommand(e) {
if (e.detail.command === 'back') {
self.closedByBack = true;
e.preventDefault();
@ -69,7 +71,6 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function onDialogClosed() {
if (!isHistoryEnabled(dlg)) {
inputManager.off(dlg, onBackCommand);
}
@ -84,9 +85,9 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
if (!self.closedByBack && isHistoryEnabled(dlg)) {
var state = history.state || {};
const state = window.history.state || {};
if (state.dialogId === hash) {
history.back();
window.history.back();
}
}
@ -97,7 +98,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
if (dlg.getAttribute('data-removeonclose') !== 'false') {
removeCenterFocus(dlg);
var dialogContainer = dlg.dialogContainer;
const dialogContainer = dlg.dialogContainer;
if (dialogContainer) {
tryRemoveElement(dialogContainer);
dlg.dialogContainer = null;
@ -108,7 +109,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
//resolve();
// if we just called history.back(), then use a timeout to allow the history events to fire first
setTimeout(function () {
setTimeout(() => {
resolve({
element: dlg,
closedByBack: self.closedByBack
@ -118,7 +119,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
dlg.addEventListener('close', onDialogClosed);
var center = !dlg.classList.contains('dialog-fixedSize');
const center = !dlg.classList.contains('dialog-fixedSize');
if (center) {
dlg.classList.add('centeredDialog');
}
@ -141,7 +142,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
animateDialogOpen(dlg);
if (isHistoryEnabled(dlg)) {
appRouter.pushState({ dialogId: hash }, 'Dialog', '#' + hash);
appRouter.pushState({ dialogId: hash }, 'Dialog', `#${hash}`);
window.addEventListener('popstate', onHashChange);
} else {
@ -150,11 +151,10 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function addBackdropOverlay(dlg) {
var backdrop = document.createElement('div');
const backdrop = document.createElement('div');
backdrop.classList.add('dialogBackdrop');
var backdropParent = dlg.dialogContainer || dlg;
const backdropParent = dlg.dialogContainer || dlg;
backdropParent.parentNode.insertBefore(backdrop, backdropParent);
dlg.backdrop = backdrop;
@ -162,7 +162,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
void backdrop.offsetWidth;
backdrop.classList.add('dialogBackdropOpened');
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', function (e) {
dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => {
if (e.target === dlg.dialogContainer) {
close(dlg);
}
@ -170,7 +170,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
passive: true
});
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', function (e) {
dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => {
if (e.target === dlg.dialogContainer) {
// Close the application dialog menu
close(dlg);
@ -184,40 +184,36 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
return dlg.getAttribute('data-history') === 'true';
}
function open(dlg) {
export function open(dlg) {
if (globalOnOpenCallback) {
globalOnOpenCallback(dlg);
}
var parent = dlg.parentNode;
const parent = dlg.parentNode;
if (parent) {
parent.removeChild(dlg);
}
var dialogContainer = document.createElement('div');
const dialogContainer = document.createElement('div');
dialogContainer.classList.add('dialogContainer');
dialogContainer.appendChild(dlg);
dlg.dialogContainer = dialogContainer;
document.body.appendChild(dialogContainer);
return new Promise(function (resolve, reject) {
new DialogHashHandler(dlg, 'dlg' + new Date().getTime(), resolve);
return new Promise((resolve, reject) => {
new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve);
});
}
function isOpened(dlg) {
//return dlg.opened;
return !dlg.classList.contains('hide');
}
function close(dlg) {
export function close(dlg) {
if (isOpened(dlg)) {
if (isHistoryEnabled(dlg)) {
history.back();
window.history.back();
} else {
closeDialog(dlg);
}
@ -225,15 +221,13 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function closeDialog(dlg) {
if (!dlg.classList.contains('hide')) {
dlg.dispatchEvent(new CustomEvent('closing', {
bubbles: false,
cancelable: false
}));
var onAnimationFinish = function () {
const onAnimationFinish = () => {
focusManager.popScope(dlg);
dlg.classList.add('hide');
@ -248,8 +242,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function animateDialogOpen(dlg) {
var onAnimationFinish = function () {
const onAnimationFinish = () => {
focusManager.pushScope(dlg);
if (dlg.getAttribute('data-autofocus') === 'true') {
@ -263,8 +256,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
};
if (enableAnimation()) {
var onFinish = function () {
const onFinish = () => {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
@ -280,27 +272,24 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function animateDialogClose(dlg, onAnimationFinish) {
if (enableAnimation()) {
var animated = true;
let animated = true;
switch (dlg.animationConfig.exit.name) {
case 'fadeout':
dlg.style.animation = 'fadeout ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
dlg.style.animation = `fadeout ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
case 'scaledown':
dlg.style.animation = 'scaledown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
dlg.style.animation = `scaledown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
case 'slidedown':
dlg.style.animation = 'slidedown ' + dlg.animationConfig.exit.timing.duration + 'ms ease-out normal both';
dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`;
break;
default:
animated = false;
break;
}
var onFinish = function () {
const onFinish = () => {
dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, {
once: true
});
@ -318,10 +307,9 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
onAnimationFinish();
}
var supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style;
function shouldLockDocumentScroll(options) {
if (supportsOverscrollBehavior && (options.size || !browser.touch)) {
return false;
}
@ -342,8 +330,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function removeBackdrop(dlg) {
var backdrop = dlg.backdrop;
const backdrop = dlg.backdrop;
if (!backdrop) {
return;
@ -351,12 +338,11 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
dlg.backdrop = null;
var onAnimationFinish = function () {
const onAnimationFinish = () => {
tryRemoveElement(backdrop);
};
if (enableAnimation()) {
backdrop.classList.remove('dialogBackdropOpened');
// this is not firing animatonend
@ -368,20 +354,19 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
import('scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function createDialog(options) {
export function createDialog(options) {
options = options || {};
// If there's no native dialog support, use a plain div
// Also not working well in samsung tizen browser, content inside not clickable
// Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog
var dlg = document.createElement('div');
const dlg = document.createElement('div');
dlg.classList.add('focuscontainer');
dlg.classList.add('hide');
@ -390,7 +375,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
dlg.setAttribute('data-lockscroll', 'true');
}
if (options.enableHistory !== false && appRouter.enableNativeHistory()) {
if (options.enableHistory !== false) {
dlg.setAttribute('data-history', 'true');
}
@ -406,17 +391,14 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
dlg.setAttribute('data-autofocus', 'true');
}
var defaultEntryAnimation;
var defaultExitAnimation;
defaultEntryAnimation = 'scaleup';
defaultExitAnimation = 'scaledown';
var entryAnimation = options.entryAnimation || defaultEntryAnimation;
var exitAnimation = options.exitAnimation || defaultExitAnimation;
const defaultEntryAnimation = 'scaleup';
const defaultExitAnimation = 'scaledown';
const entryAnimation = options.entryAnimation || defaultEntryAnimation;
const exitAnimation = options.exitAnimation || defaultExitAnimation;
// If it's not fullscreen then lower the default animation speed to make it open really fast
var entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
var exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280);
const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220);
dlg.animationConfig = {
// scale up
@ -461,24 +443,22 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
if (options.size) {
dlg.classList.add('dialog-fixedSize');
dlg.classList.add('dialog-' + options.size);
dlg.classList.add(`dialog-${options.size}`);
}
if (enableAnimation()) {
switch (dlg.animationConfig.entry.name) {
case 'fadein':
dlg.style.animation = 'fadein ' + entryAnimationDuration + 'ms ease-out normal';
dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`;
break;
case 'scaleup':
dlg.style.animation = 'scaleup ' + entryAnimationDuration + 'ms ease-out normal both';
dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`;
break;
case 'slideup':
dlg.style.animation = 'slideup ' + entryAnimationDuration + 'ms ease-out normal';
dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`;
break;
case 'slidedown':
dlg.style.animation = 'slidedown ' + entryAnimationDuration + 'ms ease-out normal';
dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`;
break;
default:
break;
@ -488,12 +468,15 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager',
return dlg;
}
return {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: function (val) {
globalOnOpenCallback = val;
}
};
});
export function setOnOpen(val) {
globalOnOpenCallback = val;
}
/* eslint-enable indent */
export default {
open: open,
close: close,
createDialog: createDialog,
setOnOpen: setOnOpen
};

View file

@ -1,9 +1,19 @@
define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-input', 'paper-icon-button-light', 'css!./directorybrowser', 'formDialogStyle', 'emby-button'], function(loading, dialogHelper, dom, globalize) {
'use strict';
import loading from 'loading';
import dialogHelper from 'dialogHelper';
import dom from 'dom';
import globalize from 'globalize';
import 'listViewStyle';
import 'emby-input';
import 'paper-icon-button-light';
import 'css!./directorybrowser';
import 'formDialogStyle';
import 'emby-button';
/* eslint-disable indent */
function getSystemInfo() {
return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then(
function(info) {
info => {
systemInfo = info;
return info;
}
@ -21,9 +31,9 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
loading.show();
var promises = [];
const promises = [];
if ('Network' === path) {
if (path === 'Network') {
promises.push(ApiClient.getNetworkDevices());
} else {
if (path) {
@ -35,10 +45,10 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
Promise.all(promises).then(
function(responses) {
var folders = responses[0];
var parentPath = responses[1] || '';
var html = '';
responses => {
const folders = responses[0];
const parentPath = responses[1] || '';
let html = '';
page.querySelector('.results').scrollTop = 0;
page.querySelector('#txtDirectoryPickerPath').value = path || '';
@ -46,9 +56,9 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
if (path) {
html += getItem('lnkPath lnkDirectory', '', parentPath, '...');
}
for (var i = 0, length = folders.length; i < length; i++) {
var folder = folders[i];
var cssClass = 'File' === folder.Type ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
for (let i = 0, length = folders.length; i < length; i++) {
const folder = folders[i];
const cssClass = folder.Type === 'File' ? 'lnkPath lnkFile' : 'lnkPath lnkDirectory';
html += getItem(cssClass, folder.Type, folder.Path, folder.Name);
}
@ -58,7 +68,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
page.querySelector('.results').innerHTML = html;
loading.hide();
}, function() {
}, () => {
if (updatePathOnError) {
page.querySelector('#txtDirectoryPickerPath').value = '';
page.querySelector('.results').innerHTML = '';
@ -69,8 +79,8 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
function getItem(cssClass, type, path, name) {
var html = '';
html += '<div class="listItem listItem-border ' + cssClass + '" data-type="' + type + '" data-path="' + path + '">';
let html = '';
html += `<div class="listItem listItem-border ${cssClass}" data-type="${type}" data-path="${path}">`;
html += '<div class="listItemBody" style="padding-left:0;padding-top:.5em;padding-bottom:.5em;">';
html += '<div class="listItemBodyText">';
html += name;
@ -82,19 +92,19 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
function getEditorHtml(options, systemInfo) {
var html = '';
let html = '';
html += '<div class="formDialogContent scrollY">';
html += '<div class="dialogContentInner dialog-content-centered" style="padding-top:2em;">';
if (!options.pathReadOnly) {
var instruction = options.instruction ? options.instruction + '<br/><br/>' : '';
const instruction = options.instruction ? `${options.instruction}<br/><br/>` : '';
html += '<div class="infoBanner" style="margin-bottom:1.5em;">';
html += instruction;
if ('bsd' === systemInfo.OperatingSystem.toLowerCase()) {
if (systemInfo.OperatingSystem.toLowerCase() === 'bsd') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerBSDInstruction');
html += '<br/>';
} else if ('linux' === systemInfo.OperatingSystem.toLowerCase()) {
} else if (systemInfo.OperatingSystem.toLowerCase() === 'linux') {
html += '<br/>';
html += '<br/>';
html += globalize.translate('MessageDirectoryPickerLinuxInstruction');
@ -105,17 +115,17 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
html += '<form style="margin:auto;">';
html += '<div class="inputContainer" style="display: flex; align-items: center;">';
html += '<div style="flex-grow:1;">';
var labelKey;
let labelKey;
if (options.includeFiles !== true) {
labelKey = 'LabelFolder';
} else {
labelKey = 'LabelPath';
}
var readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
html += '<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ' + readOnlyAttribute + ' label="' + globalize.translate(labelKey) + '"/>';
const readOnlyAttribute = options.pathReadOnly ? ' readonly' : '';
html += `<input is="emby-input" id="txtDirectoryPickerPath" type="text" required="required" ${readOnlyAttribute} label="${globalize.translate(labelKey)}"/>`;
html += '</div>';
if (!readOnlyAttribute) {
html += '<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="' + globalize.translate('ButtonRefresh') + '"><span class="material-icons search"></span></button>';
html += `<button type="button" is="paper-icon-button-light" class="btnRefreshDirectories emby-input-iconbutton" title="${globalize.translate('Refresh')}"><span class="material-icons search"></span></button>`;
}
html += '</div>';
if (!readOnlyAttribute) {
@ -123,14 +133,14 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
if (options.enableNetworkSharePath) {
html += '<div class="inputContainer" style="margin-top:2em;">';
html += '<input is="emby-input" id="txtNetworkPath" type="text" label="' + globalize.translate('LabelOptionalNetworkPath') + '"/>';
html += `<input is="emby-input" id="txtNetworkPath" type="text" label="${globalize.translate('LabelOptionalNetworkPath')}"/>`;
html += '<div class="fieldDescription">';
html += globalize.translate('LabelOptionalNetworkPathHelp', '<b>\\\\server</b>', '<b>\\\\192.168.1.101</b>');
html += '</div>';
html += '</div>';
}
html += '<div class="formDialogFooter">';
html += '<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">' + globalize.translate('ButtonOk') + '</button>';
html += `<button is="emby-button" type="submit" class="raised button-submit block formDialogFooterItem">${globalize.translate('ButtonOk')}</button>`;
html += '</div>';
html += '</form>';
html += '</div>';
@ -147,7 +157,7 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
function alertTextWithOptions(options) {
require(['alert'], function(alert) {
import('alert').then(({default: alert}) => {
alert(options);
});
}
@ -156,11 +166,11 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
return apiClient.ajax({
type: 'POST',
url: apiClient.getUrl('Environment/ValidatePath'),
data: {
data: JSON.stringify({
ValidateWriteable: validateWriteable,
Path: path
}
}).catch(function(response) {
})
}).catch(response => {
if (response) {
if (response.status === 404) {
alertText(globalize.translate('PathNotFound'));
@ -180,10 +190,10 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
function initEditor(content, options, fileOptions) {
content.addEventListener('click', function(e) {
var lnkPath = dom.parentWithClass(e.target, 'lnkPath');
content.addEventListener('click', e => {
const lnkPath = dom.parentWithClass(e.target, 'lnkPath');
if (lnkPath) {
var path = lnkPath.getAttribute('data-path');
const path = lnkPath.getAttribute('data-path');
if (lnkPath.classList.contains('lnkFile')) {
content.querySelector('#txtDirectoryPickerPath').value = path;
} else {
@ -192,25 +202,25 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
}
});
content.addEventListener('click', function(e) {
content.addEventListener('click', e => {
if (dom.parentWithClass(e.target, 'btnRefreshDirectories')) {
var path = content.querySelector('#txtDirectoryPickerPath').value;
const path = content.querySelector('#txtDirectoryPickerPath').value;
refreshDirectoryBrowser(content, path, fileOptions);
}
});
content.addEventListener('change', function(e) {
var txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
if (txtDirectoryPickerPath && 'txtDirectoryPickerPath' === txtDirectoryPickerPath.id) {
content.addEventListener('change', e => {
const txtDirectoryPickerPath = dom.parentWithTag(e.target, 'INPUT');
if (txtDirectoryPickerPath && txtDirectoryPickerPath.id === 'txtDirectoryPickerPath') {
refreshDirectoryBrowser(content, txtDirectoryPickerPath.value, fileOptions);
}
});
content.querySelector('form').addEventListener('submit', function(e) {
if (options.callback) {
var networkSharePath = this.querySelector('#txtNetworkPath');
let networkSharePath = this.querySelector('#txtNetworkPath');
networkSharePath = networkSharePath ? networkSharePath.value : null;
var path = this.querySelector('#txtDirectoryPickerPath').value;
const path = this.querySelector('#txtDirectoryPickerPath').value;
validatePath(path, options.validateWriteable, ApiClient).then(options.callback(path, networkSharePath));
}
e.preventDefault();
@ -224,77 +234,79 @@ define(['loading', 'dialogHelper', 'dom', 'globalize', 'listViewStyle', 'emby-in
return Promise.resolve(options.path);
} else {
return ApiClient.getJSON(ApiClient.getUrl('Environment/DefaultDirectoryBrowser')).then(
function(result) {
result => {
return result.Path || '';
}, function() {
}, () => {
return '';
}
);
}
}
function directoryBrowser() {
var currentDialog;
var self = this;
self.show = function(options) {
options = options || {};
var fileOptions = {
includeDirectories: true
};
if (options.includeDirectories != null) {
fileOptions.includeDirectories = options.includeDirectories;
}
if (options.includeFiles != null) {
fileOptions.includeFiles = options.includeFiles;
}
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
function(responses) {
var systemInfo = responses[0];
var initialPath = responses[1];
var dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
dlg.classList.add('directoryPicker');
dlg.classList.add('formDialog');
var html = '';
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += options.header || globalize.translate('HeaderSelectPath');
html += '</h3>';
html += '</div>';
html += getEditorHtml(options, systemInfo);
dlg.innerHTML = html;
initEditor(dlg, options, fileOptions);
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
dlg.querySelector('.btnCloseDialog').addEventListener('click', function() {
dialogHelper.close(dlg);
});
currentDialog = dlg;
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
var txtNetworkPath = dlg.querySelector('#txtNetworkPath');
if (txtNetworkPath) {
txtNetworkPath.value = options.networkSharePath || '';
}
if (!options.pathReadOnly) {
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
}
class directoryBrowser {
constructor() {
let currentDialog;
this.show = options => {
options = options || {};
const fileOptions = {
includeDirectories: true
};
if (options.includeDirectories != null) {
fileOptions.includeDirectories = options.includeDirectories;
}
);
};
self.close = function() {
if (currentDialog) {
dialogHelper.close(currentDialog);
}
};
if (options.includeFiles != null) {
fileOptions.includeFiles = options.includeFiles;
}
Promise.all([getSystemInfo(), getDefaultPath(options)]).then(
responses => {
const systemInfo = responses[0];
const initialPath = responses[1];
const dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('ui-body-a');
dlg.classList.add('background-theme-a');
dlg.classList.add('directoryPicker');
dlg.classList.add('formDialog');
let html = '';
html += '<div class="formDialogHeader">';
html += '<button is="paper-icon-button-light" class="btnCloseDialog autoSize" tabindex="-1"><span class="material-icons arrow_back"></span></button>';
html += '<h3 class="formDialogHeaderTitle">';
html += options.header || globalize.translate('HeaderSelectPath');
html += '</h3>';
html += '</div>';
html += getEditorHtml(options, systemInfo);
dlg.innerHTML = html;
initEditor(dlg, options, fileOptions);
dlg.addEventListener('close', onDialogClosed);
dialogHelper.open(dlg);
dlg.querySelector('.btnCloseDialog').addEventListener('click', () => {
dialogHelper.close(dlg);
});
currentDialog = dlg;
dlg.querySelector('#txtDirectoryPickerPath').value = initialPath;
const txtNetworkPath = dlg.querySelector('#txtNetworkPath');
if (txtNetworkPath) {
txtNetworkPath.value = options.networkSharePath || '';
}
if (!options.pathReadOnly) {
refreshDirectoryBrowser(dlg, initialPath, fileOptions, true);
}
}
);
};
this.close = () => {
if (currentDialog) {
dialogHelper.close(currentDialog);
}
};
}
}
var systemInfo;
return directoryBrowser;
});
let systemInfo;
/* eslint-enable indent */
export default directoryBrowser;

View file

@ -1,22 +1,41 @@
define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', 'apphost', 'focusManager', 'datetime', 'globalize', 'loading', 'connectionManager', 'skinManager', 'dom', 'events', 'emby-select', 'emby-checkbox', 'emby-button'], function (require, browser, layoutManager, appSettings, pluginManager, appHost, focusManager, datetime, globalize, loading, connectionManager, skinManager, dom, events) {
'use strict';
import browser from 'browser';
import layoutManager from 'layoutManager';
import pluginManager from 'pluginManager';
import appHost from 'apphost';
import focusManager from 'focusManager';
import datetime from 'datetime';
import globalize from 'globalize';
import loading from 'loading';
import connectionManager from 'connectionManager';
import skinManager from 'skinManager';
import events from 'events';
import 'emby-select';
import 'emby-checkbox';
import 'emby-button';
function fillThemes(select, isDashboard) {
select.innerHTML = skinManager.getThemes().map(function (t) {
var value = t.id;
if (t.isDefault && !isDashboard) {
value = '';
} else if (t.isDefaultServerDashboard && isDashboard) {
value = '';
}
/* eslint-disable indent */
return '<option value="' + value + '">' + t.name + '</option>';
}).join('');
function fillThemes(context, userSettings) {
const select = context.querySelector('#selectTheme');
skinManager.getThemes().then(themes => {
select.innerHTML = themes.map(t => {
return `<option value="${t.id}">${t.name}</option>`;
}).join('');
// get default theme
var defaultTheme = themes.find(theme => {
return theme.default;
});
// set the current theme
select.value = userSettings.theme() || defaultTheme.id;
});
}
function loadScreensavers(context, userSettings) {
var selectScreensaver = context.querySelector('.selectScreensaver');
var options = pluginManager.ofType('screensaver').map(function (plugin) {
const selectScreensaver = context.querySelector('.selectScreensaver');
const options = pluginManager.ofType('screensaver').map(plugin => {
return {
name: plugin.name,
value: plugin.id
@ -28,9 +47,10 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
value: 'none'
});
selectScreensaver.innerHTML = options.map(function (o) {
return '<option value="' + o.value + '">' + o.name + '</option>';
selectScreensaver.innerHTML = options.map(o => {
return `<option value="${o.value}">${o.name}</option>`;
}).join('');
selectScreensaver.value = userSettings.screensaver();
if (!selectScreensaver.value) {
@ -39,61 +59,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
}
}
function loadSoundEffects(context, userSettings) {
var selectSoundEffects = context.querySelector('.selectSoundEffects');
var options = pluginManager.ofType('soundeffects').map(function (plugin) {
return {
name: plugin.name,
value: plugin.id
};
});
options.unshift({
name: globalize.translate('None'),
value: 'none'
});
selectSoundEffects.innerHTML = options.map(function (o) {
return '<option value="' + o.value + '">' + o.name + '</option>';
}).join('');
selectSoundEffects.value = userSettings.soundEffects();
if (!selectSoundEffects.value) {
// TODO: set the default instead of none
selectSoundEffects.value = 'none';
}
}
function loadSkins(context, userSettings) {
var selectSkin = context.querySelector('.selectSkin');
var options = pluginManager.ofType('skin').map(function (plugin) {
return {
name: plugin.name,
value: plugin.id
};
});
selectSkin.innerHTML = options.map(function (o) {
return '<option value="' + o.value + '">' + o.name + '</option>';
}).join('');
selectSkin.value = userSettings.skin();
if (!selectSkin.value && options.length) {
selectSkin.value = options[0].value;
}
if (options.length > 1 && appHost.supports('skins')) {
context.querySelector('.selectSkinContainer').classList.remove('hide');
} else {
context.querySelector('.selectSkinContainer').classList.add('hide');
}
}
function showOrHideMissingEpisodesField(context, user, apiClient) {
function showOrHideMissingEpisodesField(context) {
if (browser.tizen || browser.web0s) {
context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide');
return;
@ -102,17 +68,7 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide');
}
function loadForm(context, user, userSettings, apiClient) {
var loggedInUserId = apiClient.getCurrentUserId();
var userId = user.Id;
if (user.Policy.IsAdministrator) {
context.querySelector('.selectDashboardThemeContainer').classList.remove('hide');
} else {
context.querySelector('.selectDashboardThemeContainer').classList.add('hide');
}
function loadForm(context, user, userSettings) {
if (appHost.supports('displaylanguage')) {
context.querySelector('.languageSection').classList.remove('hide');
} else {
@ -131,18 +87,6 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('.learnHowToContributeContainer').classList.add('hide');
}
if (appHost.supports('runatstartup')) {
context.querySelector('.fldAutorun').classList.remove('hide');
} else {
context.querySelector('.fldAutorun').classList.add('hide');
}
if (appHost.supports('soundeffects')) {
context.querySelector('.fldSoundEffects').classList.remove('hide');
} else {
context.querySelector('.fldSoundEffects').classList.add('hide');
}
if (appHost.supports('screensaver')) {
context.querySelector('.selectScreensaverContainer').classList.remove('hide');
} else {
@ -165,22 +109,15 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('.fldThemeVideo').classList.add('hide');
}
context.querySelector('.chkRunAtStartup').checked = appSettings.runAtStartup();
var selectTheme = context.querySelector('#selectTheme');
var selectDashboardTheme = context.querySelector('#selectDashboardTheme');
fillThemes(selectTheme);
fillThemes(selectDashboardTheme, true);
fillThemes(context, userSettings);
loadScreensavers(context, userSettings);
loadSoundEffects(context, userSettings);
loadSkins(context, userSettings);
context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false;
context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs();
context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos();
context.querySelector('#chkFadein').checked = userSettings.enableFastFadein();
context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash();
context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops();
context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner();
@ -189,20 +126,14 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize();
selectDashboardTheme.value = userSettings.dashboardTheme() || '';
selectTheme.value = userSettings.theme() || '';
context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || '';
showOrHideMissingEpisodesField(context, user, apiClient);
showOrHideMissingEpisodesField(context);
loading.hide();
}
function saveUser(context, user, userSettingsInstance, apiClient) {
appSettings.runAtStartup(context.querySelector('.chkRunAtStartup').checked);
user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked;
if (appHost.supports('displaylanguage')) {
@ -213,16 +144,13 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked);
userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked);
userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value);
userSettingsInstance.theme(context.querySelector('#selectTheme').value);
userSettingsInstance.soundEffects(context.querySelector('.selectSoundEffects').value);
userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value);
userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value);
userSettingsInstance.skin(context.querySelector('.selectSkin').value);
userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked);
userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked);
userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked);
userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked);
@ -237,29 +165,29 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show();
apiClient.getUser(userId).then(function (user) {
saveUser(context, user, userSettings, apiClient).then(function () {
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('SettingsSaved'));
});
}
events.trigger(instance, 'saved');
}, function () {
}, () => {
loading.hide();
});
});
}
function onSubmit(e) {
var self = this;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userId = self.options.userId;
var userSettings = self.options.userSettings;
const self = this;
const apiClient = connectionManager.getApiClient(self.options.serverId);
const userId = self.options.userId;
const userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(function () {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
userSettings.setUserInfo(userId, apiClient).then(() => {
const enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
@ -270,50 +198,51 @@ define(['require', 'browser', 'layoutManager', 'appSettings', 'pluginManager', '
return false;
}
function embed(options, self) {
require(['text!./displaySettings.template.html'], function (template) {
options.element.innerHTML = globalize.translateDocument(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
self.loadData(options.autoFocus);
});
async function embed(options, self) {
const { default: template } = await import('text!./displaySettings.template.html');
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
if (options.enableSaveButton) {
options.element.querySelector('.btnSave').classList.remove('hide');
}
self.loadData(options.autoFocus);
}
function DisplaySettings(options) {
this.options = options;
embed(options, this);
}
class DisplaySettings {
constructor(options) {
this.options = options;
embed(options, this);
}
DisplaySettings.prototype.loadData = function (autoFocus) {
var self = this;
var context = self.options.element;
loadData(autoFocus) {
const self = this;
const context = self.options.element;
loading.show();
loading.show();
var userId = self.options.userId;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userSettings = self.options.userSettings;
const userId = self.options.userId;
const apiClient = connectionManager.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings;
return apiClient.getUser(userId).then(function (user) {
return userSettings.setUserInfo(userId, apiClient).then(function () {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
return apiClient.getUser(userId).then(user => {
return userSettings.setUserInfo(userId, apiClient).then(() => {
self.dataLoaded = true;
loadForm(context, user, userSettings);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
});
});
};
}
DisplaySettings.prototype.submit = function () {
onSubmit.call(this);
};
submit() {
onSubmit.call(this);
}
DisplaySettings.prototype.destroy = function () {
this.options = null;
};
destroy() {
this.options = null;
}
}
return DisplaySettings;
});
/* eslint-enable indent */
export default DisplaySettings;

View file

@ -1,5 +1,4 @@
<form style="margin: 0 auto;">
<h2 class="sectionTitle">
${Display}
</h2>
@ -123,40 +122,36 @@
<div class="fieldDescription">${LabelPleaseRestart}</div>
</div>
<div class="selectContainer hide selectSkinContainer">
<select is="emby-select" class="selectSkin" label="${LabelSkin}"></select>
</div>
<div class="selectContainer">
<select id="selectTheme" is="emby-select" label="${LabelTheme}"></select>
</div>
<div class="selectContainer selectDashboardThemeContainer hide">
<select id="selectDashboardTheme" is="emby-select" label="${LabelDashboardTheme}"></select>
</div>
<div class="selectContainer hide selectScreensaverContainer">
<select is="emby-select" class="selectScreensaver" label="${LabelScreensaver}"></select>
</div>
<div class="selectContainer fldSoundEffects hide">
<select is="emby-select" class="selectSoundEffects" label="${LabelSoundEffects}"></select>
</div>
<div class="inputContainer inputContainer-withDescription fldFadein">
<div class="inputContainer inputContainer-withDescription">
<input is="emby-input" type="number" id="txtLibraryPageSize" pattern="[0-9]*" required="required" min="0" max="1000" step="1" label="${LabelLibraryPageSize}" />
<div class="fieldDescription">${LabelLibraryPageSizeHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldFadein">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkFadein" />
<span>${EnableFastImageFadeIn}</span>
<span>${EnableFasterAnimations}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableFastImageFadeInHelp}</div>
<div class="fieldDescription checkboxFieldDescription">${EnableFasterAnimationsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldDetailsBanner">
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBlurhash" />
<span>${EnableBlurHash}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableBlurHashHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" id="chkDetailsBanner" />
<span>${EnableDetailsBanner}</span>
@ -167,7 +162,7 @@
<div class="checkboxContainer checkboxContainer-withDescription fldBackdrops hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkBackdrops" />
<span>${EnableBackdrops}</span>
<span>${Backdrops}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableBackdropsHelp}</div>
</div>
@ -175,7 +170,7 @@
<div class="checkboxContainer checkboxContainer-withDescription fldThemeSong hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeSong" />
<span>${EnableThemeSongs}</span>
<span>${ThemeSongs}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableThemeSongsHelp}</div>
</div>
@ -183,18 +178,11 @@
<div class="checkboxContainer checkboxContainer-withDescription fldThemeVideo hide">
<label>
<input type="checkbox" is="emby-checkbox" id="chkThemeVideo" />
<span>${EnableThemeVideos}</span>
<span>${ThemeVideos}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableThemeVideosHelp}</div>
</div>
<div class="checkboxContainer hide fldAutorun">
<label>
<input type="checkbox" is="emby-checkbox" class="chkRunAtStartup" />
<span>${RunAtStartup}</span>
</label>
</div>
<div class="checkboxContainer checkboxContainer-withDescription fldDisplayMissingEpisodes hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkDisplayMissingEpisodes" />

View file

@ -1,5 +1,14 @@
define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoader', 'globalize', 'layoutManager', 'scrollStyles', 'emby-itemscontainer'], function (loading, libraryBrowser, cardBuilder, dom, appHost, imageLoader, globalize, layoutManager) {
'use strict';
import loading from 'loading';
import cardBuilder from 'cardBuilder';
import dom from 'dom';
import appHost from 'apphost';
import imageLoader from 'imageLoader';
import globalize from 'globalize';
import layoutManager from 'layoutManager';
import 'scrollStyles';
import 'emby-itemscontainer';
/* eslint-disable indent */
function enableScrollX() {
return !layoutManager.desktop;
@ -94,8 +103,8 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
}
function loadSection(elem, userId, topParentId, section, isSingleSection) {
var screenWidth = dom.getWindowSize().innerWidth;
var options = {
const screenWidth = dom.getWindowSize().innerWidth;
const options = {
SortBy: 'SortName',
SortOrder: 'Ascending',
Filters: 'IsFavorite',
@ -118,9 +127,9 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
}
}
var promise;
let promise;
if ('MusicArtist' === section.types) {
if (section.types === 'MusicArtist') {
promise = ApiClient.getArtists(userId, options);
} else {
options.IncludeItemTypes = section.types;
@ -128,7 +137,7 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
}
return promise.then(function (result) {
var html = '';
let html = '';
if (result.Items.length) {
if (html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">', !layoutManager.tv && options.Limit && result.Items.length >= options.Limit) {
@ -144,7 +153,7 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
html += '</div>';
if (enableScrollX()) {
var scrollXClass = 'scrollX hiddenScrollX';
let scrollXClass = 'scrollX hiddenScrollX';
if (layoutManager.tv) {
scrollXClass += ' smoothScrollX';
}
@ -154,14 +163,13 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
html += '<div is="emby-itemscontainer" class="itemsContainer vertical-wrap padded-left padded-right">';
}
var supportsImageAnalysis = appHost.supports('imageanalysis');
var cardLayout = (appHost.preferVisualCards || supportsImageAnalysis) && section.autoCardLayout && section.showTitle;
let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle;
cardLayout = false;
html += cardBuilder.getCardsHtml(result.Items, {
preferThumb: section.preferThumb,
shape: section.shape,
centerText: section.centerText && !cardLayout,
overlayText: false !== section.overlayText,
overlayText: section.overlayText !== false,
showTitle: section.showTitle,
showParentTitle: section.showParentTitle,
scalable: true,
@ -180,10 +188,10 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
});
}
function loadSections(page, userId, topParentId, types) {
export function loadSections(page, userId, topParentId, types) {
loading.show();
var sections = getSections();
var sectionid = getParameterByName('sectionid');
let sections = getSections();
const sectionid = getParameterByName('sectionid');
if (sectionid) {
sections = sections.filter(function (s) {
@ -193,30 +201,28 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
if (types) {
sections = sections.filter(function (s) {
return -1 !== types.indexOf(s.id);
return types.indexOf(s.id) !== -1;
});
}
var i;
var length;
var elem = page.querySelector('.favoriteSections');
let elem = page.querySelector('.favoriteSections');
if (!elem.innerHTML) {
var html = '';
let html = '';
for (i = 0, length = sections.length; i < length; i++) {
for (let i = 0, length = sections.length; i < length; i++) {
html += '<div class="verticalSection section' + sections[i].id + '"></div>';
}
elem.innerHTML = html;
}
var promises = [];
const promises = [];
for (i = 0, length = sections.length; i < length; i++) {
var section = sections[i];
for (let i = 0, length = sections.length; i < length; i++) {
const section = sections[i];
elem = page.querySelector('.section' + section.id);
promises.push(loadSection(elem, userId, topParentId, section, 1 === sections.length));
promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1));
}
Promise.all(promises).then(function () {
@ -224,7 +230,8 @@ define(['loading', 'libraryBrowser', 'cardBuilder', 'dom', 'apphost', 'imageLoad
});
}
return {
render: loadSections
};
});
export default {
render: loadSections
};
/* eslint-enable indent */

View file

@ -1,24 +1,20 @@
define([], function () {
'use strict';
function getFetchPromise(request) {
var headers = request.headers || {};
/* eslint-disable indent */
export function getFetchPromise(request) {
const headers = request.headers || {};
if (request.dataType === 'json') {
headers.accept = 'application/json';
}
var fetchRequest = {
const fetchRequest = {
headers: headers,
method: request.type,
credentials: 'same-origin'
};
var contentType = request.contentType;
let contentType = request.contentType;
if (request.data) {
if (typeof request.data === 'string') {
fetchRequest.body = request.data;
} else {
@ -29,16 +25,15 @@ define([], function () {
}
if (contentType) {
headers['Content-Type'] = contentType;
}
var url = request.url;
let url = request.url;
if (request.query) {
var paramString = paramsToString(request.query);
const paramString = paramsToString(request.query);
if (paramString) {
url += '?' + paramString;
url += `?${paramString}`;
}
}
@ -50,12 +45,10 @@ define([], function () {
}
function fetchWithTimeout(url, options, timeoutMs) {
console.debug('fetchWithTimeout: timeoutMs: ' + timeoutMs + ', url: ' + url);
console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);
return new Promise(function (resolve, reject) {
var timeout = setTimeout(reject, timeoutMs);
const timeout = setTimeout(reject, timeoutMs);
options = options || {};
options.credentials = 'same-origin';
@ -63,50 +56,46 @@ define([], function () {
fetch(url, options).then(function (response) {
clearTimeout(timeout);
console.debug('fetchWithTimeout: succeeded connecting to url: ' + url);
console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`);
resolve(response);
}, function (error) {
clearTimeout(timeout);
console.debug('fetchWithTimeout: timed out connecting to url: ' + url);
console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`);
reject();
reject(error);
});
});
}
/**
* @param params {Record<string, string | number | boolean>}
* @returns {string} Query string
*/
function paramsToString(params) {
var values = [];
for (var key in params) {
var value = params[key];
if (value !== null && value !== undefined && value !== '') {
values.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
}
}
return values.join('&');
return Object.entries(params)
// eslint-disable-next-line no-unused-vars
.filter(([_, v]) => v !== null && v !== undefined && v !== '')
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
}
function ajax(request) {
export function ajax(request) {
if (!request) {
throw new Error('Request cannot be null');
}
request.headers = request.headers || {};
console.debug('requesting url: ' + request.url);
console.debug(`requesting url: ${request.url}`);
return getFetchPromise(request).then(function (response) {
console.debug('response status: ' + response.status + ', url: ' + request.url);
console.debug(`response status: ${response.status}, url: ${request.url}`);
if (response.status < 400) {
if (request.dataType === 'json' || request.headers.accept === 'application/json') {
return response.json();
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0) {
} else if (request.dataType === 'text' || (response.headers.get('Content-Type') || '').toLowerCase().startsWith('text/')) {
return response.text();
} else {
return response;
@ -115,12 +104,8 @@ define([], function () {
return Promise.reject(response);
}
}, function (err) {
console.error('request failed to url: ' + request.url);
console.error(`request failed to url: ${request.url}`);
throw err;
});
}
return {
getFetchPromise: getFetchPromise,
ajax: ajax
};
});
/* eslint-enable indent */

View file

@ -1,21 +1,28 @@
define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'browser', 'require', 'emby-checkbox', 'emby-collapse', 'css!./style'], function (dom, dialogHelper, globalize, connectionManager, events, browser, require) {
'use strict';
import dom from 'dom';
import dialogHelper from 'dialogHelper';
import globalize from 'globalize';
import connectionManager from 'connectionManager';
import events from 'events';
import 'emby-checkbox';
import 'emby-collapse';
import 'css!./style.css';
/* eslint-disable indent */
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
const elem = context.querySelector(selector);
if (items.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
var html = '';
let html = '';
html += '<div class="checkboxList">';
html += items.map(function (filter) {
var itemHtml = '';
var checkedHtml = isCheckedFn(filter) ? ' checked' : '';
let itemHtml = '';
const checkedHtml = isCheckedFn(filter) ? 'checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + filter + '</span>';
itemHtml += `<input is="emby-checkbox" type="checkbox" ${checkedHtml} data-filter="${filter}" class="${cssClass}"/>`;
itemHtml += `<span>${filter}</span>`;
itemHtml += '</label>';
return itemHtml;
}).join('');
@ -24,21 +31,24 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
}
function renderFilters(context, result, query) {
if (result.Tags) {
result.Tags.length = Math.min(result.Tags.length, 50);
}
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
var delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) {
var delimeter = '|';
return (delimeter + (query.OfficialRatings || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) {
var delimeter = '|';
return (delimeter + (query.Tags || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = '|';
return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter);
});
renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) {
var delimeter = ',';
return (delimeter + (query.Years || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
const delimeter = ',';
return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter);
});
}
@ -52,59 +62,58 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
});
}
/**
* @param context {HTMLDivElement} Dialog
* @param options {any} Options
*/
function updateFilterControls(context, options) {
var elems;
var i;
var length;
var query = options.query;
const query = options.query;
if (options.mode == 'livetvchannels') {
context.querySelector('.chkFavorite').checked = query.IsFavorite == true;
context.querySelector('.chkLikes').checked = query.IsLiked == true;
context.querySelector('.chkDislikes').checked = query.IsDisliked == true;
if (options.mode === 'livetvchannels') {
context.querySelector('.chkFavorite').checked = query.IsFavorite === true;
context.querySelector('.chkLikes').checked = query.IsLiked === true;
context.querySelector('.chkDislikes').checked = query.IsDisliked === true;
} else {
elems = context.querySelectorAll('.chkStandardFilter');
for (i = 0, length = elems.length; i < length; i++) {
var chkStandardFilter = elems[i];
var filters = ',' + (query.Filters || '');
var filterName = chkStandardFilter.getAttribute('data-filter');
chkStandardFilter.checked = filters.indexOf(',' + filterName) != -1;
for (const elem of context.querySelectorAll('.chkStandardFilter')) {
const filters = `,${query.Filters || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
var chkVideoTypeFilter = elems[i];
var filters = ',' + (query.VideoTypes || '');
var filterName = chkVideoTypeFilter.getAttribute('data-filter');
chkVideoTypeFilter.checked = filters.indexOf(',' + filterName) != -1;
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
const filters = `,${query.VideoTypes || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
context.querySelector('.chk3DFilter').checked = query.Is3D == true;
context.querySelector('.chkHDFilter').checked = query.IsHD == true;
context.querySelector('.chk4KFilter').checked = query.Is4K == true;
context.querySelector('.chkSDFilter').checked = query.IsHD == true;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles == true;
context.querySelector('#chkTrailer').checked = query.HasTrailer == true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong == true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo == true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature == true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber == 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing == true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired == true;
for (i = 0, length = elems.length; i < length; i++) {
var chkStatus = elems[i];
var filters = ',' + (query.SeriesStatus || '');
var filterName = chkStatus.getAttribute('data-filter');
chkStatus.checked = filters.indexOf(',' + filterName) != -1;
context.querySelector('.chk3DFilter').checked = query.Is3D === true;
context.querySelector('.chkHDFilter').checked = query.IsHD === true;
context.querySelector('.chk4KFilter').checked = query.Is4K === true;
context.querySelector('.chkSDFilter').checked = query.IsHD === true;
context.querySelector('#chkSubtitle').checked = query.HasSubtitles === true;
context.querySelector('#chkTrailer').checked = query.HasTrailer === true;
context.querySelector('#chkThemeSong').checked = query.HasThemeSong === true;
context.querySelector('#chkThemeVideo').checked = query.HasThemeVideo === true;
context.querySelector('#chkSpecialFeature').checked = query.HasSpecialFeature === true;
context.querySelector('#chkSpecialEpisode').checked = query.ParentIndexNumber === 0;
context.querySelector('#chkMissingEpisode').checked = query.IsMissing === true;
context.querySelector('#chkFutureEpisode').checked = query.IsUnaired === true;
for (const elem of context.querySelectorAll('.chkStatus')) {
const filters = `,${query.SeriesStatus || ''}`;
const filterName = elem.getAttribute('data-filter');
elem.checked = filters.includes(`,${filterName}`);
}
}
/**
* @param instance {FilterDialog} An instance of FilterDialog
*/
function triggerChange(instance) {
events.trigger(instance, 'filterchange');
}
function setVisibility(context, options) {
if (options.mode == 'livetvchannels' || options.mode == 'albums' || options.mode == 'artists' || options.mode == 'albumartists' || options.mode == 'songs') {
if (options.mode === 'livetvchannels' || options.mode === 'albums' || options.mode === 'artists' || options.mode === 'albumartists' || options.mode === 'songs') {
hideByClass(context, 'videoStandard');
}
@ -115,263 +124,287 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
context.querySelector('.yearFilters').classList.remove('hide');
}
if (options.mode == 'movies' || options.mode == 'episodes') {
if (options.mode === 'movies' || options.mode === 'episodes') {
context.querySelector('.videoTypeFilters').classList.remove('hide');
}
if (options.mode == 'movies' || options.mode == 'series' || options.mode == 'episodes') {
if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') {
context.querySelector('.features').classList.remove('hide');
}
if (options.mode == 'series') {
if (options.mode === 'series') {
context.querySelector('.seriesStatus').classList.remove('hide');
}
if (options.mode == 'episodes') {
if (options.mode === 'episodes') {
showByClass(context, 'episodeFilter');
}
}
function showByClass(context, className) {
var elems = context.querySelectorAll('.' + className);
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].classList.remove('hide');
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.remove('hide');
}
}
function hideByClass(context, className) {
var elems = context.querySelectorAll('.' + className);
for (var i = 0, length = elems.length; i < length; i++) {
elems[i].classList.add('hide');
for (const elem of context.querySelectorAll(`.${className}`)) {
elem.classList.add('hide');
}
}
function enableDynamicFilters(mode) {
return mode == 'movies' || mode == 'series' || mode == 'albums' || mode == 'albumartists' || mode == 'artists' || mode == 'songs' || mode == 'episodes';
return mode === 'movies' || mode === 'series' || mode === 'albums' || mode === 'albumartists' || mode === 'artists' || mode === 'songs' || mode === 'episodes';
}
return function (options) {
function onFavoriteChange() {
var query = options.query;
query.StartIndex = 0;
query.IsFavorite = !!this.checked || null;
triggerChange(self);
class FilterDialog {
constructor(options) {
/**
* @private
*/
this.options = options;
}
function onStandardFilterChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.Filters || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onFavoriteChange(elem) {
const query = this.options.query;
query.StartIndex = 0;
query.IsFavorite = !!elem.checked || null;
triggerChange(this);
}
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
/**
* @private
*/
onStandardFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.Filters || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.Filters = filters;
triggerChange(self);
triggerChange(this);
}
function onVideoTypeFilterChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.VideoTypes || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onVideoTypeFilterChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.VideoTypes || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.StartIndex = 0;
query.VideoTypes = filters;
triggerChange(self);
triggerChange(this);
}
function onStatusChange() {
var query = options.query;
var filterName = this.getAttribute('data-filter');
var filters = query.SeriesStatus || '';
filters = (',' + filters).replace(',' + filterName, '').substring(1);
/**
* @private
*/
onStatusChange(elem) {
const query = this.options.query;
const filterName = elem.getAttribute('data-filter');
let filters = query.SeriesStatus || '';
filters = (`,${filters}`).replace(`,${filterName}`, '').substring(1);
if (this.checked) {
filters = filters ? filters + ',' + filterName : filterName;
if (elem.checked) {
filters = filters ? `${filters},${filterName}` : filterName;
}
query.SeriesStatus = filters;
query.StartIndex = 0;
triggerChange(self);
triggerChange(this);
}
function bindEvents(context) {
var elems;
var i;
var length;
var query = options.query;
/**
* @param context {HTMLDivElement} The dialog
*/
bindEvents(context) {
const query = this.options.query;
if (options.mode == 'livetvchannels') {
elems = context.querySelectorAll('.chkFavorite');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onFavoriteChange);
if (this.options.mode === 'livetvchannels') {
for (const elem of context.querySelectorAll('.chkFavorite')) {
elem.addEventListener('change', () => this.onFavoriteChange(elem));
}
context.querySelector('.chkLikes').addEventListener('change', function () {
const chkLikes = context.querySelector('.chkLikes');
chkLikes.addEventListener('change', () => {
query.StartIndex = 0;
query.IsLiked = this.checked ? true : null;
triggerChange(self);
query.IsLiked = chkLikes.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkDislikes').addEventListener('change', function () {
const chkDislikes = context.querySelector('.chkDislikes');
chkDislikes.addEventListener('change', () => {
query.StartIndex = 0;
query.IsDisliked = this.checked ? true : null;
triggerChange(self);
query.IsDisliked = chkDislikes.checked ? true : null;
triggerChange(this);
});
} else {
elems = context.querySelectorAll('.chkStandardFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onStandardFilterChange);
for (const elem of context.querySelectorAll('.chkStandardFilter')) {
elem.addEventListener('change', () => this.onStandardFilterChange(elem));
}
}
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onVideoTypeFilterChange);
for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) {
elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem));
}
context.querySelector('.chk3DFilter').addEventListener('change', function () {
const chk3DFilter = context.querySelector('.chk3DFilter');
chk3DFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.Is3D = this.checked ? true : null;
triggerChange(self);
query.Is3D = chk3DFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chk4KFilter').addEventListener('change', function () {
const chk4KFilter = context.querySelector('.chk4KFilter');
chk4KFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.Is4K = this.checked ? true : null;
triggerChange(self);
query.Is4K = chk4KFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkHDFilter').addEventListener('change', function () {
const chkHDFilter = context.querySelector('.chkHDFilter');
chkHDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = this.checked ? true : null;
triggerChange(self);
query.IsHD = chkHDFilter.checked ? true : null;
triggerChange(this);
});
context.querySelector('.chkSDFilter').addEventListener('change', function () {
const chkSDFilter = context.querySelector('.chkSDFilter');
chkSDFilter.addEventListener('change', () => {
query.StartIndex = 0;
query.IsHD = this.checked ? false : null;
triggerChange(self);
query.IsHD = chkSDFilter.checked ? false : null;
triggerChange(this);
});
elems = context.querySelectorAll('.chkStatus');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].addEventListener('change', onStatusChange);
for (const elem of context.querySelectorAll('.chkStatus')) {
elem.addEventListener('change', () => this.onStatusChange(elem));
}
context.querySelector('#chkTrailer').addEventListener('change', function () {
const chkTrailer = context.querySelector('#chkTrailer');
chkTrailer.addEventListener('change', () => {
query.StartIndex = 0;
query.HasTrailer = this.checked ? true : null;
triggerChange(self);
query.HasTrailer = chkTrailer.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkThemeSong').addEventListener('change', function () {
const chkThemeSong = context.querySelector('#chkThemeSong');
chkThemeSong.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeSong = this.checked ? true : null;
triggerChange(self);
query.HasThemeSong = chkThemeSong.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkSpecialFeature').addEventListener('change', function () {
const chkSpecialFeature = context.querySelector('#chkSpecialFeature');
chkSpecialFeature.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSpecialFeature = this.checked ? true : null;
triggerChange(self);
query.HasSpecialFeature = chkSpecialFeature.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkThemeVideo').addEventListener('change', function () {
const chkThemeVideo = context.querySelector('#chkThemeVideo');
chkThemeVideo.addEventListener('change', () => {
query.StartIndex = 0;
query.HasThemeVideo = this.checked ? true : null;
triggerChange(self);
query.HasThemeVideo = chkThemeVideo.checked ? true : null;
triggerChange(this);
});
context.querySelector('#chkMissingEpisode').addEventListener('change', function () {
const chkMissingEpisode = context.querySelector('#chkMissingEpisode');
chkMissingEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.IsMissing = this.checked ? true : false;
triggerChange(self);
query.IsMissing = !!chkMissingEpisode.checked;
triggerChange(this);
});
context.querySelector('#chkSpecialEpisode').addEventListener('change', function () {
const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode');
chkSpecialEpisode.addEventListener('change', () => {
query.StartIndex = 0;
query.ParentIndexNumber = this.checked ? 0 : null;
triggerChange(self);
query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null;
triggerChange(this);
});
context.querySelector('#chkFutureEpisode').addEventListener('change', function () {
const chkFutureEpisode = context.querySelector('#chkFutureEpisode');
chkFutureEpisode.addEventListener('change', () => {
query.StartIndex = 0;
if (this.checked) {
if (chkFutureEpisode.checked) {
query.IsUnaired = true;
query.IsVirtualUnaired = null;
} else {
query.IsUnaired = null;
query.IsVirtualUnaired = false;
}
triggerChange(self);
triggerChange(this);
});
context.querySelector('#chkSubtitle').addEventListener('change', function () {
const chkSubtitle = context.querySelector('#chkSubtitle');
chkSubtitle.addEventListener('change', () => {
query.StartIndex = 0;
query.HasSubtitles = this.checked ? true : null;
triggerChange(self);
query.HasSubtitles = chkSubtitle.checked ? true : null;
triggerChange(this);
});
context.addEventListener('change', function (e) {
var chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
context.addEventListener('change', (e) => {
const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter');
if (chkGenreFilter) {
var filterName = chkGenreFilter.getAttribute('data-filter');
var filters = query.Genres || '';
var delimiter = '|';
const filterName = chkGenreFilter.getAttribute('data-filter');
let filters = query.Genres || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkGenreFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Genres = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter');
if (chkTagFilter) {
var filterName = chkTagFilter.getAttribute('data-filter');
var filters = query.Tags || '';
var delimiter = '|';
const filterName = chkTagFilter.getAttribute('data-filter');
let filters = query.Tags || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkTagFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Tags = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter');
if (chkYearFilter) {
var filterName = chkYearFilter.getAttribute('data-filter');
var filters = query.Years || '';
var delimiter = ',';
const filterName = chkYearFilter.getAttribute('data-filter');
let filters = query.Years || '';
const delimiter = ',';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkYearFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.Years = filters;
triggerChange(self);
triggerChange(this);
return;
}
var chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter');
if (chkOfficialRatingFilter) {
var filterName = chkOfficialRatingFilter.getAttribute('data-filter');
var filters = query.OfficialRatings || '';
var delimiter = '|';
const filterName = chkOfficialRatingFilter.getAttribute('data-filter');
let filters = query.OfficialRatings || '';
const delimiter = '|';
filters = (delimiter + filters).replace(delimiter + filterName, '').substring(1);
if (chkOfficialRatingFilter.checked) {
filters = filters ? (filters + delimiter + filterName) : filterName;
}
query.StartIndex = 0;
query.OfficialRatings = filters;
triggerChange(self);
return;
triggerChange(this);
}
});
}
var self = this;
self.show = function () {
return new Promise(function (resolve, reject) {
require(['text!./filterdialog.template.html'], function (template) {
var dlg = dialogHelper.createDialog({
show() {
return import('text!./filterdialog.template.html').then(({default: template}) => {
return new Promise((resolve) => {
const dlg = dialogHelper.createDialog({
removeOnClose: true,
modal: false
});
@ -379,19 +412,22 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow
dlg.classList.add('background-theme-a');
dlg.classList.add('formDialog');
dlg.classList.add('filterDialog');
dlg.innerHTML = globalize.translateDocument(template);
setVisibility(dlg, options);
dlg.innerHTML = globalize.translateHtml(template);
setVisibility(dlg, this.options);
dialogHelper.open(dlg);
dlg.addEventListener('close', resolve);
updateFilterControls(dlg, options);
bindEvents(dlg);
if (enableDynamicFilters(options.mode)) {
updateFilterControls(dlg, this.options);
this.bindEvents(dlg);
if (enableDynamicFilters(this.options.mode)) {
dlg.classList.add('dynamicFilterDialog');
var apiClient = connectionManager.getApiClient(options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), options.query);
const apiClient = connectionManager.getApiClient(this.options.serverId);
loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query);
}
});
});
};
};
});
}
}
/* eslint-enable indent */
export default FilterDialog;

View file

@ -1,6 +1,6 @@
<div style="margin: 0;padding:1.5em 2em;" class="filterDialogContent">
<div is="emby-collapse" title="${HeaderFilters}">
<div is="emby-collapse" title="${Filters}">
<div class="collapseContent">
<div class="checkboxList">
<label>
@ -63,7 +63,7 @@
</div>
</div>
<div is="emby-collapse" title="${HeaderFeatures}" class="features hide">
<div is="emby-collapse" title="${Features}" class="features hide">
<div class="collapseContent">
<div class="checkboxList">
<label>
@ -90,7 +90,7 @@
</div>
</div>
<div is="emby-collapse" title="${HeaderGenres}" class="genreFilters hide">
<div is="emby-collapse" title="${Genres}" class="genreFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>
@ -100,7 +100,7 @@
</div>
</div>
<div is="emby-collapse" title="${HeaderTags}" class="tagFilters hide">
<div is="emby-collapse" title="${Tags}" class="tagFilters hide">
<div class="collapseContent filterOptions">
</div>
</div>

View file

@ -1,271 +1,220 @@
define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost', 'inputManager', 'layoutManager', 'connectionManager', 'appRouter', 'globalize', 'userSettings', 'emby-checkbox', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button', 'flexStyles'], function (require, dom, focusManager, dialogHelper, loading, appHost, inputManager, layoutManager, connectionManager, appRouter, globalize, userSettings) {
'use strict';
import dom from 'dom';
import focusManager from 'focusManager';
import dialogHelper from 'dialogHelper';
import inputManager from 'inputManager';
import layoutManager from 'layoutManager';
import connectionManager from 'connectionManager';
import globalize from 'globalize';
import * as userSettings from 'userSettings';
import 'emby-checkbox';
import 'emby-input';
import 'paper-icon-button-light';
import 'emby-select';
import 'material-icons';
import 'css!./../formdialog';
import 'emby-button';
import 'flexStyles';
function onSubmit(e) {
function onSubmit(e) {
e.preventDefault();
return false;
}
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var elem = context.querySelector(selector);
e.preventDefault();
return false;
if (items.length) {
elem.classList.remove('hide');
} else {
elem.classList.add('hide');
}
function renderOptions(context, selector, cssClass, items, isCheckedFn) {
var html = '';
var elem = context.querySelector(selector);
html += items.map(function (filter) {
var itemHtml = '';
if (items.length) {
var checkedHtml = isCheckedFn(filter) ? ' checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + filter.Name + '</span>';
itemHtml += '</label>';
elem.classList.remove('hide');
return itemHtml;
}).join('');
elem.querySelector('.filterOptions').innerHTML = html;
}
function renderDynamicFilters(context, result, options) {
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
// Switching from | to ,
var delimeter = (options.settings.GenreIds || '').indexOf('|') === -1 ? ',' : '|';
return (delimeter + (options.settings.GenreIds || '') + delimeter).indexOf(delimeter + i.Id + delimeter) !== -1;
});
}
function setBasicFilter(context, key, elem) {
var value = elem.checked;
value = value ? value : null;
userSettings.setFilter(key, value);
}
function moveCheckboxFocus(elem, offset) {
var parent = dom.parentWithClass(elem, 'checkboxList-verticalwrap');
var elems = focusManager.getFocusableElements(parent);
var index = -1;
for (let i = 0, length = elems.length; i < length; i++) {
if (elems[i] === elem) {
index = i;
break;
}
}
index += offset;
index = Math.min(elems.length - 1, index);
index = Math.max(0, index);
var newElem = elems[index];
if (newElem) {
focusManager.focus(newElem);
}
}
function centerFocus(elem, horiz, on) {
import('scrollHelper').then(({ default: scrollHelper }) => {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
function onInputCommand(e) {
switch (e.detail.command) {
case 'left':
moveCheckboxFocus(e.target, -1);
e.preventDefault();
break;
case 'right':
moveCheckboxFocus(e.target, 1);
e.preventDefault();
break;
default:
break;
}
}
function saveValues(context, settings, settingsKey) {
var elems = context.querySelectorAll('.simpleFilter');
for (let i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i]);
} else {
elem.classList.add('hide');
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input'));
}
var html = '';
html += items.map(function (filter) {
var itemHtml = '';
var checkedHtml = isCheckedFn(filter) ? ' checked' : '';
itemHtml += '<label>';
itemHtml += '<input is="emby-checkbox" type="checkbox"' + checkedHtml + ' data-filter="' + filter.Id + '" class="' + cssClass + '"/>';
itemHtml += '<span>' + filter.Name + '</span>';
itemHtml += '</label>';
return itemHtml;
}).join('');
elem.querySelector('.filterOptions').innerHTML = html;
}
function renderDynamicFilters(context, result, options) {
// Video type
var videoTypes = [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
// If there's a huge number of these they will be really show to render
//if (result.Tags) {
// result.Tags.length = Math.min(result.Tags.length, 50);
//}
for (let i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
videoTypes.push(elems[i].getAttribute('data-filter'));
}
}
userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(','));
renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) {
// Series status
var seriesStatuses = [];
elems = context.querySelectorAll('.chkSeriesStatus');
// Switching from | to ,
var delimeter = (options.settings.GenreIds || '').indexOf('|') === -1 ? ',' : '|';
return (delimeter + (options.settings.GenreIds || '') + delimeter).indexOf(delimeter + i.Id + delimeter) !== -1;
});
//renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) {
// var delimeter = '|';
// return (delimeter + (query.OfficialRatings || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
//renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) {
// var delimeter = '|';
// return (delimeter + (query.Tags || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
//renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) {
// var delimeter = ',';
// return (delimeter + (query.Years || '') + delimeter).indexOf(delimeter + i + delimeter) != -1;
//});
for (let i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
seriesStatuses.push(elems[i].getAttribute('data-filter'));
}
}
function loadDynamicFilters(context, options) {
// Genres
var genres = [];
elems = context.querySelectorAll('.chkGenreFilter');
var apiClient = connectionManager.getApiClient(options.serverId);
var filterMenuOptions = Object.assign(options.filterMenuOptions, {
UserId: apiClient.getCurrentUserId(),
ParentId: options.parentId,
IncludeItemTypes: options.itemTypes.join(',')
});
apiClient.getFilters(filterMenuOptions).then(function (result) {
renderDynamicFilters(context, result, options);
}, function () {
// older server
});
for (let i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
genres.push(elems[i].getAttribute('data-filter'));
}
}
function initEditor(context, settings) {
context.querySelector('form').addEventListener('submit', onSubmit);
var elems = context.querySelectorAll('.simpleFilter');
var i;
var length;
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
elems[i].checked = settings[elems[i].getAttribute('data-settingname')] || false;
} else {
elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false;
}
}
var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(',') : [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = videoTypes.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(',') : [];
elems = context.querySelectorAll('.chkSeriesStatus');
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = seriesStatuses.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
if (context.querySelector('.basicFilterSection .viewSetting:not(.hide)')) {
context.querySelector('.basicFilterSection').classList.remove('hide');
userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(','));
}
function bindCheckboxInput(context, on) {
var elems = context.querySelectorAll('.checkboxList-verticalwrap');
for (let i = 0, length = elems.length; i < length; i++) {
if (on) {
inputManager.on(elems[i], onInputCommand);
} else {
context.querySelector('.basicFilterSection').classList.add('hide');
inputManager.off(elems[i], onInputCommand);
}
}
}
function initEditor(context, settings) {
context.querySelector('form').addEventListener('submit', onSubmit);
if (context.querySelector('.featureSection .viewSetting:not(.hide)')) {
context.querySelector('.featureSection').classList.remove('hide');
var elems = context.querySelectorAll('.simpleFilter');
var i;
var length;
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
elems[i].checked = settings[elems[i].getAttribute('data-settingname')] || false;
} else {
context.querySelector('.featureSection').classList.add('hide');
elems[i].querySelector('input').checked = settings[elems[i].getAttribute('data-settingname')] || false;
}
}
function saveValues(context, settings, settingsKey) {
var videoTypes = settings.VideoTypes ? settings.VideoTypes.split(',') : [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
var elems = context.querySelectorAll('.simpleFilter');
var i;
var length;
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].tagName === 'INPUT') {
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i]);
} else {
setBasicFilter(context, settingsKey + '-filter-' + elems[i].getAttribute('data-settingname'), elems[i].querySelector('input'));
}
}
// Video type
var videoTypes = [];
elems = context.querySelectorAll('.chkVideoTypeFilter');
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
videoTypes.push(elems[i].getAttribute('data-filter'));
}
}
userSettings.setFilter(settingsKey + '-filter-VideoTypes', videoTypes.join(','));
// Series status
var seriesStatuses = [];
elems = context.querySelectorAll('.chkSeriesStatus');
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
seriesStatuses.push(elems[i].getAttribute('data-filter'));
}
}
// Genres
var genres = [];
elems = context.querySelectorAll('.chkGenreFilter');
for (i = 0, length = elems.length; i < length; i++) {
if (elems[i].checked) {
genres.push(elems[i].getAttribute('data-filter'));
}
}
userSettings.setFilter(settingsKey + '-filter-GenreIds', genres.join(','));
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = videoTypes.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
function setBasicFilter(context, key, elem) {
var seriesStatuses = settings.SeriesStatus ? settings.SeriesStatus.split(',') : [];
elems = context.querySelectorAll('.chkSeriesStatus');
var value = elem.checked;
value = value ? value : null;
userSettings.setFilter(key, value);
for (i = 0, length = elems.length; i < length; i++) {
elems[i].checked = seriesStatuses.indexOf(elems[i].getAttribute('data-filter')) !== -1;
}
function centerFocus(elem, horiz, on) {
require(['scrollHelper'], function (scrollHelper) {
var fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
if (context.querySelector('.basicFilterSection .viewSetting:not(.hide)')) {
context.querySelector('.basicFilterSection').classList.remove('hide');
} else {
context.querySelector('.basicFilterSection').classList.add('hide');
}
function moveCheckboxFocus(elem, offset) {
var parent = dom.parentWithClass(elem, 'checkboxList-verticalwrap');
var elems = focusManager.getFocusableElements(parent);
var index = -1;
for (var i = 0, length = elems.length; i < length; i++) {
if (elems[i] === elem) {
index = i;
break;
}
}
index += offset;
index = Math.min(elems.length - 1, index);
index = Math.max(0, index);
var newElem = elems[index];
if (newElem) {
focusManager.focus(newElem);
}
if (context.querySelector('.featureSection .viewSetting:not(.hide)')) {
context.querySelector('.featureSection').classList.remove('hide');
} else {
context.querySelector('.featureSection').classList.add('hide');
}
}
function loadDynamicFilters(context, options) {
var apiClient = connectionManager.getApiClient(options.serverId);
function onInputCommand(e) {
switch (e.detail.command) {
var filterMenuOptions = Object.assign(options.filterMenuOptions, {
case 'left':
moveCheckboxFocus(e.target, -1);
e.preventDefault();
break;
case 'right':
moveCheckboxFocus(e.target, 1);
e.preventDefault();
break;
default:
break;
}
}
function FilterMenu() {
}
function bindCheckboxInput(context, on) {
var elems = context.querySelectorAll('.checkboxList-verticalwrap');
for (var i = 0, length = elems.length; i < length; i++) {
if (on) {
inputManager.on(elems[i], onInputCommand);
} else {
inputManager.off(elems[i], onInputCommand);
}
}
}
FilterMenu.prototype.show = function (options) {
return new Promise(function (resolve, reject) {
require(['text!./filtermenu.template.html'], function (template) {
UserId: apiClient.getCurrentUserId(),
ParentId: options.parentId,
IncludeItemTypes: options.itemTypes.join(',')
});
apiClient.getFilters(filterMenuOptions).then((result) => {
renderDynamicFilters(context, result, options);
});
}
class FilterMenu {
show(options) {
return new Promise( (resolve, reject) => {
import('text!./filtermenu.template.html').then(({ default: template }) => {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
@ -286,10 +235,10 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost',
html += template;
dlg.innerHTML = globalize.translateDocument(html, 'core');
dlg.innerHTML = globalize.translateHtml(html, 'core');
var settingElements = dlg.querySelectorAll('.viewSetting');
for (var i = 0, length = settingElements.length; i < length; i++) {
for (let i = 0, length = settingElements.length; i < length; i++) {
if (options.visibleSettings.indexOf(settingElements[i].getAttribute('data-settingname')) === -1) {
settingElements[i].classList.add('hide');
} else {
@ -301,9 +250,7 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost',
loadDynamicFilters(dlg, options);
bindCheckboxInput(dlg, true);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
@ -314,17 +261,10 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost',
var submitted;
dlg.querySelector('form').addEventListener('change', function () {
submitted = true;
//if (options.onChange) {
// saveValues(dlg, options.settings, options.settingsKey);
// options.onChange();
//}
}, true);
dialogHelper.open(dlg).then(function () {
dialogHelper.open(dlg).then( function() {
bindCheckboxInput(dlg, false);
if (layoutManager.tv) {
@ -332,19 +272,16 @@ define(['require', 'dom', 'focusManager', 'dialogHelper', 'loading', 'apphost',
}
if (submitted) {
//if (!options.onChange) {
saveValues(dlg, options.settings, options.settingsKey);
resolve();
return resolve();
//}
return;
}
reject();
return resolve();
});
});
});
};
}
}
return FilterMenu;
});
export default FilterMenu;

View file

@ -1,5 +1,7 @@
define(['dom', 'scrollManager'], function (dom, scrollManager) {
'use strict';
/* eslint-disable indent */
import dom from 'dom';
import scrollManager from 'scrollManager';
var scopes = [];
function pushScope(elem) {
@ -7,14 +9,12 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function popScope(elem) {
if (scopes.length) {
scopes.length -= 1;
}
}
function autoFocus(view, defaultToFirst, findAutoFocusElement) {
var element;
if (findAutoFocusElement !== false) {
element = view.querySelector('*[autofocus]');
@ -37,7 +37,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function focus(element) {
try {
element.focus({
preventScroll: scrollManager.isEnabled()
@ -50,16 +49,13 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A'];
var focusableContainerTagNames = ['BODY', 'DIALOG'];
var focusableQuery = focusableTagNames.map(function (t) {
if (t === 'INPUT') {
t += ':not([type="range"]):not([type="file"])';
}
return t + ':not([tabindex="-1"]):not(:disabled)';
}).join(',') + ',.focusable';
function isFocusable(elem) {
if (focusableTagNames.indexOf(elem.tagName) !== -1) {
return true;
}
@ -83,7 +79,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function focusableParent(elem) {
var originalElement = elem;
while (!isFocusable(elem)) {
@ -101,7 +96,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusableInternal(elem) {
// http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
if (elem.offsetParent === null) {
return false;
@ -112,7 +106,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
// Determines if a focusable element can be focused at a given point in time
function isCurrentlyFocusable(elem) {
if (elem.disabled) {
return false;
}
@ -143,7 +136,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var focusableElements = [];
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (excludeClass && elem.classList.contains(excludeClass)) {
@ -163,7 +155,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function isFocusContainer(elem, direction) {
if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) {
return true;
}
@ -217,7 +208,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function getOffset(elem) {
var box;
// Support: BlackBerry 5, iOS 3 (original iPhone)
@ -234,7 +224,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
if (box.right === null) {
// Create a new object because some browsers will throw an error when trying to set data onto the Rect object
var newBox = {
top: box.top,
@ -253,7 +242,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function nav(activeElement, direction, container, focusableElements) {
activeElement = activeElement || document.activeElement;
if (activeElement) {
@ -272,14 +260,10 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var rect = getOffset(activeElement);
// Get elements and work out x/y points
var cache = [];
var point1x = parseFloat(rect.left) || 0;
var point1y = parseFloat(rect.top) || 0;
var point2x = parseFloat(point1x + rect.width - 1) || point1x;
var point2y = parseFloat(point1y + rect.height - 1) || point1y;
// Shortcuts to help with compression
var min = Math.min;
var max = Math.max;
var sourceMidX = rect.left + (rect.width / 2);
var sourceMidY = rect.top + (rect.height / 2);
@ -301,10 +285,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
continue;
}
//if (!isCurrentlyFocusableInternal(curr)) {
// continue;
//}
var elementRect = getOffset(curr);
// not currently visible
@ -313,7 +293,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
switch (direction) {
case 0:
// left
if (elementRect.left >= rect.left) {
@ -369,7 +348,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var distY;
switch (direction) {
case 0:
// left
distX = Math.abs(point1x - Math.min(point1x, x2));
@ -403,7 +381,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
if (nearestElement) {
// See if there's a focusable container, and if so, send the focus command to that
if (activeElement) {
var nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable');
@ -418,12 +395,10 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function intersectsInternal(a1, a2, b1, b2) {
return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2);
}
function intersects(a1, a2, b1, b2) {
return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2);
}
@ -434,11 +409,9 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function focusFirst(container, focusableSelector) {
var elems = container.querySelectorAll(focusableSelector);
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
@ -449,11 +422,9 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function focusLast(container, focusableSelector) {
var elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse();
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
@ -464,7 +435,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
function moveFocus(sourceElement, container, focusableSelector, offset) {
var elems = container.querySelectorAll(focusableSelector);
var list = [];
var i;
@ -472,7 +442,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var elem;
for (i = 0, length = elems.length; i < length; i++) {
elem = elems[i];
if (isCurrentlyFocusableInternal(elem)) {
@ -483,7 +452,6 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
var currentIndex = -1;
for (i = 0, length = list.length; i < length; i++) {
elem = list[i];
if (sourceElement === elem || elem.contains(sourceElement)) {
@ -506,37 +474,38 @@ define(['dom', 'scrollManager'], function (dom, scrollManager) {
}
}
return {
autoFocus: autoFocus,
focus: focus,
focusableParent: focusableParent,
getFocusableElements: getFocusableElements,
moveLeft: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 0, container, focusableElements);
},
moveRight: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 1, container, focusableElements);
},
moveUp: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 2, container, focusableElements);
},
moveDown: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 3, container, focusableElements);
},
sendText: sendText,
isCurrentlyFocusable: isCurrentlyFocusable,
pushScope: pushScope,
popScope: popScope,
focusFirst: focusFirst,
focusLast: focusLast,
moveFocus: moveFocus
};
});
/* eslint-enable indent */
export default {
autoFocus: autoFocus,
focus: focus,
focusableParent: focusableParent,
getFocusableElements: getFocusableElements,
moveLeft: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 0, container, focusableElements);
},
moveRight: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 1, container, focusableElements);
},
moveUp: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 2, container, focusableElements);
},
moveDown: function (sourceElement, options) {
var container = options ? options.container : null;
var focusableElements = options ? options.focusableElements : null;
nav(sourceElement, 3, container, focusableElements);
},
sendText: sendText,
isCurrentlyFocusable: isCurrentlyFocusable,
pushScope: pushScope,
popScope: popScope,
focusFirst: focusFirst,
focusLast: focusLast,
moveFocus: moveFocus
};

View file

@ -55,7 +55,7 @@
/* Without this emby-checkbox is able to appear on top */
z-index: 1;
align-items: flex-end;
justify-content: flex-end;
justify-content: center;
flex-wrap: wrap;
}

View file

@ -1,5 +1,8 @@
define(['dom', 'appRouter', 'connectionManager'], function (dom, appRouter, connectionManager) {
'use strict';
/* eslint-disable indent */
import dom from 'dom';
import appRouter from 'appRouter';
import connectionManager from 'connectionManager';
function onGroupedCardClick(e, card) {
var itemId = card.getAttribute('data-id');
@ -18,11 +21,11 @@ define(['dom', 'appRouter', 'connectionManager'], function (dom, appRouter, conn
if (!actionableParent || actionableParent.classList.contains('cardContent')) {
apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) {
if (1 === items.length) {
if (items.length === 1) {
return void appRouter.showItem(items[0]);
}
var url = 'itemdetails.html?id=' + itemId + '&serverId=' + serverId;
var url = 'details?id=' + itemId + '&serverId=' + serverId;
Dashboard.navigate(url);
});
e.stopPropagation();
@ -31,7 +34,7 @@ define(['dom', 'appRouter', 'connectionManager'], function (dom, appRouter, conn
}
}
function onItemsContainerClick(e) {
export default function onItemsContainerClick(e) {
var groupedCard = dom.parentWithClass(e.target, 'groupedCard');
if (groupedCard) {
@ -39,7 +42,4 @@ define(['dom', 'appRouter', 'connectionManager'], function (dom, appRouter, conn
}
}
return {
onItemsContainerClick: onItemsContainerClick
};
});
/* eslint-enable indent */

View file

@ -1,160 +1,149 @@
define(['dialogHelper', 'globalize', 'userSettings', 'layoutManager', 'connectionManager', 'require', 'loading', 'scrollHelper', 'emby-checkbox', 'emby-radio', 'css!./../formdialog', 'material-icons'], function (dialogHelper, globalize, userSettings, layoutManager, connectionManager, require, loading, scrollHelper) {
'use strict';
import dialogHelper from 'dialogHelper';
import globalize from 'globalize';
import * as userSettings from 'userSettings';
import layoutManager from 'layoutManager';
import scrollHelper from 'scrollHelper';
import 'emby-checkbox';
import 'emby-radio';
import 'css!./../formdialog';
import 'material-icons';
function saveCategories(context, options) {
function saveCategories(context, options) {
const categories = [];
var categories = [];
const chkCategorys = context.querySelectorAll('.chkCategory');
for (const chkCategory of chkCategorys) {
const type = chkCategory.getAttribute('data-type');
var chkCategorys = context.querySelectorAll('.chkCategory');
for (var i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute('data-type');
if (chkCategorys[i].checked) {
categories.push(type);
}
}
if (categories.length >= 4) {
categories.push('series');
}
// differentiate between none and all
categories.push('all');
options.categories = categories;
}
function loadCategories(context, options) {
var selectedCategories = options.categories || [];
var chkCategorys = context.querySelectorAll('.chkCategory');
for (var i = 0, length = chkCategorys.length; i < length; i++) {
var type = chkCategorys[i].getAttribute('data-type');
chkCategorys[i].checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1;
if (chkCategory.checked) {
categories.push(type);
}
}
function save(context) {
if (categories.length >= 4) {
categories.push('series');
}
var i;
var length;
// differentiate between none and all
categories.push('all');
options.categories = categories;
}
var chkIndicators = context.querySelectorAll('.chkIndicator');
for (i = 0, length = chkIndicators.length; i < length; i++) {
function loadCategories(context, options) {
const selectedCategories = options.categories || [];
var type = chkIndicators[i].getAttribute('data-type');
userSettings.set('guide-indicator-' + type, chkIndicators[i].checked);
const chkCategorys = context.querySelectorAll('.chkCategory');
for (const chkCategory of chkCategorys) {
const type = chkCategory.getAttribute('data-type');
chkCategory.checked = !selectedCategories.length || selectedCategories.indexOf(type) !== -1;
}
}
function save(context) {
const chkIndicators = context.querySelectorAll('.chkIndicator');
for (const chkIndicator of chkIndicators) {
const type = chkIndicator.getAttribute('data-type');
userSettings.set('guide-indicator-' + type, chkIndicator.checked);
}
userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked);
userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked);
const sortBys = context.querySelectorAll('.chkSortOrder');
for (const sortBy of sortBys) {
if (sortBy.checked) {
userSettings.set('livetv-channelorder', sortBy.value);
break;
}
}
}
userSettings.set('guide-colorcodedbackgrounds', context.querySelector('.chkColorCodedBackgrounds').checked);
userSettings.set('livetv-favoritechannelsattop', context.querySelector('.chkFavoriteChannelsAtTop').checked);
function load(context) {
const chkIndicators = context.querySelectorAll('.chkIndicator');
var sortBys = context.querySelectorAll('.chkSortOrder');
for (i = 0, length = sortBys.length; i < length; i++) {
if (sortBys[i].checked) {
userSettings.set('livetv-channelorder', sortBys[i].value);
break;
}
for (const chkIndicator of chkIndicators) {
const type = chkIndicator.getAttribute('data-type');
if (chkIndicator.getAttribute('data-default') === 'true') {
chkIndicator.checked = userSettings.get('guide-indicator-' + type) !== 'false';
} else {
chkIndicator.checked = userSettings.get('guide-indicator-' + type) === 'true';
}
}
function load(context) {
context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true';
context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false';
var i;
var length;
const sortByValue = userSettings.get('livetv-channelorder') || 'Number';
var chkIndicators = context.querySelectorAll('.chkIndicator');
for (i = 0, length = chkIndicators.length; i < length; i++) {
const sortBys = context.querySelectorAll('.chkSortOrder');
for (const sortBy of sortBys) {
sortBy.checked = sortBy.value === sortByValue;
}
}
var type = chkIndicators[i].getAttribute('data-type');
function showEditor(options) {
return new Promise(function (resolve, reject) {
let settingsChanged = false;
if (chkIndicators[i].getAttribute('data-default') === 'true') {
chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) !== 'false';
import('text!./guide-settings.template.html').then(({ default: template }) => {
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
chkIndicators[i].checked = userSettings.get('guide-indicator-' + type) === 'true';
dialogOptions.size = 'small';
}
}
context.querySelector('.chkColorCodedBackgrounds').checked = userSettings.get('guide-colorcodedbackgrounds') === 'true';
context.querySelector('.chkFavoriteChannelsAtTop').checked = userSettings.get('livetv-favoritechannelsattop') !== 'false';
const dlg = dialogHelper.createDialog(dialogOptions);
var sortByValue = userSettings.get('livetv-channelorder') || 'Number';
dlg.classList.add('formDialog');
var sortBys = context.querySelectorAll('.chkSortOrder');
for (i = 0, length = sortBys.length; i < length; i++) {
sortBys[i].checked = sortBys[i].value === sortByValue;
}
}
let html = '';
function showEditor(options) {
html += globalize.translateHtml(template, 'core');
return new Promise(function (resolve, reject) {
dlg.innerHTML = html;
var settingsChanged = false;
require(['text!./guide-settings.template.html'], function (template) {
var dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
var html = '';
html += globalize.translateDocument(template, 'core');
dlg.innerHTML = html;
dlg.addEventListener('change', function () {
settingsChanged = true;
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
save(dlg);
saveCategories(dlg, options);
if (settingsChanged) {
resolve();
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
}
load(dlg);
loadCategories(dlg, options);
dialogHelper.open(dlg);
dlg.addEventListener('change', function () {
settingsChanged = true;
});
});
}
return {
show: showEditor
};
});
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false);
}
save(dlg);
saveCategories(dlg, options);
if (settingsChanged) {
resolve();
} else {
reject();
}
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false);
}
load(dlg);
loadCategories(dlg, options);
dialogHelper.open(dlg);
});
});
}
export default {
show: showEditor
};

View file

@ -1,5 +1,5 @@
<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>
</button>
<h3 class="formDialogHeaderTitle">

View file

@ -70,6 +70,10 @@
contain: strict;
}
.programContainer.emby-scroller {
margin: 0;
}
.channelPrograms {
height: 4.42em;
contain: strict;

File diff suppressed because it is too large Load diff

View file

@ -29,10 +29,10 @@
</div>
<div class="guideOptions hide">
<button is="paper-icon-button-light" type="button" class="btnPreviousPage" title="${LabelPrevious}">
<button is="paper-icon-button-light" type="button" class="btnPreviousPage" title="${Previous}">
<span class="material-icons arrow_back" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" type="button" class="btnNextPage" title="${LabelNext}">
<button is="paper-icon-button-light" type="button" class="btnNextPage" title="${Next}">
<span class="material-icons arrow_forward" aria-hidden="true"></span>
</button>
</div>

View file

@ -1,30 +1,38 @@
define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loading', 'connectionManager', 'homeSections', 'dom', 'events', 'listViewStyle', 'emby-select', 'emby-checkbox'], function (require, appHost, layoutManager, focusManager, globalize, loading, connectionManager, homeSections, dom, events) {
'use strict';
import layoutManager from 'layoutManager';
import focusManager from 'focusManager';
import globalize from 'globalize';
import loading from 'loading';
import connectionManager from 'connectionManager';
import homeSections from 'homeSections';
import dom from 'dom';
import events from 'events';
import 'listViewStyle';
import 'emby-select';
import 'emby-checkbox';
var numConfigurableSections = 7;
/* eslint-disable indent */
const numConfigurableSections = 7;
function renderViews(page, user, result) {
var folderHtml = '';
let folderHtml = '';
folderHtml += '<div class="checkboxList">';
folderHtml += result.map(function (i) {
folderHtml += result.map(i => {
let currentHtml = '';
var currentHtml = '';
const id = `chkGroupFolder${i.Id}`;
var id = 'chkGroupFolder' + i.Id;
const isChecked = user.Configuration.GroupedFolders.includes(i.Id);
var isChecked = user.Configuration.GroupedFolders.indexOf(i.Id) !== -1;
var checkedHtml = isChecked ? ' checked="checked"' : '';
const checkedHtml = isChecked ? ' checked="checked"' : '';
currentHtml += '<label>';
currentHtml += '<input type="checkbox" is="emby-checkbox" class="chkGroupFolder" data-folderid="' + i.Id + '" id="' + id + '"' + checkedHtml + '/>';
currentHtml += '<span>' + i.Name + '</span>';
currentHtml += `<input type="checkbox" is="emby-checkbox" class="chkGroupFolder" data-folderid="${i.Id}" id="${id}"${checkedHtml}/>`;
currentHtml += `<span>${i.Name}</span>`;
currentHtml += '</label>';
return currentHtml;
}).join('');
folderHtml += '</div>';
@ -33,8 +41,7 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function getLandingScreenOptions(type) {
var list = [];
const list = [];
if (type === 'movies') {
list.push({
@ -122,28 +129,22 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function getLandingScreenOptionsHtml(type, userValue) {
return getLandingScreenOptions(type).map(o => {
const selected = userValue === o.value || (o.isDefault && !userValue);
const selectedHtml = selected ? ' selected' : '';
const optionValue = o.isDefault ? '' : o.value;
return getLandingScreenOptions(type).map(function (o) {
var selected = userValue === o.value || (o.isDefault && !userValue);
var selectedHtml = selected ? ' selected' : '';
var optionValue = o.isDefault ? '' : o.value;
return '<option value="' + optionValue + '"' + selectedHtml + '>' + o.name + '</option>';
return `<option value="${optionValue}"${selectedHtml}>${o.name}</option>`;
}).join('');
}
function renderViewOrder(context, user, result) {
let html = '';
var html = '';
html += result.Items.map((view) => {
let currentHtml = '';
var index = 0;
html += result.Items.map(function (view) {
var currentHtml = '';
currentHtml += '<div class="listItem viewItem" data-viewid="' + view.Id + '">';
currentHtml += `<div class="listItem viewItem" data-viewid="${view.Id}">`;
currentHtml += '<span class="material-icons listItemIcon folder_open"></span>';
@ -155,29 +156,25 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
currentHtml += '</div>';
currentHtml += '<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="' + globalize.translate('Up') + '"><span class="material-icons keyboard_arrow_up"></span></button>';
currentHtml += '<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="' + globalize.translate('Down') + '"><span class="material-icons keyboard_arrow_down"></span></button>';
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemUp btnViewItemMove autoSize" title="${globalize.translate('Up')}"><span class="material-icons keyboard_arrow_up"></span></button>`;
currentHtml += `<button type="button" is="paper-icon-button-light" class="btnViewItemDown btnViewItemMove autoSize" title="${globalize.translate('Down')}"><span class="material-icons keyboard_arrow_down"></span></button>`;
currentHtml += '</div>';
index++;
return currentHtml;
}).join('');
context.querySelector('.viewOrderList').innerHTML = html;
}
function updateHomeSectionValues(context, userSettings) {
for (let i = 1; i <= 7; i++) {
const select = context.querySelector(`#selectHomeSection${i}`);
const defaultValue = homeSections.getDefaultSection(i - 1);
for (var i = 1; i <= 7; i++) {
const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]');
var select = context.querySelector('#selectHomeSection' + i);
var defaultValue = homeSections.getDefaultSection(i - 1);
var option = select.querySelector('option[value=' + defaultValue + ']') || select.querySelector('option[value=""]');
var userValue = userSettings.get('homesection' + (i - 1));
const userValue = userSettings.get(`homesection${i - 1}`);
option.value = '';
@ -192,43 +189,39 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function getPerLibrarySettingsHtml(item, user, userSettings, apiClient) {
let html = '';
var html = '';
var isChecked;
let isChecked;
if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') {
isChecked = (user.Configuration.MyMediaExcludes || []).indexOf(item.Id) === -1;
isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id);
html += '<div>';
html += '<label>';
html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : '') + '/>';
html += '<span>' + globalize.translate('DisplayInMyMedia') + '</span>';
html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInMyMedia" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInMyMedia')}</span>`;
html += '</label>';
html += '</div>';
}
var excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels'];
if (excludeFromLatest.indexOf(item.CollectionType || '') === -1) {
isChecked = user.Configuration.LatestItemsExcludes.indexOf(item.Id) === -1;
const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels'];
if (!excludeFromLatest.includes(item.CollectionType || '')) {
isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id);
html += '<label class="fldIncludeInLatest">';
html += '<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="' + item.Id + '"' + (isChecked ? ' checked="checked"' : '') + '/>';
html += '<span>' + globalize.translate('DisplayInOtherHomeScreenSections') + '</span>';
html += `<input type="checkbox" is="emby-checkbox" class="chkIncludeInLatest" data-folderid="${item.Id}"${isChecked ? ' checked="checked"' : ''}/>`;
html += `<span>${globalize.translate('DisplayInOtherHomeScreenSections')}</span>`;
html += '</label>';
}
if (html) {
html = '<div class="checkboxListContainer">' + html + '</div>';
html = `<div class="checkboxListContainer">${html}</div>`;
}
if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') {
var idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id;
const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id;
html += '<div class="selectContainer">';
html += '<select is="emby-select" class="selectLanding" data-folderid="' + idForLanding + '" label="' + globalize.translate('LabelDefaultScreen') + '">';
html += `<select is="emby-select" class="selectLanding" data-folderid="${idForLanding}" label="${globalize.translate('LabelDefaultScreen')}">`;
var userValue = userSettings.get('landing-' + idForLanding);
const userValue = userSettings.get(`landing-${idForLanding}`);
html += getLandingScreenOptionsHtml(item.CollectionType, userValue);
@ -237,8 +230,7 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
if (html) {
var prefix = '';
let prefix = '';
prefix += '<div class="verticalSection">';
prefix += '<h2 class="sectionTitle">';
@ -253,12 +245,10 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function renderPerLibrarySettings(context, user, userViews, userSettings, apiClient) {
const elem = context.querySelector('.perLibrarySettings');
let html = '';
var elem = context.querySelector('.perLibrarySettings');
var html = '';
for (var i = 0, length = userViews.length; i < length; i++) {
for (let i = 0, length = userViews.length; i < length; i++) {
html += getPerLibrarySettingsHtml(userViews[i], user, userSettings, apiClient);
}
@ -266,16 +256,14 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function loadForm(context, user, userSettings, apiClient) {
context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false;
updateHomeSectionValues(context, userSettings);
var promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id);
var promise2 = apiClient.getJSON(apiClient.getUrl('Users/' + user.Id + '/GroupingOptions'));
Promise.all([promise1, promise2]).then(function (responses) {
const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id);
const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`));
Promise.all([promise1, promise2]).then(responses => {
renderViewOrder(context, user, responses[0]);
renderPerLibrarySettings(context, user, responses[0].Items, userSettings, apiClient);
@ -286,48 +274,23 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
});
}
function getSibling(elem, type, className) {
var sibling = elem[type];
while (sibling != null) {
if (sibling.classList.contains(className)) {
break;
}
}
if (sibling != null) {
if (!sibling.classList.contains(className)) {
sibling = null;
}
}
return sibling;
}
function onSectionOrderListClick(e) {
var target = dom.parentWithClass(e.target, 'btnViewItemMove');
const target = dom.parentWithClass(e.target, 'btnViewItemMove');
if (target) {
var viewItem = dom.parentWithClass(target, 'viewItem');
const viewItem = dom.parentWithClass(target, 'viewItem');
if (viewItem) {
var ul = dom.parentWithClass(viewItem, 'paperList');
if (target.classList.contains('btnViewItemDown')) {
var next = viewItem.nextSibling;
const next = viewItem.nextSibling;
if (next) {
viewItem.parentNode.removeChild(viewItem);
next.parentNode.insertBefore(viewItem, next.nextSibling);
focusManager.focus(e.target);
}
} else {
var prev = viewItem.previousSibling;
const prev = viewItem.previousSibling;
if (prev) {
viewItem.parentNode.removeChild(viewItem);
@ -340,44 +303,37 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function getCheckboxItems(selector, context, isChecked) {
const inputs = context.querySelectorAll(selector);
const list = [];
var inputs = context.querySelectorAll(selector);
var list = [];
for (var i = 0, length = inputs.length; i < length; i++) {
for (let i = 0, length = inputs.length; i < length; i++) {
if (inputs[i].checked === isChecked) {
list.push(inputs[i]);
}
}
return list;
}
function saveUser(context, user, userSettingsInstance, apiClient) {
user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked;
user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(function (i) {
user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => {
return i.getAttribute('data-folderid');
});
user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(function (i) {
user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => {
return i.getAttribute('data-folderid');
});
user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(function (i) {
user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => {
return i.getAttribute('data-folderid');
});
var viewItems = context.querySelectorAll('.viewItem');
var orderedViews = [];
var i;
var length;
const viewItems = context.querySelectorAll('.viewItem');
const orderedViews = [];
let i;
let length;
for (i = 0, length = viewItems.length; i < length; i++) {
orderedViews.push(viewItems[i].getAttribute('data-viewid'));
}
@ -394,48 +350,42 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
userSettingsInstance.set('homesection5', context.querySelector('#selectHomeSection6').value);
userSettingsInstance.set('homesection6', context.querySelector('#selectHomeSection7').value);
var selectLandings = context.querySelectorAll('.selectLanding');
const selectLandings = context.querySelectorAll('.selectLanding');
for (i = 0, length = selectLandings.length; i < length; i++) {
var selectLanding = selectLandings[i];
userSettingsInstance.set('landing-' + selectLanding.getAttribute('data-folderid'), selectLanding.value);
const selectLanding = selectLandings[i];
userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value);
}
return apiClient.updateUserConfiguration(user.Id, user.Configuration);
}
function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) {
loading.show();
apiClient.getUser(userId).then(function (user) {
saveUser(context, user, userSettings, apiClient).then(function () {
apiClient.getUser(userId).then(user => {
saveUser(context, user, userSettings, apiClient).then(() => {
loading.hide();
if (enableSaveConfirmation) {
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('SettingsSaved'));
});
}
events.trigger(instance, 'saved');
}, function () {
}, () => {
loading.hide();
});
});
}
function onSubmit(e) {
const self = this;
const apiClient = connectionManager.getApiClient(self.options.serverId);
const userId = self.options.userId;
const userSettings = self.options.userSettings;
var self = this;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userId = self.options.userId;
var userSettings = self.options.userSettings;
userSettings.setUserInfo(userId, apiClient).then(function () {
var enableSaveConfirmation = self.options.enableSaveConfirmation;
userSettings.setUserInfo(userId, apiClient).then(() => {
const enableSaveConfirmation = self.options.enableSaveConfirmation;
save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation);
});
@ -447,14 +397,13 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function onChange(e) {
var chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia');
const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia');
if (!chkIncludeInMyMedia) {
return;
}
var section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection');
var fldIncludeInLatest = section.querySelector('.fldIncludeInLatest');
const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection');
const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest');
if (fldIncludeInLatest) {
if (chkIncludeInMyMedia.checked) {
fldIncludeInLatest.classList.remove('hide');
@ -465,14 +414,12 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
}
function embed(options, self) {
require(['text!./homeScreenSettings.template.html'], function (template) {
for (var i = 1; i <= numConfigurableSections; i++) {
template = template.replace('{section' + i + 'label}', globalize.translate('LabelHomeScreenSectionValue', i));
return import('text!./homeScreenSettings.template.html').then(({default: template}) => {
for (let i = 1; i <= numConfigurableSections; i++) {
template = template.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i));
}
options.element.innerHTML = globalize.translateDocument(template, 'core');
options.element.innerHTML = globalize.translateHtml(template, 'core');
options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick);
options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self));
@ -492,47 +439,44 @@ define(['require', 'apphost', 'layoutManager', 'focusManager', 'globalize', 'loa
});
}
function HomeScreenSettings(options) {
class HomeScreenSettings {
constructor(options) {
this.options = options;
embed(options, this);
}
this.options = options;
loadData(autoFocus) {
const self = this;
const context = self.options.element;
embed(options, this);
loading.show();
const userId = self.options.userId;
const apiClient = connectionManager.getApiClient(self.options.serverId);
const userSettings = self.options.userSettings;
apiClient.getUser(userId).then(user => {
userSettings.setUserInfo(userId, apiClient).then(() => {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
});
}
submit() {
onSubmit.call(this);
}
destroy() {
this.options = null;
}
}
HomeScreenSettings.prototype.loadData = function (autoFocus) {
/* eslint-enable indent */
var self = this;
var context = self.options.element;
loading.show();
var userId = self.options.userId;
var apiClient = connectionManager.getApiClient(self.options.serverId);
var userSettings = self.options.userSettings;
apiClient.getUser(userId).then(function (user) {
userSettings.setUserInfo(userId, apiClient).then(function () {
self.dataLoaded = true;
loadForm(context, user, userSettings, apiClient);
if (autoFocus) {
focusManager.autoFocus(context);
}
});
});
};
HomeScreenSettings.prototype.submit = function () {
onSubmit.call(this);
};
HomeScreenSettings.prototype.destroy = function () {
this.options = null;
};
return HomeScreenSettings;
});
export default HomeScreenSettings;

View file

@ -1,6 +1,6 @@
<form style="margin:0 auto;">
<div class="verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle">${HeaderHome}</h2>
<h2 class="sectionTitle">${Home}</h2>
<div class="selectContainer hide selectTVHomeScreenContainer">
<select is="emby-select" class="selectTVHomeScreen" label="${LabelTVHomeScreen}">
@ -25,7 +25,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -38,7 +38,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -51,7 +51,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -64,7 +64,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -77,7 +77,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -90,7 +90,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>
@ -103,7 +103,7 @@
<option value="resume">${HeaderContinueWatching}</option>
<option value="resumeaudio">${HeaderContinueListening}</option>
<option value="latestmedia">${HeaderLatestMedia}</option>
<option value="nextup">${HeaderNextUp}</option>
<option value="nextup">${NextUp}</option>
<option value="livetv">${LiveTV}</option>
<option value="none">${None}</option>
</select>

View file

@ -1,7 +1,20 @@
define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'layoutManager', 'imageLoader', 'globalize', 'itemShortcuts', 'itemHelper', 'appRouter', 'scripts/imagehelper', 'paper-icon-button-light', 'emby-itemscontainer', 'emby-scroller', 'emby-button', 'css!./homesections'], function (connectionManager, cardBuilder, appSettings, dom, appHost, layoutManager, imageLoader, globalize, itemShortcuts, itemHelper, appRouter, imageHelper) {
'use strict';
import connectionManager from 'connectionManager';
import cardBuilder from 'cardBuilder';
import dom from 'dom';
import layoutManager from 'layoutManager';
import imageLoader from 'imageLoader';
import globalize from 'globalize';
import appRouter from 'appRouter';
import imageHelper from 'scripts/imagehelper';
import 'paper-icon-button-light';
import 'emby-itemscontainer';
import 'emby-scroller';
import 'emby-button';
import 'css!./homesections';
function getDefaultSection(index) {
/* eslint-disable indent */
export function getDefaultSection(index) {
switch (index) {
case 0:
return 'smalllibrarytiles';
@ -23,9 +36,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getAllSectionsToShow(userSettings, sectionCount) {
var sections = [];
for (var i = 0, length = sectionCount; i < length; i++) {
var section = userSettings.get('homesection' + i) || getDefaultSection(i);
const sections = [];
for (let i = 0, length = sectionCount; i < length; i++) {
let section = userSettings.get('homesection' + i) || getDefaultSection(i);
if (section === 'folders') {
section = getDefaultSection(0);
}
@ -36,22 +49,22 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
return sections;
}
function loadSections(elem, apiClient, user, userSettings) {
export function loadSections(elem, apiClient, user, userSettings) {
return getUserViews(apiClient, user.Id).then(function (userViews) {
var html = '';
let html = '';
if (userViews.length) {
var sectionCount = 7;
for (var i = 0; i < sectionCount; i++) {
const sectionCount = 7;
for (let i = 0; i < sectionCount; i++) {
html += '<div class="verticalSection section' + i + '"></div>';
}
elem.innerHTML = html;
elem.classList.add('homeSectionsContainer');
var promises = [];
var sections = getAllSectionsToShow(userSettings, sectionCount);
for (var i = 0; i < sections.length; i++) {
const promises = [];
const sections = getAllSectionsToShow(userSettings, sectionCount);
for (let i = 0; i < sections.length; i++) {
promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i));
}
@ -62,7 +75,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
});
});
} else {
var noLibDescription;
let noLibDescription;
if (user['Policy'] && user['Policy']['IsAdministrator']) {
noLibDescription = globalize.translate('NoCreatedLibraries', '<br><a id="button-createLibrary" class="button-link">', '</a>');
} else {
@ -75,7 +88,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
html += '</div>';
elem.innerHTML = html;
var createNowLink = elem.querySelector('#button-createLibrary');
const createNowLink = elem.querySelector('#button-createLibrary');
if (createNowLink) {
createNowLink.addEventListener('click', function () {
Dashboard.navigate('library.html');
@ -85,9 +98,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
});
}
function destroySections(elem) {
var elems = elem.querySelectorAll('.itemsContainer');
for (var i = 0; i < elems.length; i++) {
export function destroySections(elem) {
const elems = elem.querySelectorAll('.itemsContainer');
for (let i = 0; i < elems.length; i++) {
elems[i].fetchData = null;
elems[i].parentContainer = null;
elems[i].getItemsHtml = null;
@ -96,35 +109,32 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.innerHTML = '';
}
function pause(elem) {
var elems = elem.querySelectorAll('.itemsContainer');
for (var i = 0; i < elems.length; i++) {
export function pause(elem) {
const elems = elem.querySelectorAll('.itemsContainer');
for (let i = 0; i < elems.length; i++) {
elems[i].pause();
}
}
function resume(elem, options) {
var elems = elem.querySelectorAll('.itemsContainer');
var i;
var length;
var promises = [];
export function resume(elem, options) {
const elems = elem.querySelectorAll('.itemsContainer');
const promises = [];
for (i = 0, length = elems.length; i < length; i++) {
for (let i = 0, length = elems.length; i < length; i++) {
promises.push(elems[i].resume(options));
}
var promise = Promise.all(promises);
const promise = Promise.all(promises);
if (!options || options.returnPromise !== false) {
return promise;
}
}
function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) {
const section = allSections[index];
const userId = user.Id;
var section = allSections[index];
var userId = user.Id;
var elem = page.querySelector('.section' + index);
const elem = page.querySelector('.section' + index);
if (section === 'latestmedia') {
loadRecentlyAdded(elem, apiClient, user, userViews);
@ -168,11 +178,11 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getPortraitShape() {
return enableScrollX() ? 'autooverflow' : 'auto';
return enableScrollX() ? 'overflowPortrait' : 'portrait';
}
function getLibraryButtonsHtml(items) {
var html = '';
let html = '';
html += '<div class="verticalSection verticalSection-extrabottompadding">';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderMyMedia') + '</h2>';
@ -180,9 +190,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
html += '<div is="emby-itemscontainer" class="itemsContainer padded-left padded-right vertical-wrap focuscontainer-x" data-multiselect="false">';
// library card background images
for (var i = 0, length = items.length; i < length; i++) {
var item = items[i];
var icon = imageHelper.getLibraryIcon(item.CollectionType);
for (let i = 0, length = items.length; i < length; i++) {
const item = items[i];
const icon = imageHelper.getLibraryIcon(item.CollectionType);
html += '<a is="emby-linkbutton" href="' + appRouter.getRouteUrl(item) + '" class="raised homeLibraryButton"><span class="material-icons homeLibraryIcon ' + icon + '"></span><span class="homeLibraryText">' + item.Name + '</span></a>';
}
@ -194,24 +204,16 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) {
elem.classList.remove('verticalSection');
var html = getLibraryButtonsHtml(userViews);
const html = getLibraryButtonsHtml(userViews);
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
}
/**
* Returns a random integer between min (inclusive) and max (inclusive)
* Using Math.round() will give you a non-uniform distribution!
*/
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getFetchLatestItemsFn(serverId, parentId, collectionType) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
var limit = 16;
const apiClient = connectionManager.getApiClient(serverId);
let limit = 16;
if (enableScrollX()) {
if (collectionType === 'music') {
@ -227,9 +229,9 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
}
var options = {
const options = {
Limit: limit,
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path',
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Thumb',
ParentId: parentId
@ -241,8 +243,8 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getLatestItemsHtmlFn(itemType, viewType) {
return function (items) {
var cardLayout = false;
var shape;
const cardLayout = false;
let shape;
if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') {
shape = getPortraitShape();
} else if (viewType === 'music' || viewType === 'homevideos') {
@ -254,7 +256,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
return cardBuilder.getCardsHtml({
items: items,
shape: shape,
preferThumb: viewType !== 'movies' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null,
showUnplayedIndicator: false,
showChildCountIndicator: true,
context: 'home',
@ -272,7 +274,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function renderLatestSection(elem, apiClient, user, parent) {
var html = '';
let html = '';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
if (!layoutManager.tv) {
@ -303,7 +305,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType);
itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType);
itemsContainer.parentContainer = elem;
@ -311,10 +313,10 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function loadRecentlyAdded(elem, apiClient, user, userViews) {
elem.classList.remove('verticalSection');
var excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels'];
const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels'];
for (var i = 0, length = userViews.length; i < length; i++) {
var item = userViews[i];
for (let i = 0, length = userViews.length; i < length; i++) {
const item = userViews[i];
if (user.Configuration.LatestItemsExcludes.indexOf(item.Id) !== -1) {
continue;
}
@ -323,7 +325,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
continue;
}
var frag = document.createElement('div');
const frag = document.createElement('div');
frag.classList.add('verticalSection');
frag.classList.add('hide');
elem.appendChild(frag);
@ -332,14 +334,8 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
}
function getRequirePromise(deps) {
return new Promise(function (resolve, reject) {
require(deps, resolve);
});
}
function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews, allSections) {
var html = '';
export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews, allSections) {
let html = '';
if (userViews.length) {
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderMyMedia') + '</h2>';
if (enableScrollX()) {
@ -372,10 +368,10 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getContinueWatchingFetchFn(serverId) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
var screenWidth = dom.getWindowSize().innerWidth;
const apiClient = connectionManager.getApiClient(serverId);
const screenWidth = dom.getWindowSize().innerWidth;
var limit;
let limit;
if (enableScrollX()) {
limit = 12;
} else {
@ -383,7 +379,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
limit = Math.min(limit, 5);
}
var options = {
const options = {
Limit: limit,
Recursive: true,
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
@ -398,7 +394,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getContinueWatchingItemsHtml(items) {
var cardLayout = false;
const cardLayout = false;
return cardBuilder.getCardsHtml({
items: items,
preferThumb: true,
@ -419,7 +415,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function loadResumeVideo(elem, apiClient, userId) {
var html = '';
let html = '';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderContinueWatching') + '</h2>';
if (enableScrollX()) {
@ -437,7 +433,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.classList.add('hide');
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.fetchData = getContinueWatchingFetchFn(apiClient.serverId());
itemsContainer.getItemsHtml = getContinueWatchingItemsHtml;
itemsContainer.parentContainer = elem;
@ -445,10 +441,10 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getContinueListeningFetchFn(serverId) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
var screenWidth = dom.getWindowSize().innerWidth;
const apiClient = connectionManager.getApiClient(serverId);
const screenWidth = dom.getWindowSize().innerWidth;
var limit;
let limit;
if (enableScrollX()) {
limit = 12;
} else {
@ -456,7 +452,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
limit = Math.min(limit, 5);
}
var options = {
const options = {
Limit: limit,
Recursive: true,
Fields: 'PrimaryImageAspectRatio,BasicSyncInfo',
@ -471,7 +467,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getContinueListeningItemsHtml(items) {
var cardLayout = false;
const cardLayout = false;
return cardBuilder.getCardsHtml({
items: items,
preferThumb: true,
@ -492,7 +488,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function loadResumeAudio(elem, apiClient, userId) {
var html = '';
let html = '';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + globalize.translate('HeaderContinueWatching') + '</h2>';
if (enableScrollX()) {
@ -510,7 +506,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.classList.add('hide');
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.fetchData = getContinueListeningFetchFn(apiClient.serverId());
itemsContainer.getItemsHtml = getContinueListeningItemsHtml;
itemsContainer.parentContainer = elem;
@ -518,7 +514,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getOnNowFetchFn(serverId) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
const apiClient = connectionManager.getApiClient(serverId);
return apiClient.getLiveTvRecommendedPrograms({
userId: apiClient.getCurrentUserId(),
IsAiring: true,
@ -532,7 +528,6 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getOnNowItemsHtml(items) {
var cardLayout = false;
return cardBuilder.getCardsHtml({
items: items,
preferThumb: 'auto',
@ -559,7 +554,6 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
return Promise.resolve();
}
var userId = user.Id;
return apiClient.getLiveTvRecommendedPrograms({
userId: apiClient.getCurrentUserId(),
IsAiring: true,
@ -569,7 +563,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
EnableTotalRecordCount: false,
Fields: 'ChannelInfo,PrimaryImageAspectRatio'
}).then(function (result) {
var html = '';
let html = '';
if (result.Items.length) {
elem.classList.remove('padded-left');
elem.classList.remove('padded-right');
@ -632,7 +626,6 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
html += '</h2>';
html += '<span class="material-icons chevron_right"></span>';
html += '</a>';
} else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('HeaderOnNow') + '</h2>';
}
@ -654,7 +647,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.parentContainer = elem;
itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId());
itemsContainer.getItemsHtml = getOnNowItemsHtml;
@ -664,10 +657,10 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getNextUpFetchFn(serverId) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
const apiClient = connectionManager.getApiClient(serverId);
return apiClient.getNextUpEpisodes({
Limit: enableScrollX() ? 24 : 15,
Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo',
Fields: 'PrimaryImageAspectRatio,SeriesInfo,DateCreated,BasicSyncInfo,Path',
UserId: apiClient.getCurrentUserId(),
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb',
@ -677,7 +670,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function getNextUpItemsHtml(items) {
var cardLayout = false;
const cardLayout = false;
return cardBuilder.getCardsHtml({
items: items,
preferThumb: true,
@ -695,7 +688,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function loadNextUp(elem, apiClient, userId) {
var html = '';
let html = '';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards padded-left">';
if (!layoutManager.tv) {
@ -703,12 +696,12 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
serverId: apiClient.serverId()
}) + '" class="button-flat button-flat-mini sectionTitleTextButton">';
html += '<h2 class="sectionTitle sectionTitle-cards">';
html += globalize.translate('HeaderNextUp');
html += globalize.translate('NextUp');
html += '</h2>';
html += '<span class="material-icons chevron_right"></span>';
html += '</a>';
} else {
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('HeaderNextUp') + '</h2>';
html += '<h2 class="sectionTitle sectionTitle-cards">' + globalize.translate('NextUp') + '</h2>';
}
html += '</div>';
@ -727,7 +720,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.classList.add('hide');
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId());
itemsContainer.getItemsHtml = getNextUpItemsHtml;
itemsContainer.parentContainer = elem;
@ -735,7 +728,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) {
return function () {
var apiClient = connectionManager.getApiClient(serverId);
const apiClient = connectionManager.getApiClient(serverId);
return apiClient.getLiveTvRecordings({
userId: apiClient.getCurrentUserId(),
Limit: enableScrollX() ? 12 : 5,
@ -749,7 +742,6 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
function getLatestRecordingItemsHtml(activeRecordingsOnly) {
return function (items) {
var cardLayout = false;
return cardBuilder.getCardsHtml({
items: items,
shape: enableScrollX() ? 'autooverflow' : 'auto',
@ -774,11 +766,11 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
}
function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient, userId) {
var title = activeRecordingsOnly ?
const title = activeRecordingsOnly ?
globalize.translate('HeaderActiveRecordings') :
globalize.translate('HeaderLatestRecordings');
var html = '';
let html = '';
html += '<div class="sectionTitleContainer sectionTitleContainer-cards">';
html += '<h2 class="sectionTitle sectionTitle-cards padded-left">' + title + '</h2>';
@ -799,18 +791,19 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la
elem.classList.add('hide');
elem.innerHTML = html;
var itemsContainer = elem.querySelector('.itemsContainer');
const itemsContainer = elem.querySelector('.itemsContainer');
itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly);
itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly);
itemsContainer.parentContainer = elem;
}
return {
loadLibraryTiles: loadLibraryTiles,
getDefaultSection: getDefaultSection,
loadSections: loadSections,
destroySections: destroySections,
pause: pause,
resume: resume
};
});
export default {
loadLibraryTiles: loadLibraryTiles,
getDefaultSection: getDefaultSection,
loadSections: loadSections,
destroySections: destroySections,
pause: pause,
resume: resume
};
/* eslint-enable indent */

View file

@ -1,17 +1,20 @@
define(['appSettings', 'browser', 'events'], function (appSettings, browser, events) {
'use strict';
/* eslint-disable indent */
function getSavedVolume() {
import appSettings from 'appSettings' ;
import browser from 'browser';
import events from 'events';
export function getSavedVolume() {
return appSettings.get('volume') || 1;
}
function saveVolume(value) {
export function saveVolume(value) {
if (value) {
appSettings.set('volume', value);
}
}
function getCrossOriginValue(mediaSource) {
export function getCrossOriginValue(mediaSource) {
if (mediaSource.IsRemote) {
return null;
}
@ -30,34 +33,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
return false;
}
function enableHlsShakaPlayer(item, mediaSource, mediaType) {
/* eslint-disable-next-line compat/compat */
if (!!window.MediaSource && !!MediaSource.isTypeSupported) {
if (canPlayNativeHls()) {
if (browser.edge && mediaType === 'Video') {
return true;
}
// simple playback should use the native support
if (mediaSource.RunTimeTicks) {
//if (!browser.edge) {
//return false;
//}
}
//return false;
}
return true;
}
return false;
}
function enableHlsJsPlayer(runTimeTicks, mediaType) {
export function enableHlsJsPlayer(runTimeTicks, mediaType) {
if (window.MediaSource == null) {
return false;
}
@ -73,7 +49,6 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
if (canPlayNativeHls()) {
// Having trouble with chrome's native support and transcoded music
if (browser.android && mediaType === 'Audio') {
return true;
@ -98,8 +73,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
var recoverDecodingErrorDate;
var recoverSwapAudioCodecDate;
function handleHlsJsMediaError(instance, reject) {
export function handleHlsJsMediaError(instance, reject) {
var hlsPlayer = instance._hlsPlayer;
if (!hlsPlayer) {
@ -134,8 +108,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function onErrorInternal(instance, type) {
export function onErrorInternal(instance, type) {
// Needed for video
if (instance.destroyCustomTrack) {
instance.destroyCustomTrack(instance._mediaElement);
@ -148,7 +121,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
]);
}
function isValidDuration(duration) {
export function isValidDuration(duration) {
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
return true;
}
@ -162,13 +135,10 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function seekOnPlaybackStart(instance, element, ticks, onMediaReady) {
export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) {
var seconds = (ticks || 0) / 10000000;
if (seconds) {
var src = (instance.currentSrc() || '').toLowerCase();
// Appending #t=xxx to the query string doesn't seem to work with HLS
// For plain video files, not all browsers support it either
@ -200,12 +170,9 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function applySrc(elem, src, options) {
export function applySrc(elem, src, options) {
if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) {
return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) {
var playlist = new Windows.Media.Playback.MediaPlaybackList();
var source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file);
@ -214,9 +181,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true });
return Promise.resolve();
});
} else {
elem.src = src;
}
@ -224,18 +189,15 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
function onSuccessfulPlay(elem, onErrorFn) {
elem.addEventListener('error', onErrorFn);
}
function playWithPromise(elem, onErrorFn) {
export function playWithPromise(elem, onErrorFn) {
try {
var promise = elem.play();
if (promise && promise.then) {
// Chrome now returns a promise
return promise.catch(function (e) {
var errorName = (e.name || '').toLowerCase();
// safari uses aborterror
if (errorName === 'notallowederror' ||
@ -256,8 +218,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function destroyCastPlayer(instance) {
export function destroyCastPlayer(instance) {
var player = instance._castPlayer;
if (player) {
try {
@ -270,20 +231,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function destroyShakaPlayer(instance) {
var player = instance._shakaPlayer;
if (player) {
try {
player.destroy();
} catch (err) {
console.error(err);
}
instance._shakaPlayer = null;
}
}
function destroyHlsPlayer(instance) {
export function destroyHlsPlayer(instance) {
var player = instance._hlsPlayer;
if (player) {
try {
@ -296,7 +244,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function destroyFlvPlayer(instance) {
export function destroyFlvPlayer(instance) {
var player = instance._flvPlayer;
if (player) {
try {
@ -311,11 +259,9 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
}
function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) {
hls.on(Hls.Events.MANIFEST_PARSED, function () {
playWithPromise(elem, onErrorFn).then(resolve, function () {
if (reject) {
reject();
reject = null;
@ -324,14 +270,12 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
});
hls.on(Hls.Events.ERROR, function (event, data) {
console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false));
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
// try to recover network error
if (data.response && data.response.code && data.response.code >= 400) {
console.debug('hls.js response error code: ' + data.response.code);
// Trigger failure differently depending on whether this is prior to start of playback, or after
@ -345,7 +289,6 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
}
return;
}
break;
@ -358,7 +301,6 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
case Hls.ErrorTypes.NETWORK_ERROR:
if (data.response && data.response.code === 0) {
// This could be a CORS error related to access control response headers
console.debug('hls.js response error code: ' + data.response.code);
@ -403,8 +345,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
});
}
function onEndedInternal(instance, elem, onErrorFn) {
export function onEndedInternal(instance, elem, onErrorFn) {
elem.removeEventListener('error', onErrorFn);
elem.src = '';
@ -413,7 +354,6 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
destroyHlsPlayer(instance);
destroyFlvPlayer(instance);
destroyShakaPlayer(instance);
destroyCastPlayer(instance);
var stopInfo = {
@ -427,8 +367,7 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
instance._currentPlayOptions = null;
}
function getBufferedRanges(instance, elem) {
export function getBufferedRanges(instance, elem) {
var ranges = [];
var seekable = elem.buffered || [];
@ -441,7 +380,6 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
offset = offset || 0;
for (var i = 0, length = seekable.length; i < length; i++) {
var start = seekable.start(i);
var end = seekable.end(i);
@ -462,23 +400,4 @@ define(['appSettings', 'browser', 'events'], function (appSettings, browser, eve
return ranges;
}
return {
getSavedVolume: getSavedVolume,
saveVolume: saveVolume,
enableHlsJsPlayer: enableHlsJsPlayer,
enableHlsShakaPlayer: enableHlsShakaPlayer,
handleHlsJsMediaError: handleHlsJsMediaError,
isValidDuration: isValidDuration,
onErrorInternal: onErrorInternal,
seekOnPlaybackStart: seekOnPlaybackStart,
applySrc: applySrc,
playWithPromise: playWithPromise,
destroyHlsPlayer: destroyHlsPlayer,
destroyFlvPlayer: destroyFlvPlayer,
destroyCastPlayer: destroyCastPlayer,
bindEventsToHlsPlayer: bindEventsToHlsPlayer,
onEndedInternal: onEndedInternal,
getCrossOriginValue: getCrossOriginValue,
getBufferedRanges: getBufferedRanges
};
});
/* eslint-enable indent */

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,38 @@
define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'imageLoader', 'browser', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'emby-checkbox', 'paper-icon-button-light', 'emby-button', 'formDialogStyle', 'cardStyle'], function (dom, loading, appHost, dialogHelper, connectionManager, imageLoader, browser, layoutManager, scrollHelper, globalize, require) {
'use strict';
import dom from 'dom';
import loading from 'loading';
import appHost from 'apphost';
import dialogHelper from 'dialogHelper';
import connectionManager from 'connectionManager';
import imageLoader from 'imageLoader';
import browser from 'browser';
import layoutManager from 'layoutManager';
import scrollHelper from 'scrollHelper';
import globalize from 'globalize';
import 'emby-checkbox';
import 'paper-icon-button-light';
import 'emby-button';
import 'formDialogStyle';
import 'cardStyle';
var enableFocusTransform = !browser.slow && !browser.edge;
/* eslint-disable indent */
var currentItemId;
var currentItemType;
var currentResolve;
var currentReject;
var hasChanges = false;
const enableFocusTransform = !browser.slow && !browser.edge;
let currentItemId;
let currentItemType;
let currentResolve;
let currentReject;
let hasChanges = false;
// These images can be large and we're seeing memory problems in safari
var browsableImagePageSize = browser.slow ? 6 : 30;
const browsableImagePageSize = browser.slow ? 6 : 30;
var browsableImageStartIndex = 0;
var browsableImageType = 'Primary';
var selectedProvider;
let browsableImageStartIndex = 0;
let browsableImageType = 'Primary';
let selectedProvider;
function getBaseRemoteOptions() {
var options = {};
const options = {};
options.itemId = currentItemId;
@ -26,58 +40,53 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
function reloadBrowsableImages(page, apiClient) {
loading.show();
var options = getBaseRemoteOptions();
const options = getBaseRemoteOptions();
options.type = browsableImageType;
options.startIndex = browsableImageStartIndex;
options.limit = browsableImagePageSize;
options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked;
var provider = selectedProvider || '';
const provider = selectedProvider || '';
if (provider) {
options.ProviderName = provider;
}
apiClient.getAvailableRemoteImages(options).then(function (result) {
renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit);
page.querySelector('#selectBrowsableImageType').value = browsableImageType;
var providersHtml = result.Providers.map(function (p) {
const providersHtml = result.Providers.map(function (p) {
return '<option value="' + p + '">' + p + '</option>';
});
var selectImageProvider = page.querySelector('#selectImageProvider');
const selectImageProvider = page.querySelector('#selectImageProvider');
selectImageProvider.innerHTML = '<option value="">' + globalize.translate('All') + '</option>' + providersHtml;
selectImageProvider.value = provider;
loading.hide();
});
}
function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) {
page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount);
var html = '';
for (var i = 0, length = imagesResult.Images.length; i < length; i++) {
let html = '';
for (let i = 0, length = imagesResult.Images.length; i < length; i++) {
html += getRemoteImageHtml(imagesResult.Images[i], imageType, apiClient);
}
var availableImagesList = page.querySelector('.availableImagesList');
const availableImagesList = page.querySelector('.availableImagesList');
availableImagesList.innerHTML = html;
imageLoader.lazyChildren(availableImagesList);
var btnNextPage = page.querySelector('.btnNextPage');
var btnPreviousPage = page.querySelector('.btnPreviousPage');
const btnNextPage = page.querySelector('.btnNextPage');
const btnPreviousPage = page.querySelector('.btnPreviousPage');
if (btnNextPage) {
btnNextPage.addEventListener('click', function () {
@ -92,23 +101,21 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
reloadBrowsableImages(page, apiClient);
});
}
}
function getPagingHtml(startIndex, limit, totalRecordCount) {
let html = '';
var html = '';
var recordsEnd = Math.min(startIndex + limit, totalRecordCount);
const recordsEnd = Math.min(startIndex + limit, totalRecordCount);
// 20 is the minimum page size
var showControls = totalRecordCount > limit;
const showControls = totalRecordCount > limit;
html += '<div class="listPaging">';
html += '<span style="margin-right: 10px;">';
var startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
const startAtDisplay = totalRecordCount ? startIndex + 1 : 0;
html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount);
html += '</span>';
@ -127,7 +134,7 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
function downloadRemoteImage(page, apiClient, url, type, provider) {
var options = getBaseRemoteOptions();
const options = getBaseRemoteOptions();
options.Type = type;
options.ImageUrl = url;
@ -136,9 +143,8 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
loading.show();
apiClient.downloadRemoteImage(options).then(function () {
hasChanges = true;
var dlg = dom.parentWithClass(page, 'dialog');
const dlg = dom.parentWithClass(page, 'dialog');
dialogHelper.close(dlg);
});
}
@ -148,17 +154,17 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
function getRemoteImageHtml(image, imageType, apiClient) {
var tagName = layoutManager.tv ? 'button' : 'div';
var enableFooterButtons = !layoutManager.tv;
const tagName = layoutManager.tv ? 'button' : 'div';
const enableFooterButtons = !layoutManager.tv;
// TODO move card creation code to Card component
var html = '';
let html = '';
var cssClass = 'card scalableCard imageEditorCard';
var cardBoxCssClass = 'cardBox visualCardBox';
let cssClass = 'card scalableCard imageEditorCard';
const cardBoxCssClass = 'cardBox visualCardBox';
var shape = 'backdrop';
let shape;
if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') {
shape = 'backdrop';
} else if (imageType === 'Banner') {
@ -166,7 +172,6 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
} else if (imageType === 'Disc') {
shape = 'square';
} else {
if (currentItemType === 'Episode') {
shape = 'backdrop';
} else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') {
@ -217,19 +222,16 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
html += '<div class="cardText cardTextCentered">' + image.ProviderName + '</div>';
if (image.Width || image.Height || image.Language) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.Width && image.Height) {
html += image.Width + ' x ' + image.Height;
if (image.Language) {
html += ' • ' + image.Language;
}
} else {
if (image.Language) {
html += image.Language;
}
}
@ -238,13 +240,11 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
if (image.CommunityRating != null) {
html += '<div class="cardText cardText-secondary cardTextCentered">';
if (image.RatingType === 'Likes') {
html += image.CommunityRating + (image.CommunityRating === 1 ? ' like' : ' likes');
} else {
if (image.CommunityRating) {
html += image.CommunityRating.toFixed(1);
@ -270,7 +270,6 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
// end footer
html += '</div>';
//html += '</div>';
html += '</' + tagName + '>';
@ -287,7 +286,6 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
});
page.querySelector('#selectImageProvider').addEventListener('change', function () {
browsableImageStartIndex = 0;
selectedProvider = this.value;
@ -295,22 +293,20 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
});
page.querySelector('#chkAllLanguages').addEventListener('change', function () {
browsableImageStartIndex = 0;
reloadBrowsableImages(page, apiClient);
});
page.addEventListener('click', function (e) {
var btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage');
if (btnDownloadRemoteImage) {
var card = dom.parentWithClass(btnDownloadRemoteImage, 'card');
const card = dom.parentWithClass(btnDownloadRemoteImage, 'card');
downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider'));
return;
}
var btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
const btnImageCard = dom.parentWithClass(e.target, 'btnImageCard');
if (btnImageCard) {
downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider'));
}
@ -320,14 +316,13 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
function showEditor(itemId, serverId, itemType) {
loading.show();
require(['text!./imageDownloader.template.html'], function (template) {
var apiClient = connectionManager.getApiClient(serverId);
import('text!./imageDownloader.template.html').then(({default: template}) => {
const apiClient = connectionManager.getApiClient(serverId);
currentItemId = itemId;
currentItemType = itemType;
var dialogOptions = {
const dialogOptions = {
removeOnClose: true
};
@ -337,9 +332,9 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.innerHTML = globalize.translateDocument(template, 'core');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
@ -350,11 +345,10 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
dialogHelper.open(dlg);
var editorContent = dlg.querySelector('.formDialogContent');
const editorContent = dlg.querySelector('.formDialogContent');
initEditor(editorContent, apiClient);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
@ -363,7 +357,7 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
function onDialogClosed() {
var dlg = this;
const dlg = this;
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
@ -377,18 +371,20 @@ define(['dom', 'loading', 'apphost', 'dialogHelper', 'connectionManager', 'image
}
}
return {
show: function (itemId, serverId, itemType, imageType) {
return new Promise(function (resolve, reject) {
currentResolve = resolve;
currentReject = reject;
hasChanges = false;
browsableImageStartIndex = 0;
browsableImageType = imageType || 'Primary';
selectedProvider = null;
export function show(itemId, serverId, itemType, imageType) {
return new Promise(function (resolve, reject) {
currentResolve = resolve;
currentReject = reject;
hasChanges = false;
browsableImageStartIndex = 0;
browsableImageType = imageType || 'Primary';
selectedProvider = null;
showEditor(itemId, serverId, itemType);
});
}
showEditor(itemId, serverId, itemType);
});
}
};
});
export default {
show: show
};
/* eslint-enable indent */

View file

@ -1,16 +1,27 @@
define(['globalize', 'dom', 'dialogHelper', 'emby-checkbox', 'emby-select', 'emby-input'], function (globalize, dom, dialogHelper) {
'use strict';
/* eslint-disable indent */
/**
* Module for image Options Editor.
* @module components/imageOptionsEditor/imageOptionsEditor
*/
import globalize from 'globalize';
import dom from 'dom';
import dialogHelper from 'dialogHelper';
import 'emby-checkbox';
import 'emby-select';
import 'emby-input';
function getDefaultImageConfig(itemType, type) {
return {
Type: type,
MinWidth: 0,
Limit: 'Primary' === type ? 1 : 0
Limit: type === 'Primary' ? 1 : 0
};
}
function findImageOptions(imageOptions, type) {
return imageOptions.filter(function (i) {
return imageOptions.filter(i => {
return i.Type == type;
})[0];
}
@ -31,14 +42,14 @@ define(['globalize', 'dom', 'dialogHelper', 'emby-checkbox', 'emby-select', 'emb
}
function loadValues(context, itemType, options, availableOptions) {
var supportedImageTypes = availableOptions.SupportedImageTypes || [];
setVisibilityOfBackdrops(context.querySelector('.backdropFields'), -1 != supportedImageTypes.indexOf('Backdrop'));
setVisibilityOfBackdrops(context.querySelector('.screenshotFields'), -1 != supportedImageTypes.indexOf('Screenshot'));
Array.prototype.forEach.call(context.querySelectorAll('.imageType'), function (i) {
var imageType = i.getAttribute('data-imagetype');
var container = dom.parentWithTag(i, 'LABEL');
const supportedImageTypes = availableOptions.SupportedImageTypes || [];
setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop'));
setVisibilityOfBackdrops(context.querySelector('.screenshotFields'), supportedImageTypes.includes('Screenshot'));
Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => {
const imageType = i.getAttribute('data-imagetype');
const container = dom.parentWithTag(i, 'LABEL');
if (-1 == supportedImageTypes.indexOf(imageType)) {
if (!supportedImageTypes.includes(imageType)) {
container.classList.add('hide');
} else {
container.classList.remove('hide');
@ -50,16 +61,16 @@ define(['globalize', 'dom', 'dialogHelper', 'emby-checkbox', 'emby-select', 'emb
i.checked = false;
}
});
var backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType);
const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType);
context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit;
context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth;
var screenshotConfig = getImageConfig(options, availableOptions, 'Screenshot', itemType);
const screenshotConfig = getImageConfig(options, availableOptions, 'Screenshot', itemType);
context.querySelector('#txtMaxScreenshots').value = screenshotConfig.Limit;
context.querySelector('#txtMinScreenshotDownloadWidth').value = screenshotConfig.MinWidth;
}
function saveValues(context, options) {
options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), function (c) {
options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => {
return {
Type: c.getAttribute('data-imagetype'),
Limit: c.checked ? 1 : 0,
@ -78,35 +89,36 @@ define(['globalize', 'dom', 'dialogHelper', 'emby-checkbox', 'emby-select', 'emb
});
}
function editor() {
this.show = function (itemType, options, availableOptions) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'components/imageOptionsEditor/imageOptionsEditor.template.html', true);
async function showEditor(itemType, options, availableOptions) {
const response = await fetch('components/imageOptionsEditor/imageOptionsEditor.template.html');
const template = await response.text();
xhr.onload = function (e) {
var template = this.response;
var dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateDocument(template);
dlg.addEventListener('close', function () {
saveValues(dlg, options);
});
loadValues(dlg, itemType, options, availableOptions);
dialogHelper.open(dlg).then(resolve, resolve);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
};
xhr.send();
});
};
var dlg = dialogHelper.createDialog({
size: 'small',
removeOnClose: true,
scrollY: false
});
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateHtml(template);
dlg.addEventListener('close', function () {
saveValues(dlg, options);
});
loadValues(dlg, itemType, options, availableOptions);
dialogHelper.open(dlg).then(() => {
return;
}).catch(() => {
return;
});
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
return editor;
});
export class editor {
constructor() {
this.show = showEditor;
}
}
/* eslint-enable indent */
export default editor;

View file

@ -1,25 +1,40 @@
define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', 'layoutManager', 'globalize', 'require', 'emby-button', 'emby-select', 'formDialogStyle', 'css!./style'], function (dialogHelper, connectionManager, dom, loading, scrollHelper, layoutManager, globalize, require) {
'use strict';
/* eslint-disable indent */
var currentItemId;
var currentServerId;
var currentFile;
var hasChanges = false;
/**
* Module for imageUploader.
* @module components/imageUploader/imageUploader
*/
import dialogHelper from 'dialogHelper';
import connectionManager from 'connectionManager';
import dom from 'dom';
import loading from 'loading';
import scrollHelper from 'scrollHelper';
import layoutManager from 'layoutManager';
import globalize from 'globalize';
import 'emby-button';
import 'emby-select';
import 'formDialogStyle';
import 'css!./style';
let currentItemId;
let currentServerId;
let currentFile;
let hasChanges = false;
function onFileReaderError(evt) {
loading.hide();
switch (evt.target.error.code) {
case evt.target.error.NOT_FOUND_ERR:
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageFileReadError'));
});
break;
case evt.target.error.ABORT_ERR:
break; // noop
default:
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageFileReadError'));
});
break;
@ -27,8 +42,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
}
function setFiles(page, files) {
var file = files[0];
const file = files[0];
if (!file || !file.type.match('image.*')) {
page.querySelector('#imageOutput').innerHTML = '';
@ -39,25 +53,25 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
currentFile = file;
var reader = new FileReader();
const reader = new FileReader();
reader.onerror = onFileReaderError;
reader.onloadstart = function () {
reader.onloadstart = () => {
page.querySelector('#fldUpload').classList.add('hide');
};
reader.onabort = function () {
reader.onabort = () => {
loading.hide();
console.debug('File read cancelled');
};
// Closure to capture the file information.
reader.onload = (function (theFile) {
return function (e) {
reader.onload = (theFile => {
return e => {
// Render thumbnail.
var html = ['<img style="max-width:100%;max-height:100%;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
const html = ['<img style="max-width:100%;max-height:100%;" src="', e.target.result, '" title="', escape(theFile.name), '"/>'].join('');
page.querySelector('#imageOutput').innerHTML = html;
page.querySelector('#dropImageText').classList.add('hide');
page.querySelector('#fldUpload').classList.remove('hide');
};
})(file);
@ -67,15 +81,14 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
}
function onSubmit(e) {
var file = currentFile;
const file = currentFile;
if (!file) {
return false;
}
if (!file.type.startsWith('image/')) {
require(['toast'], function (toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageImageFileTypeAllowed'));
});
e.preventDefault();
@ -84,19 +97,18 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
loading.show();
var dlg = dom.parentWithClass(this, 'dialog');
const dlg = dom.parentWithClass(this, 'dialog');
var imageType = dlg.querySelector('#selectImageType').value;
const imageType = dlg.querySelector('#selectImageType').value;
if (imageType === 'None') {
require(['toast'], function(toast) {
import('toast').then(({default: toast}) => {
toast(globalize.translate('MessageImageTypeNotSelected'));
});
e.preventDefault();
return false;
}
connectionManager.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(function () {
connectionManager.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => {
dlg.querySelector('#uploadImage').value = '';
loading.hide();
@ -109,28 +121,25 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
}
function initEditor(page) {
page.querySelector('form').addEventListener('submit', onSubmit);
page.querySelector('#uploadImage').addEventListener('change', function () {
setFiles(page, this.files);
});
page.querySelector('.btnBrowse').addEventListener('click', function () {
page.querySelector('.btnBrowse').addEventListener('click', () => {
page.querySelector('#uploadImage').click();
});
}
function showEditor(options, resolve, reject) {
function showEditor(options, resolve) {
options = options || {};
require(['text!./imageUploader.template.html'], function (template) {
return import('text!./imageUploader.template.html').then(({default: template}) => {
currentItemId = options.itemId;
currentServerId = options.serverId;
var dialogOptions = {
const dialogOptions = {
removeOnClose: true
};
@ -140,19 +149,18 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateDocument(template, 'core');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', function () {
dlg.addEventListener('close', () => {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
@ -167,22 +175,21 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', '
dlg.querySelector('#selectImageType').value = options.imageType || 'Primary';
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
});
}
return {
show: function (options) {
export function show(options) {
return new Promise(resolve => {
hasChanges = false;
return new Promise(function (resolve, reject) {
showEditor(options, resolve);
});
}
hasChanges = false;
showEditor(options, resolve, reject);
});
}
};
});
/* eslint-enable indent */
export default {
show: show
};

View file

@ -20,7 +20,7 @@
</div>
<div>
<div class="imageEditor-dropZone fieldDescription">
<div>${LabelDropImageHere}</div>
<div id="dropImageText">${LabelDropImageHere}</div>
<output id="imageOutput" class="flex align-items-center justify-content-center" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;"></output>
<input type="file" accept="image/*" id="uploadImage" name="uploadImage" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;opacity:0;" />
</div>

View file

@ -1,14 +1,29 @@
define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager', 'focusManager', 'globalize', 'scrollHelper', 'imageLoader', 'require', 'browser', 'apphost', 'cardStyle', 'formDialogStyle', 'emby-button', 'paper-icon-button-light', 'css!./imageeditor'], function (dialogHelper, connectionManager, loading, dom, layoutManager, focusManager, globalize, scrollHelper, imageLoader, require, browser, appHost) {
'use strict';
import dialogHelper from 'dialogHelper';
import connectionManager from 'connectionManager';
import loading from 'loading';
import dom from 'dom';
import layoutManager from 'layoutManager';
import focusManager from 'focusManager';
import globalize from 'globalize';
import scrollHelper from 'scrollHelper';
import imageLoader from 'imageLoader';
import browser from 'browser';
import appHost from 'apphost';
import 'cardStyle';
import 'formDialogStyle';
import 'emby-button';
import 'paper-icon-button-light';
import 'css!./imageeditor';
var enableFocusTransform = !browser.slow && !browser.edge;
/* eslint-disable indent */
var currentItem;
var hasChanges = false;
const enableFocusTransform = !browser.slow && !browser.edge;
let currentItem;
let hasChanges = false;
function getBaseRemoteOptions() {
var options = {};
const options = {};
options.itemId = currentItem.Id;
@ -16,16 +31,14 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function reload(page, item, focusContext) {
loading.show();
var apiClient;
let apiClient;
if (item) {
apiClient = connectionManager.getApiClient(item.ServerId);
reloadItem(page, item, apiClient, focusContext);
} else {
apiClient = connectionManager.getApiClient(currentItem.ServerId);
apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (item) {
reloadItem(page, item, apiClient, focusContext);
@ -34,9 +47,8 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function addListeners(container, className, eventName, fn) {
container.addEventListener(eventName, function (e) {
var elem = dom.parentWithClass(e.target, className);
const elem = dom.parentWithClass(e.target, className);
if (elem) {
fn.call(elem, e);
}
@ -44,14 +56,11 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function reloadItem(page, item, apiClient, focusContext) {
currentItem = item;
apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) {
var btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages');
for (var i = 0, length = btnBrowseAllImages.length; i < length; i++) {
if (providers.length) {
btnBrowseAllImages[i].classList.remove('hide');
} else {
@ -60,7 +69,6 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) {
renderStandardImages(page, apiClient, item, imageInfos, providers);
renderBackdrops(page, apiClient, item, imageInfos, providers);
renderScreenshots(page, apiClient, item, imageInfos, providers);
@ -74,7 +82,6 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function getImageUrl(item, apiClient, type, index, options) {
options = options || {};
options.type = type;
options.index = index;
@ -94,13 +101,12 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function getCardHtml(image, index, numImages, apiClient, imageProviders, imageSize, tagName, enableFooterButtons) {
// TODO move card creation code to Card component
var html = '';
let html = '';
var cssClass = 'card scalableCard imageEditorCard';
var cardBoxCssClass = 'cardBox visualCardBox';
let cssClass = 'card scalableCard imageEditorCard';
const cardBoxCssClass = 'cardBox visualCardBox';
cssClass += ' backdropCard backdropCard-scalable';
@ -130,7 +136,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
html += '<div class="cardContent">';
var imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize });
const imageUrl = getImageUrl(currentItem, apiClient, image.ImageType, image.ImageIndex, { maxWidth: imageSize });
html += '<div class="cardImageContainer" style="background-image:url(\'' + imageUrl + '\');background-position:center center;background-size:contain;"></div>';
@ -153,7 +159,6 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
html += '<div class="cardText cardTextCentered">';
if (image.ImageType === 'Backdrop' || image.ImageType === 'Screenshot') {
if (index > 0) {
html += '<button type="button" is="paper-icon-button-light" class="btnMoveImage autoSize" data-imagetype="' + image.ImageType + '" data-index="' + image.ImageIndex + '" data-newindex="' + (image.ImageIndex - 1) + '" title="' + globalize.translate('MoveLeft') + '"><span class="material-icons chevron_left"></span></button>';
} else {
@ -183,13 +188,10 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) {
var afterConfirm = function () {
const afterConfirm = function () {
apiClient.deleteItemImage(itemId, type, index).then(function () {
hasChanges = true;
reload(context);
});
};
@ -198,8 +200,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
return;
}
require(['confirm'], function (confirm) {
import('confirm').then(({default: confirm}) => {
confirm({
text: globalize.translate('ConfirmDeleteImage'),
@ -211,36 +212,30 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) {
apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () {
hasChanges = true;
reload(context, null, focusContext);
}, function () {
require(['alert'], function (alert) {
alert(globalize.translate('DefaultErrorMessage'));
import('alert').then(({default: alert}) => {
alert(globalize.translate('ErrorDefault'));
});
});
}
function renderImages(page, item, apiClient, images, imageProviders, elem) {
let html = '';
var html = '';
var imageSize = 300;
var windowSize = dom.getWindowSize();
let imageSize = 300;
const windowSize = dom.getWindowSize();
if (windowSize.innerWidth >= 1280) {
imageSize = Math.round(windowSize.innerWidth / 4);
}
var tagName = layoutManager.tv ? 'button' : 'div';
var enableFooterButtons = !layoutManager.tv;
for (var i = 0, length = images.length; i < length; i++) {
var image = images[i];
const tagName = layoutManager.tv ? 'button' : 'div';
const enableFooterButtons = !layoutManager.tv;
for (let i = 0, length = images.length; i < length; i++) {
const image = images[i];
html += getCardHtml(image, i, length, apiClient, imageProviders, imageSize, tagName, enableFooterButtons);
}
@ -249,8 +244,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) {
var images = imageInfos.filter(function (i) {
const images = imageInfos.filter(function (i) {
return i.ImageType !== 'Screenshot' && i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter';
});
@ -258,10 +252,8 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) {
var images = imageInfos.filter(function (i) {
const images = imageInfos.filter(function (i) {
return i.ImageType === 'Backdrop';
}).sort(function (a, b) {
return a.ImageIndex - b.ImageIndex;
});
@ -275,10 +267,8 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function renderScreenshots(page, apiClient, item, imageInfos, imageProviders) {
var images = imageInfos.filter(function (i) {
const images = imageInfos.filter(function (i) {
return i.ImageType === 'Screenshot';
}).sort(function (a, b) {
return a.ImageIndex - b.ImageIndex;
});
@ -292,32 +282,26 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
function showImageDownloader(page, imageType) {
require(['imageDownloader'], function (ImageDownloader) {
import('imageDownloader').then(({default: ImageDownloader}) => {
ImageDownloader.show(currentItem.Id, currentItem.ServerId, currentItem.Type, imageType).then(function () {
hasChanges = true;
reload(page);
});
});
}
function showActionSheet(context, imageCard) {
const itemId = imageCard.getAttribute('data-id');
const serverId = imageCard.getAttribute('data-serverid');
const apiClient = connectionManager.getApiClient(serverId);
var itemId = imageCard.getAttribute('data-id');
var serverId = imageCard.getAttribute('data-serverid');
var apiClient = connectionManager.getApiClient(serverId);
const type = imageCard.getAttribute('data-imagetype');
const index = parseInt(imageCard.getAttribute('data-index'));
const providerCount = parseInt(imageCard.getAttribute('data-providers'));
const numImages = parseInt(imageCard.getAttribute('data-numimages'));
var type = imageCard.getAttribute('data-imagetype');
var index = parseInt(imageCard.getAttribute('data-index'));
var providerCount = parseInt(imageCard.getAttribute('data-providers'));
var numImages = parseInt(imageCard.getAttribute('data-numimages'));
require(['actionsheet'], function (actionSheet) {
var commands = [];
import('actionsheet').then(({default: actionSheet}) => {
const commands = [];
commands.push({
name: globalize.translate('Delete'),
@ -353,9 +337,7 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
positionTo: imageCard
}).then(function (id) {
switch (id) {
case 'delete':
deleteImage(context, itemId, type, index, apiClient, false);
break;
@ -371,16 +353,14 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
default:
break;
}
});
});
}
function initEditor(context, options) {
var uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
var isFileInputSupported = appHost.supports('fileinput');
for (var i = 0, length = uploadButtons.length; i < length; i++) {
const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu');
const isFileInputSupported = appHost.supports('fileinput');
for (let i = 0, length = uploadButtons.length; i < length; i++) {
if (isFileInputSupported) {
uploadButtons[i].classList.remove('hide');
} else {
@ -389,10 +369,9 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
}
addListeners(context, 'btnOpenUploadMenu', 'click', function () {
var imageType = this.getAttribute('data-imagetype');
require(['imageUploader'], function (imageUploader) {
const imageType = this.getAttribute('data-imagetype');
import('imageUploader').then(({default: imageUploader}) => {
imageUploader.show({
theme: options.theme,
@ -401,7 +380,6 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
serverId: currentItem.ServerId
}).then(function (hasChanged) {
if (hasChanged) {
hasChanges = true;
reload(context);
@ -423,34 +401,32 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
});
addListeners(context, 'btnDeleteImage', 'click', function () {
var type = this.getAttribute('data-imagetype');
var index = this.getAttribute('data-index');
const type = this.getAttribute('data-imagetype');
let index = this.getAttribute('data-index');
index = index === 'null' ? null : parseInt(index);
var apiClient = connectionManager.getApiClient(currentItem.ServerId);
const apiClient = connectionManager.getApiClient(currentItem.ServerId);
deleteImage(context, currentItem.Id, type, index, apiClient, true);
});
addListeners(context, 'btnMoveImage', 'click', function () {
var type = this.getAttribute('data-imagetype');
var index = this.getAttribute('data-index');
var newIndex = this.getAttribute('data-newindex');
var apiClient = connectionManager.getApiClient(currentItem.ServerId);
const type = this.getAttribute('data-imagetype');
const index = this.getAttribute('data-index');
const newIndex = this.getAttribute('data-newindex');
const apiClient = connectionManager.getApiClient(currentItem.ServerId);
moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer'));
});
}
function showEditor(options, resolve, reject) {
var itemId = options.itemId;
var serverId = options.serverId;
const itemId = options.itemId;
const serverId = options.serverId;
loading.show();
require(['text!./imageeditor.template.html'], function (template) {
var apiClient = connectionManager.getApiClient(serverId);
import('text!./imageeditor.template.html').then(({default: template}) => {
const apiClient = connectionManager.getApiClient(serverId);
apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) {
var dialogOptions = {
const dialogOptions = {
removeOnClose: true
};
@ -460,11 +436,11 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
dialogOptions.size = 'small';
}
var dlg = dialogHelper.createDialog(dialogOptions);
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.innerHTML = globalize.translateDocument(template, 'core');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
@ -474,7 +450,6 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
@ -493,22 +468,21 @@ define(['dialogHelper', 'connectionManager', 'loading', 'dom', 'layoutManager',
reload(dlg, item);
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
});
});
}
return {
show: function (options) {
export function show (options) {
return new Promise(function (resolve, reject) {
hasChanges = false;
showEditor(options, resolve, reject);
});
}
return new Promise(function (resolve, reject) {
export default {
show
};
hasChanges = false;
showEditor(options, resolve, reject);
});
}
};
});
/* eslint-enable indent */

View file

@ -1,5 +1,6 @@
import * as lazyLoader from 'lazyLoader';
import * as userSettings from 'userSettings';
import * as blurhash from 'blurhash';
import 'css!./style';
/* eslint-disable indent */
@ -11,47 +12,96 @@ import 'css!./style';
fillImageElement(elem, source);
}
function itemBlurhashing(target, blurhashstr) {
if (blurhash.isBlurhashValid(blurhashstr)) {
// Although the default values recommended by Blurhash developers is 32x32, a size of 18x18 seems to be the sweet spot for us,
// improving the performance and reducing the memory usage, while retaining almost full blur quality.
// Lower values had more visible pixelation
const width = 18;
const height = 18;
let pixels;
try {
pixels = blurhash.decode(blurhashstr, width, height);
} catch (err) {
console.error('Blurhash decode error: ', err);
target.classList.add('non-blurhashable');
return;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const imgData = ctx.createImageData(width, height);
imgData.data.set(pixels);
ctx.putImageData(imgData, 0, 0);
requestAnimationFrame(() => {
canvas.classList.add('blurhash-canvas');
if (userSettings.enableFastFadein()) {
canvas.classList.add('lazy-blurhash-fadein-fast');
} else {
canvas.classList.add('lazy-blurhash-fadein');
}
target.parentNode.insertBefore(canvas, target);
target.classList.add('blurhashed');
target.removeAttribute('data-blurhash');
});
}
}
export function fillImage(entry) {
if (!entry) {
throw new Error('entry cannot be null');
}
const target = entry.target;
var source = undefined;
if (entry.target) {
source = entry.target.getAttribute('data-src');
if (target) {
source = target.getAttribute('data-src');
} else {
source = entry;
}
if (entry.intersectionRatio > 0) {
if (source) fillImageElement(entry.target, source);
if (source) fillImageElement(target, source);
} else if (!source) {
emptyImageElement(entry.target);
requestAnimationFrame(() => {
emptyImageElement(target);
});
}
}
function fillImageElement(elem, url) {
if (url === undefined) {
throw new Error('url cannot be undefined');
throw new TypeError('url cannot be undefined');
}
let preloaderImg = new Image();
const preloaderImg = new Image();
preloaderImg.src = url;
elem.classList.add('lazy-hidden');
preloaderImg.addEventListener('load', () => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
} else {
elem.setAttribute('src', url);
}
requestAnimationFrame(() => {
if (elem.tagName !== 'IMG') {
elem.style.backgroundImage = "url('" + url + "')";
if (elem.classList.contains('blurhashed')) {
elem.style.backgroundColor = '#fff';
}
} else {
elem.setAttribute('src', url);
}
elem.removeAttribute('data-src');
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
elem.removeAttribute('data-src');
elem.classList.remove('lazy-hidden');
if (userSettings.enableFastFadein()) {
elem.classList.add('lazy-image-fadein-fast');
} else {
elem.classList.add('lazy-image-fadein');
}
});
});
}
@ -61,27 +111,35 @@ import 'css!./style';
if (elem.tagName !== 'IMG') {
url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, '');
elem.style.backgroundImage = 'none';
elem.style.backgroundColor = null;
} else {
url = elem.getAttribute('src');
elem.setAttribute('src', '');
}
elem.setAttribute('data-src', url);
elem.classList.remove('lazy-image-fadein-fast');
elem.classList.remove('lazy-image-fadein');
elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein');
elem.classList.add('lazy-hidden');
}
export function lazyChildren(elem) {
if (userSettings.enableBlurhash()) {
for (const lazyElem of elem.querySelectorAll('.lazy')) {
const blurhashstr = lazyElem.getAttribute('data-blurhash');
if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) {
itemBlurhashing(lazyElem, blurhashstr);
} else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) {
lazyElem.classList.add('non-blurhashable');
}
}
}
lazyLoader.lazyChildren(elem, fillImage);
}
export function getPrimaryImageAspectRatio(items) {
var values = [];
for (var i = 0, length = items.length; i < length; i++) {
var ratio = items[i].PrimaryImageAspectRatio || 0;
if (!ratio) {
@ -137,15 +195,21 @@ import 'css!./style';
}
export function fillImages(elems) {
for (var i = 0, length = elems.length; i < length; i++) {
var elem = elems[0];
fillImage(elem);
}
}
export function setLazyImage(element, url) {
element.classList.add('lazy');
element.setAttribute('data-src', url);
lazyImage(element);
}
/* eslint-enable indent */
export default {
setLazyImage: setLazyImage,
fillImages: fillImages,
fillImage: fillImage,
lazyImage: lazyImage,

View file

@ -1,13 +1,42 @@
.cardImageContainer.lazy {
.lazy-image-fadein {
opacity: 1;
transition: opacity 0.5s;
}
.lazy-image-fadein-fast {
opacity: 1;
transition: opacity 0.1s;
}
.lazy-hidden {
opacity: 0;
}
.cardImageContainer.lazy.lazy-image-fadein {
opacity: 1;
transition: opacity 0.7s;
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.cardImageContainer.lazy.lazy-image-fadein-fast {
opacity: 1;
transition: opacity 0.2s;
.lazy-blurhash-fadein-fast {
animation: fadein 0.1s;
}
.lazy-blurhash-fadein {
animation: fadein 0.4s;
}
.blurhash-canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}

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