diff --git a/.ci/azure-pipelines-build.yml b/.ci/azure-pipelines-build.yml new file mode 100644 index 0000000000..128fe54605 --- /dev/null +++ b/.ci/azure-pipelines-build.yml @@ -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)' diff --git a/.ci/azure-pipelines-lint.yml b/.ci/azure-pipelines-lint.yml new file mode 100644 index 0000000000..1e4bddbd04 --- /dev/null +++ b/.ci/azure-pipelines-lint.yml @@ -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' diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml new file mode 100644 index 0000000000..2249bd20eb --- /dev/null +++ b/.ci/azure-pipelines-package.yml @@ -0,0 +1,106 @@ +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: 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' + + steps: + - 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) + stable + +- 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)' diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 059c39aa56..3d7a5f1cde 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -12,88 +12,6 @@ pr: - '*' jobs: -- job: Build - displayName: 'Build' - - strategy: - matrix: - Development: - BuildConfiguration: development - Production: - BuildConfiguration: production - - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: NodeTool@0 - displayName: 'Install Node' - inputs: - versionSpec: '12.x' - - - task: Cache@2 - displayName: '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: '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 diff --git a/.eslintrc.js b/.eslintrc.js index 27b5c2a237..ab53f0f03d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -132,6 +132,7 @@ module.exports = { 'Object.getOwnPropertyDescriptor', 'Object.getPrototypeOf', 'Object.keys', + 'Object.entries', 'Object.getOwnPropertyNames', 'Function.name', 'Function.hasInstance', diff --git a/.gitignore b/.gitignore index 9bccd32fb8..36b843f022 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# config -config.json - # npm dist web @@ -10,5 +7,5 @@ node_modules .idea .vscode -#log +# log yarn-error.log diff --git a/deployment/Dockerfile.centos.all b/deployment/Dockerfile.centos similarity index 51% rename from deployment/Dockerfile.centos.all rename to deployment/Dockerfile.centos index 93bf8d6988..64cffc7eed 100644 --- a/deployment/Dockerfile.centos.all +++ b/deployment/Dockerfile.centos @@ -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"] diff --git a/deployment/Dockerfile.debian.all b/deployment/Dockerfile.debian similarity index 69% rename from deployment/Dockerfile.debian.all rename to deployment/Dockerfile.debian index 54281a5eb4..80ea103b92 100644 --- a/deployment/Dockerfile.debian.all +++ b/deployment/Dockerfile.debian @@ -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"] diff --git a/deployment/Dockerfile.docker b/deployment/Dockerfile.docker new file mode 100644 index 0000000000..5d12938185 --- /dev/null +++ b/deployment/Dockerfile.docker @@ -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} diff --git a/deployment/Dockerfile.fedora.all b/deployment/Dockerfile.fedora similarity index 56% rename from deployment/Dockerfile.fedora.all rename to deployment/Dockerfile.fedora index d47f4ff4da..e6026985d1 100644 --- a/deployment/Dockerfile.fedora.all +++ b/deployment/Dockerfile.fedora @@ -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"] diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index e0d1f45265..0d4c79bb2f 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -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"] diff --git a/deployment/build.centos b/deployment/build.centos new file mode 100755 index 0000000000..25d1e8c175 --- /dev/null +++ b/deployment/build.centos @@ -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 <>jellyfin-web.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- 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 diff --git a/deployment/build.centos.all b/deployment/build.centos.all deleted file mode 100755 index 8c2cec6d33..0000000000 --- a/deployment/build.centos.all +++ /dev/null @@ -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 diff --git a/deployment/build.debian b/deployment/build.debian new file mode 100755 index 0000000000..9fe3fb51e9 --- /dev/null +++ b/deployment/build.debian @@ -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 <changelog +jellyfin-web (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Web unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team $( 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 diff --git a/deployment/build.debian.all b/deployment/build.debian.all deleted file mode 100755 index 8d617a288e..0000000000 --- a/deployment/build.debian.all +++ /dev/null @@ -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 diff --git a/deployment/build.fedora b/deployment/build.fedora new file mode 100755 index 0000000000..60d270ed6c --- /dev/null +++ b/deployment/build.fedora @@ -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 <>jellyfin-web.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team +- 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 diff --git a/deployment/build.fedora.all b/deployment/build.fedora.all deleted file mode 100755 index 4ba12f35ea..0000000000 --- a/deployment/build.fedora.all +++ /dev/null @@ -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 diff --git a/deployment/build.portable b/deployment/build.portable index c4cbe927e4..873c145fab 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -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} diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index dbc0bd0efa..dcc9d9d2ab 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -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 diff --git a/gulpfile.js b/gulpfile.js index 03826e8b6e..84f4558e6a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -16,6 +16,7 @@ const stream = require('webpack-stream'); const inject = require('gulp-inject'); const postcss = require('gulp-postcss'); const sass = require('gulp-sass'); +const gulpif = require('gulp-if'); const lazypipe = require('lazypipe'); sass.compiler = require('node-sass'); @@ -67,7 +68,7 @@ function serve() { } }); - watch(options.apploader.query, apploader()); + watch(options.apploader.query, apploader(true)); watch('src/bundle.js', webpack); @@ -130,12 +131,18 @@ function javascript(query) { .pipe(browserSync.stream()); } -function apploader() { - return src(options.apploader.query, { base: './src/' }) - .pipe(concat('scripts/apploader.js')) - .pipe(pipelineJavascript()) - .pipe(dest('dist/')) - .pipe(browserSync.stream()); +function apploader(standalone) { + function task() { + return src(options.apploader.query, { base: './src/' }) + .pipe(gulpif(standalone, concat('scripts/apploader.js'))) + .pipe(pipelineJavascript()) + .pipe(dest('dist/')) + .pipe(browserSync.stream()); + } + + task.displayName = 'apploader'; + + return task; } function webpack() { @@ -183,5 +190,10 @@ function injectBundle() { .pipe(browserSync.stream()); } -exports.default = series(clean, parallel(javascript, apploader, webpack, css, html, images, copy), injectBundle); -exports.serve = series(exports.default, serve); +function build(standalone) { + return series(clean, parallel(javascript, apploader(standalone), webpack, css, html, images, copy)); +} + +exports.default = series(build(false), injectBundle); +exports.standalone = series(build(true), injectBundle); +exports.serve = series(exports.standalone, serve); diff --git a/package.json b/package.json index 117477a705..6aed7fac10 100644 --- a/package.json +++ b/package.json @@ -5,29 +5,29 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "^7.10.2", + "@babel/core": "^7.10.3", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-private-methods": "^7.10.1", "@babel/plugin-transform-modules-amd": "^7.9.6", "@babel/polyfill": "^7.8.7", - "@babel/preset-env": "^7.10.2", - "autoprefixer": "^9.8.0", + "@babel/preset-env": "^7.10.3", + "autoprefixer": "^9.8.2", "babel-eslint": "^11.0.0-beta.2", "babel-loader": "^8.0.6", "browser-sync": "^2.26.7", "copy-webpack-plugin": "^5.1.1", - "css-loader": "^3.4.2", + "css-loader": "^3.6.0", "cssnano": "^4.1.10", "del": "^5.1.0", "eslint": "^6.8.0", "eslint-plugin-compat": "^3.5.1", "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.20.2", + "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", @@ -44,10 +44,10 @@ "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", + "stylelint-order": "^4.1.0", "webpack": "^4.41.5", "webpack-merge": "^4.2.2", "webpack-stream": "^5.2.1" @@ -60,26 +60,26 @@ "date-fns": "^2.14.0", "document-register-element": "^1.14.3", "epubjs": "^0.3.85", - "fast-text-encoding": "^1.0.1", + "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", "headroom.js": "^0.11.0", "hls.js": "^0.13.1", "howler": "^2.2.0", "intersection-observer": "^0.10.0", - "jellyfin-apiclient": "^1.2.0", + "jellyfin-apiclient": "^1.2.2", "jellyfin-noto": "https://github.com/jellyfin/jellyfin-noto", "jquery": "^3.5.1", - "jstree": "^3.3.7", + "jstree": "^3.3.10", "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.12", + "shaka-player": "^3.0.1", "sortablejs": "^1.10.2", - "swiper": "^5.4.1", + "swiper": "^5.4.5", "webcomponents.js": "^0.7.24", "whatwg-fetch": "^3.0.0" }, @@ -90,14 +90,22 @@ "overrides": [ { "test": [ + "src/components/accessSchedule/accessSchedule.js", "src/components/actionSheet/actionSheet.js", "src/components/autoFocuser.js", "src/components/cardbuilder/cardBuilder.js", + "src/components/cardbuilder/chaptercardbuilder.js", + "src/components/cardbuilder/peoplecardbuilder.js", + "src/components/collectionEditor/collectionEditor.js", + "src/components/dialog/dialog.js", + "src/components/dialogHelper/dialogHelper.js", + "src/components/channelMapper/channelMapper.js", "src/components/images/imageLoader.js", "src/components/indicators/indicators.js", "src/components/lazyLoader/lazyLoaderIntersectionObserver.js", "src/components/mediaLibraryCreator/mediaLibraryCreator.js", "src/components/mediaLibraryEditor/mediaLibraryEditor.js", + "src/components/listview/listview.js", "src/components/playback/brightnessosd.js", "src/components/playback/mediasession.js", "src/components/playback/nowplayinghelper.js", @@ -107,15 +115,19 @@ "src/components/playback/playmethodhelper.js", "src/components/playback/remotecontrolautoplay.js", "src/components/playback/volumeosd.js", + "src/components/playlisteditor/playlisteditor.js", "src/components/playmenu.js", "src/components/sanatizefilename.js", "src/components/scrollManager.js", - "src/components/syncplay/groupSelectionMenu.js", - "src/components/syncplay/playbackPermissionManager.js", - "src/components/syncplay/syncPlayManager.js", - "src/components/syncplay/timeSyncManager.js", + "src/components/shortcuts.js", + "src/components/syncPlay/groupSelectionMenu.js", + "src/components/syncPlay/playbackPermissionManager.js", + "src/components/syncPlay/syncPlayManager.js", + "src/components/syncPlay/timeSyncManager.js", + "src/controllers/dashboard/logs.js", + "src/controllers/dashboard/plugins/repositories.js", "src/plugins/bookPlayer/plugin.js", - "src/plugins/bookPlayer/tableOfContent.js", + "src/plugins/bookPlayer/tableOfContents.js", "src/plugins/photoPlayer/plugin.js", "src/scripts/deleteHelper.js", "src/scripts/dfnshelper.js", @@ -124,6 +136,9 @@ "src/scripts/filesystem.js", "src/scripts/imagehelper.js", "src/scripts/inputManager.js", + "src/plugins/backdropScreensaver/plugin.js", + "src/components/filterdialog/filterdialog.js", + "src/components/fetchhelper.js", "src/scripts/keyboardNavigation.js", "src/scripts/settings/appSettings.js", "src/scripts/settings/userSettings.js", @@ -157,6 +172,7 @@ "prepare": "gulp --production", "build:development": "gulp --development", "build:production": "gulp --production", + "build:standalone": "gulp standalone --development", "lint": "eslint \".\"", "stylelint": "stylelint \"src/**/*.css\"" } diff --git a/src/addserver.html b/src/addserver.html index 02850fffb8..d25a8eef98 100644 --- a/src/addserver.html +++ b/src/addserver.html @@ -1,6 +1,6 @@
-
+

${HeaderConnectToServer}

diff --git a/src/assets/css/librarybrowser.css b/src/assets/css/librarybrowser.css index 494a6c523d..88598fb94d 100644 --- a/src/assets/css/librarybrowser.css +++ b/src/assets/css/librarybrowser.css @@ -597,6 +597,7 @@ .detailImageContainer { position: relative; margin-top: -25vh; + margin-bottom: 10vh; float: left; width: 25vw; z-index: 3; @@ -641,7 +642,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 { @@ -797,9 +799,9 @@ div.itemDetailGalleryLink.defaultCardBackground { } .detailImageProgressContainer { - position: absolute; bottom: 0; - width: 22.786458333333332vw; + margin-top: -0.4vw; + width: 100%; } .detailButton-text { diff --git a/src/assets/css/site.css b/src/assets/css/site.css index d489f77f01..9fbd8a4fca 100644 --- a/src/assets/css/site.css +++ b/src/assets/css/site.css @@ -121,6 +121,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; } diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js index 768e310593..166460a025 100644 --- a/src/components/accessSchedule/accessSchedule.js +++ b/src/components/accessSchedule/accessSchedule.js @@ -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,25 +24,25 @@ define(['dialogHelper', 'datetime', 'globalize', 'emby-select', 'paper-icon-butt } function populateHours(context) { - var html = ''; + let html = ''; - for (var i = 0; i < 24; i++) { - html += ''; + for (let i = 0; i < 24; i++) { + html += ``; } - html += ''; + html += ``; 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 @@ -46,44 +57,42 @@ 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 () { - dialogHelper.close(dlg); - }); - dlg.querySelector('form').addEventListener('submit', function (event) { - submitSchedule(dlg, options); - event.preventDefault(); - return false; - }); - }; - - xhr.send(); + export function show(options) { + return new Promise((resolve, reject) => { + // TODO: remove require + require(['text!./components/accessSchedule/accessSchedule.template.html'], template => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + size: 'small' + }); + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateDocument(template); + dlg.innerHTML = html; + populateHours(dlg); + loadSchedule(dlg, options.schedule); + dialogHelper.open(dlg); + dlg.addEventListener('close', () => { + if (dlg.submitted) { + resolve(options.schedule); + } else { + reject(); + } + }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + dlg.querySelector('form').addEventListener('submit', event => { + submitSchedule(dlg, options); + event.preventDefault(); + return false; + }); }); - } - }; -}); + }); + } + +/* eslint-enable indent */ + +export default { + show: show +}; diff --git a/src/components/alert.js b/src/components/alert.js index 8a37ac1845..97b580f8f6 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -31,7 +31,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(); } diff --git a/src/components/cardbuilder/card.css b/src/components/cardbuilder/card.css index c24fcf6ba6..d77fe5660c 100644 --- a/src/components/cardbuilder/card.css +++ b/src/components/cardbuilder/card.css @@ -192,9 +192,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 +208,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 +221,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); } diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index e540a40211..52d5ed202a 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -368,9 +368,7 @@ import 'programStyles'; let apiClient; let lastServerId; - for (let i = 0; i < items.length; i++) { - - let item = items[i]; + for (const [i, item] of items.entries()) { let serverId = item.ServerId || options.serverId; if (serverId !== lastServerId) { @@ -541,7 +539,7 @@ import 'programStyles'; imgType = 'Backdrop'; imgTag = item.ParentBackdropImageTags[0]; itemId = item.ParentBackdropItemId; - } else if (item.ImageTags && item.ImageTags.Primary) { + } 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; @@ -556,7 +554,10 @@ 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; @@ -577,10 +578,6 @@ import 'programStyles'; imgType = 'Primary'; imgTag = item.ParentPrimaryImageTag; itemId = item.ParentPrimaryImageItemId; - } else if (item.SeriesPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.SeriesPrimaryImageTag; - itemId = item.SeriesId; } else if (item.AlbumId && item.AlbumPrimaryImageTag) { imgType = 'Primary'; imgTag = item.AlbumPrimaryImageTag; @@ -1370,9 +1367,6 @@ import 'programStyles'; let cardScalableClose = ''; let cardContentClass = 'cardContent'; - if (!options.cardLayout) { - cardContentClass += ' cardContent-shadow'; - } let blurhashAttrib = ''; if (blurhash && blurhash.length > 0) { @@ -1380,21 +1374,20 @@ import 'programStyles'; } if (layoutManager.tv) { - // Don't use the IMG tag with safari because it puts a white border around it cardImageContainerOpen = imgUrl ? ('
') : ('
'); cardImageContainerClose = '
'; } else { // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? (''; } let cardScalableClass = 'cardScalable'; - cardImageContainerOpen = '
' + cardImageContainerOpen; + cardImageContainerOpen = '
' + cardImageContainerOpen; cardBoxClose = '
'; cardScalableClose = '
'; diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index eae60574b3..c6ee9ba3c5 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -1,13 +1,23 @@ -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,12 +27,12 @@ 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) { @@ -31,24 +41,24 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse } } - 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); + const apiClient = connectionManager.getApiClient(item.ServerId); - for (var i = 0, length = chapters.length; i < length; i++) { + for (let i = 0, length = chapters.length; i < length; i++) { if (options.rows && itemsInRow === 0) { html += '
'; } - var chapter = chapters[i]; + const chapter = chapters[i]; html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); itemsInRow++; @@ -62,50 +72,50 @@ define(['datetime', 'imageLoader', 'connectionManager', 'layoutManager', 'browse return html; } - function getImgUrl(item, chapter, index, maxWidth, apiClient) { + function getImgUrl({Id}, {ImageTag}, index, maxWidth, apiClient) { - if (chapter.ImageTag) { + if (ImageTag) { - return apiClient.getScaledImageUrl(item.Id, { + return apiClient.getScaledImageUrl(Id, { maxWidth: maxWidth * 2, - tag: chapter.ImageTag, + 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) { - var imgUrl = getImgUrl(item, chapter, index, options.width || 400, apiClient); + const imgUrl = getImgUrl(item, chapter, index, 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 ? ('
') : ('
'); + 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 ? (`
`) : (`
`); if (!imgUrl) { cardImageContainer += ''; } - var nameHtml = ''; - nameHtml += '
' + chapter.Name + '
'; - nameHtml += '
' + datetime.getDisplayRunningTime(chapter.StartPositionTicks) + '
'; + let nameHtml = ''; + nameHtml += `
${chapter.Name}
`; + nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; - var cardBoxCssClass = 'cardBox'; - var cardScalableClass = 'cardScalable'; + const cardBoxCssClass = 'cardBox'; + const cardScalableClass = 'cardScalable'; - var html = '
'; + const html = `
`; return html; } - function buildChapterCards(item, chapters, options) { + export function buildChapterCards(item, chapters, options) { if (options.parentContainer) { // Abort if the container has been disposed @@ -121,15 +131,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 +}; -}); diff --git a/src/components/cardbuilder/peoplecardbuilder.js b/src/components/cardbuilder/peoplecardbuilder.js index 5d34d29e6e..3b9a26a704 100644 --- a/src/components/cardbuilder/peoplecardbuilder.js +++ b/src/components/cardbuilder/peoplecardbuilder.js @@ -1,7 +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, @@ -15,8 +21,8 @@ define(['cardBuilder'], function (cardBuilder) { cardBuilder.buildCards(items, options); } - return { - buildPeopleCards: buildPeopleCards - }; + /* eslint-enable indent */ -}); +export default { + buildPeopleCards: buildPeopleCards +}; diff --git a/src/components/channelMapper/channelMapper.js b/src/components/channelMapper/channelMapper.js index f2ad88e713..89d085c185 100644 --- a/src/components/channelMapper/channelMapper.js +++ b/src/components/channelMapper/channelMapper.js @@ -1,10 +1,21 @@ -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'), @@ -14,8 +25,8 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act 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 += '
'; html += ''; html += '
'; @@ -73,16 +84,16 @@ define(['dom', 'dialogHelper', 'loading', 'connectionManager', 'globalize', 'act html += '
'; html += '
'; - html += ''; + html += ``; return html += '
'; } function getEditorHtml() { - var html = ''; + let html = ''; html += '
'; html += '
'; html += ''; - html += '

' + globalize.translate('HeaderChannels') + '

'; + html += `

${globalize.translate('HeaderChannels')}

`; html += '
'; html += '
'; html += ''; @@ -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 += '
'; html += ''; html += '

'; @@ -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); }); }; - }; -}); + } +} diff --git a/src/components/collectionEditor/collectionEditor.js b/src/components/collectionEditor/collectionEditor.js index 46b0640305..18cc0d311f 100644 --- a/src/components/collectionEditor/collectionEditor.js +++ b/src/components/collectionEditor/collectionEditor.js @@ -1,16 +1,32 @@ -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 appHost from 'apphost'; +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); @@ -24,7 +40,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,11 +52,11 @@ 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); @@ -56,7 +72,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio 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 +81,14 @@ 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')); }); }); @@ -86,11 +102,11 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio 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 +114,16 @@ 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 => { - var html = ''; + let html = ''; - html += ''; + html += ``; - html += result.Items.map(function (i) { + html += result.Items.map(i => { - return ''; + return ``; }); select.innerHTML = html; @@ -120,7 +136,7 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio function getEditorHtml() { - var html = ''; + let html = ''; html += '
'; html += '
'; @@ -134,27 +150,27 @@ define(['dom', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'connectio html += '
'; html += '
'; html += '
'; - html += ''; + html += ``; html += '
'; html += '
'; html += '
'; html += '
'; - html += ''; - html += '
' + globalize.translate('NewCollectionNameExample') + '
'; + html += ``; + html += `
${globalize.translate('NewCollectionNameExample')}
`; html += '
'; html += ''; // newCollectionInfo html += '
'; html += '
'; - html += ''; + html += ``; html += '
'; html += ''; @@ -188,7 +204,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 +212,77 @@ 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 += '
'; - html += ''; - html += '

'; - html += title; - html += '

'; - - if (appHost.supports('externallinks')) { - html += '' + globalize.translate('Help') + ''; - } - - html += '
'; - - 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 += '
'; + html += ''; + html += '

'; + html += title; + html += '

'; + + if (appHost.supports('externallinks')) { + html += `${globalize.translate('Help')}`; } - return Promise.reject(); - }); - }; + html += '
'; - return CollectionEditor; -}); + html += getEditorHtml(); + + dlg.innerHTML = html; + + initEditor(dlg, items); + + dlg.querySelector('.btnCancel').addEventListener('click', () => { + + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + return dialogHelper.open(dlg).then(() => { + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (dlg.submitted) { + return Promise.resolve(); + } + + return Promise.reject(); + }); + } + } + +/* eslint-enable indent */ +export default showEditor; diff --git a/src/components/confirm/confirm.js b/src/components/confirm/confirm.js index 517d6fa9be..6b9252b5f5 100644 --- a/src/components/confirm/confirm.js +++ b/src/components/confirm/confirm.js @@ -53,7 +53,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(); } diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index cfb5821b38..937e96d297 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -1,20 +1,31 @@ -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 +33,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,7 +41,7 @@ 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'); } @@ -44,27 +55,27 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're 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++) { - var item = options.buttons[i]; - var autoFocus = i === 0 ? ' autofocus' : ''; + const item = options.buttons[i]; + const 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 +86,10 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom'; } - html += ''; + html += ``; if (item.description) { - html += '
' + item.description + '
'; + html += `
${item.description}
`; } } @@ -88,18 +99,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 +124,9 @@ define(['dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 're }); } - return function (text, title) { + export async function show(text, title) { - var options; + let options; if (typeof text === 'string') { options = { title: title, @@ -125,10 +136,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 +}; diff --git a/src/components/dialog/dialog.template.html b/src/components/dialog/dialog.template.html index bee0ef7f73..6d4310c0f4 100644 --- a/src/components/dialog/dialog.template.html +++ b/src/components/dialog/dialog.template.html @@ -4,12 +4,8 @@
- -
- -
+
-
-
+
diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index 8b08cde678..ca7c94a416 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -1,7 +1,15 @@ -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() { @@ -25,7 +33,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', } function tryRemoveElement(elem) { - var parentNode = elem.parentNode; + const parentNode = elem.parentNode; if (parentNode) { // Seeing crashes in edge webview @@ -39,14 +47,14 @@ 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); @@ -84,7 +92,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', } if (!self.closedByBack && isHistoryEnabled(dlg)) { - var state = history.state || {}; + const state = history.state || {}; if (state.dialogId === hash) { history.back(); } @@ -97,7 +105,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 +116,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 +126,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 +149,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 { @@ -151,10 +159,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 +170,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 +178,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,26 +192,26 @@ 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) { + return new Promise((resolve, reject) => { - new DialogHashHandler(dlg, 'dlg' + new Date().getTime(), resolve); + new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve); }); } @@ -213,7 +221,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', return !dlg.classList.contains('hide'); } - function close(dlg) { + export function close(dlg) { if (isOpened(dlg)) { if (isHistoryEnabled(dlg)) { @@ -233,7 +241,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', cancelable: false })); - var onAnimationFinish = function () { + const onAnimationFinish = () => { focusManager.popScope(dlg); dlg.classList.add('hide'); @@ -249,7 +257,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', function animateDialogOpen(dlg) { - var onAnimationFinish = function () { + const onAnimationFinish = () => { focusManager.pushScope(dlg); if (dlg.getAttribute('data-autofocus') === 'true') { @@ -264,7 +272,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', if (enableAnimation()) { - var onFinish = function () { + const onFinish = () => { dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, { once: true }); @@ -283,24 +291,24 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 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,7 +326,7 @@ 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) { @@ -343,7 +351,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', function removeBackdrop(dlg) { - var backdrop = dlg.backdrop; + const backdrop = dlg.backdrop; if (!backdrop) { return; @@ -351,7 +359,7 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', dlg.backdrop = null; - var onAnimationFinish = function () { + const onAnimationFinish = () => { tryRemoveElement(backdrop); }; @@ -368,20 +376,20 @@ 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'); @@ -406,17 +414,17 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', dlg.setAttribute('data-autofocus', 'true'); } - var defaultEntryAnimation; - var defaultExitAnimation; + let defaultEntryAnimation; + let defaultExitAnimation; defaultEntryAnimation = 'scaleup'; defaultExitAnimation = 'scaledown'; - var entryAnimation = options.entryAnimation || defaultEntryAnimation; - var exitAnimation = options.exitAnimation || defaultExitAnimation; + 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,7 +469,7 @@ 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()) { @@ -469,16 +477,16 @@ define(['appRouter', 'focusManager', 'browser', 'layoutManager', 'inputManager', 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 +496,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 +}; diff --git a/src/components/fetchhelper.js b/src/components/fetchhelper.js index 08fe803ecf..f626abefee 100644 --- a/src/components/fetchhelper.js +++ b/src/components/fetchhelper.js @@ -1,21 +1,19 @@ -define([], function () { - 'use strict'; +/* eslint-disable indent */ + export function getFetchPromise(request) { - function getFetchPromise(request) { - - var headers = request.headers || {}; + 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) { @@ -33,12 +31,12 @@ define([], function () { 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}`; } } @@ -51,11 +49,11 @@ 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 +61,47 @@ 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} + * @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 +110,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 */ diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js index 1dfd04c679..041fa82e76 100644 --- a/src/components/filterdialog/filterdialog.js +++ b/src/components/filterdialog/filterdialog.js @@ -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 += '
'; html += items.map(function (filter) { - var itemHtml = ''; - var checkedHtml = isCheckedFn(filter) ? ' checked' : ''; + let itemHtml = ''; + const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; itemHtml += ''; 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 }); @@ -380,18 +413,21 @@ define(['dom', 'dialogHelper', 'globalize', 'connectionManager', 'events', 'brow dlg.classList.add('formDialog'); dlg.classList.add('filterDialog'); dlg.innerHTML = globalize.translateDocument(template); - setVisibility(dlg, options); + 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; diff --git a/src/components/formdialog.css b/src/components/formdialog.css index 94695f4865..d7cb162e8c 100644 --- a/src/components/formdialog.css +++ b/src/components/formdialog.css @@ -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; } diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 535f1cd687..bba4b9db1b 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -168,7 +168,7 @@ define(['connectionManager', 'cardBuilder', 'appSettings', 'dom', 'apphost', 'la } function getPortraitShape() { - return enableScrollX() ? 'autooverflow' : 'auto'; + return enableScrollX() ? 'overflowPortrait' : 'portrait'; } function getLibraryButtonsHtml(items) { @@ -254,7 +254,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', diff --git a/src/components/imageUploader/imageUploader.js b/src/components/imageUploader/imageUploader.js index e078a9fa30..417bd94e11 100644 --- a/src/components/imageUploader/imageUploader.js +++ b/src/components/imageUploader/imageUploader.js @@ -58,6 +58,7 @@ define(['dialogHelper', 'connectionManager', 'dom', 'loading', 'scrollHelper', ' var html = [''].join(''); page.querySelector('#imageOutput').innerHTML = html; + page.querySelector('#dropImageText').classList.add('hide'); page.querySelector('#fldUpload').classList.remove('hide'); }; })(file); diff --git a/src/components/imageUploader/imageUploader.template.html b/src/components/imageUploader/imageUploader.template.html index 9c6a139c04..6a9e21ff15 100644 --- a/src/components/imageUploader/imageUploader.template.html +++ b/src/components/imageUploader/imageUploader.template.html @@ -20,7 +20,7 @@
-
${LabelDropImageHere}
+
${LabelDropImageHere}
diff --git a/src/components/images/style.css b/src/components/images/style.css index a709f732c5..2b09da2da4 100644 --- a/src/components/images/style.css +++ b/src/components/images/style.css @@ -1,11 +1,11 @@ .lazy-image-fadein { opacity: 1; - transition: opacity 0.7s; + transition: opacity 0.5s; } .lazy-image-fadein-fast { opacity: 1; - transition: opacity 0.2s; + transition: opacity 0.1s; } .lazy-hidden { @@ -29,4 +29,5 @@ width: 100%; height: 100%; z-index: 100; + pointer-events: none; } diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index f258f5fe4b..584b25f0e4 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -319,7 +319,7 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter', switch (id) { case 'addtocollection': require(['collectionEditor'], function (collectionEditor) { - new collectionEditor().show({ + new collectionEditor.showEditor({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); @@ -327,7 +327,7 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter', break; case 'addtoplaylist': require(['playlistEditor'], function (playlistEditor) { - new playlistEditor().show({ + new playlistEditor.showEditor({ items: [itemId], serverId: serverId }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); @@ -403,7 +403,7 @@ define(['apphost', 'globalize', 'connectionManager', 'itemHelper', 'appRouter', break; case 'moremediainfo': require(['itemMediaInfo'], function (itemMediaInfo) { - itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); }); break; case 'refresh': diff --git a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js index 8cf5b9cd0a..7751fe8bc2 100644 --- a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js +++ b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js @@ -11,9 +11,9 @@ (entries) => { entries.forEach(entry => { callback(entry); - }, - {rootMargin: '50%'}); - }); + }); + }, + {rootMargin: '25%'}); this.observer = observer; } diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index 7bafc925b2..692d74d38b 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -1,5 +1,20 @@ -define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutManager', 'globalize', 'datetime', 'apphost', 'css!./listview', 'emby-ratingbutton', 'emby-playstatebutton'], function (itemHelper, mediaInfo, indicators, connectionManager, layoutManager, globalize, datetime, appHost) { - 'use strict'; +/* eslint-disable indent */ + +/** + * Module for display list view. + * @module components/listview/listview + */ + +import itemHelper from 'itemHelper'; +import mediaInfo from 'mediaInfo'; +import indicators from 'indicators'; +import connectionManager from 'connectionManager'; +import layoutManager from 'layoutManager'; +import globalize from 'globalize'; +import datetime from 'datetime'; +import 'css!./listview'; +import 'emby-ratingbutton'; +import 'emby-playstatebutton'; function getIndex(item, options) { @@ -8,9 +23,9 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); } - var sortBy = (options.sortBy || '').toLowerCase(); - var code; - var name; + const sortBy = (options.sortBy || '').toLowerCase(); + let code; + let name; if (sortBy.indexOf('sortname') === 0) { @@ -69,10 +84,10 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan function getImageUrl(item, width) { - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); let itemId; - var options = { + const options = { maxWidth: width * 2, type: 'Primary' }; @@ -80,9 +95,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (item.ImageTags && item.ImageTags.Primary) { options.tag = item.ImageTags.Primary; itemId = item.Id; - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { options.tag = item.AlbumPrimaryImageTag; itemId = item.AlbumId; } else if (item.SeriesId && item.SeriesPrimaryImageTag) { @@ -92,18 +105,20 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan options.tag = item.ParentPrimaryImageTag; itemId = item.ParentPrimaryImageItemId; } + let blurHashes = item.ImageBlurHashes || {}; let blurhashstr = (blurHashes[options.type] || {})[options.tag]; if (itemId) { return { url: apiClient.getScaledImageUrl(itemId, options), blurhash: blurhashstr }; } + return null; } function getChannelImageUrl(item, width) { - var apiClient = connectionManager.getApiClient(item.ServerId); - var options = { + const apiClient = connectionManager.getApiClient(item.ServerId); + const options = { maxWidth: width * 2, type: 'Primary' }; @@ -121,13 +136,13 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan function getTextLinesHtml(textlines, isLargeStyle) { - var html = ''; + let html = ''; - var largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; + const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; - for (var i = 0, length = textlines.length; i < length; i++) { + for (let i = 0, length = textlines.length; i < length; i++) { - var text = textlines[i]; + const text = textlines[i]; if (!text) { continue; @@ -135,7 +150,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (i === 0) { if (isLargeStyle) { - html += '<' + largeTitleTagName + ' class="listItemBodyText">'; + html += `<${largeTitleTagName} class="listItemBodyText">`; } else { html += '
'; } @@ -144,7 +159,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } html += (textlines[i] || ' '); if (i === 0 && isLargeStyle) { - html += ''; + html += ``; } else { html += '
'; } @@ -155,13 +170,13 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan function getRightButtonsHtml(options) { - var html = ''; + let html = ''; - for (var i = 0, length = options.rightButtons.length; i < length; i++) { + for (let i = 0, length = options.rightButtons.length; i < length; i++) { - var button = options.rightButtons[i]; + const button = options.rightButtons[i]; - html += ''; + html += ``; } return html; @@ -171,34 +186,34 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan return item.Id; } - function getListViewHtml(options) { + export function getListViewHtml(options) { - var items = options.items; + const items = options.items; - var groupTitle = ''; - var action = options.action || 'link'; + let groupTitle = ''; + const action = options.action || 'link'; - var isLargeStyle = options.imageSize === 'large'; - var enableOverview = options.enableOverview; + const isLargeStyle = options.imageSize === 'large'; + const enableOverview = options.enableOverview; - var clickEntireItem = layoutManager.tv ? true : false; - var outerTagName = clickEntireItem ? 'button' : 'div'; - var enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; + const clickEntireItem = layoutManager.tv ? true : false; + const outerTagName = clickEntireItem ? 'button' : 'div'; + const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; - var outerHtml = ''; + let outerHtml = ''; - var enableContentWrapper = options.enableOverview && !layoutManager.tv; - var containerAlbumArtistIds = (options.containerAlbumArtists || []).map(getId); + const enableContentWrapper = options.enableOverview && !layoutManager.tv; + const containerAlbumArtistIds = (options.containerAlbumArtists || []).map(getId); - for (var i = 0, length = items.length; i < length; i++) { + for (let i = 0, length = items.length; i < length; i++) { - var item = items[i]; + const item = items[i]; - var html = ''; + let html = ''; if (options.showIndex) { - var itemGroupTitle = getIndex(item, options); + const itemGroupTitle = getIndex(item, options); if (itemGroupTitle !== groupTitle) { @@ -220,7 +235,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } - var cssClass = 'listItem'; + let cssClass = 'listItem'; if (options.border || (options.highlight !== false && !layoutManager.tv)) { cssClass += ' listItem-border'; @@ -234,28 +249,28 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan cssClass += ' listItem-focusscale'; } - var downloadWidth = 80; + let downloadWidth = 80; if (isLargeStyle) { cssClass += ' listItem-largeImage'; downloadWidth = 500; } - var playlistItemId = item.PlaylistItemId ? (' data-playlistitemid="' + item.PlaylistItemId + '"') : ''; + const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; - var positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (' data-positionticks="' + item.UserData.PlaybackPositionTicks + '"') : ''; - var collectionIdData = options.collectionId ? (' data-collectionid="' + options.collectionId + '"') : ''; - var playlistIdData = options.playlistId ? (' data-playlistid="' + options.playlistId + '"') : ''; - var mediaTypeData = item.MediaType ? (' data-mediatype="' + item.MediaType + '"') : ''; - var collectionTypeData = item.CollectionType ? (' data-collectiontype="' + item.CollectionType + '"') : ''; - var channelIdData = item.ChannelId ? (' data-channelid="' + item.ChannelId + '"') : ''; + const positionTicksData = item.UserData && item.UserData.PlaybackPositionTicks ? (` data-positionticks="${item.UserData.PlaybackPositionTicks}"`) : ''; + const collectionIdData = options.collectionId ? (` data-collectionid="${options.collectionId}"`) : ''; + const playlistIdData = options.playlistId ? (` data-playlistid="${options.playlistId}"`) : ''; + const mediaTypeData = item.MediaType ? (` data-mediatype="${item.MediaType}"`) : ''; + const collectionTypeData = item.CollectionType ? (` data-collectiontype="${item.CollectionType}"`) : ''; + const channelIdData = item.ChannelId ? (` data-channelid="${item.ChannelId}"`) : ''; if (enableContentWrapper) { cssClass += ' listItem-withContentWrapper'; } - html += '<' + outerTagName + ' class="' + cssClass + '"' + playlistItemId + ' data-action="' + action + '" data-isfolder="' + item.IsFolder + '" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + positionTicksData + collectionIdData + playlistIdData + '>'; + html += `<${outerTagName} class="${cssClass}"${playlistItemId} data-action="${action}" data-isfolder="${item.IsFolder}" data-id="${item.Id}" data-serverid="${item.ServerId}" data-type="${item.Type}"${mediaTypeData}${collectionTypeData}${channelIdData}${positionTicksData}${collectionIdData}${playlistIdData}>`; if (enableContentWrapper) { @@ -278,37 +293,37 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan imageClass += ' listItemImage-large-tv'; } - var playOnImageClick = options.imagePlayButton && !layoutManager.tv; + const playOnImageClick = options.imagePlayButton && !layoutManager.tv; if (!clickEntireItem) { imageClass += ' itemAction'; } - var imageAction = playOnImageClick ? 'resume' : action; + const imageAction = playOnImageClick ? 'resume' : action; let blurhashAttrib = ''; if (blurhash && blurhash.length > 0) { - blurhashAttrib = 'data-blurhash="' + blurhash + '"'; + blurhashAttrib = `data-blurhash="${blurhash}"`; } if (imgUrl) { - html += '
'; + html += `
`; } else { - html += '
'; + html += `
`; } - var indicatorsHtml = ''; + let indicatorsHtml = ''; indicatorsHtml += indicators.getPlayedIndicatorHtml(item); if (indicatorsHtml) { - html += '
' + indicatorsHtml + '
'; + html += `
${indicatorsHtml}
`; } if (playOnImageClick) { html += ''; } - var progressHtml = indicators.getProgressBarHtml(item, { + const progressHtml = indicators.getProgressBarHtml(item, { containerClass: 'listItemProgressBar' }); @@ -325,7 +340,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan html += '
'; } - var textlines = []; + const textlines = []; if (options.showProgramDateTime) { textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), { @@ -348,7 +363,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } - var parentTitle = null; + let parentTitle = null; if (options.showParentTitle) { if (item.Type === 'Episode') { @@ -358,12 +373,12 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } - var displayName = itemHelper.getDisplayName(item, { + let displayName = itemHelper.getDisplayName(item, { includeParentInfo: options.includeParentInfoInTitle }); if (options.showIndexNumber && item.IndexNumber != null) { - displayName = item.IndexNumber + '. ' + displayName; + displayName = `${item.IndexNumber}. ${displayName}`; } if (options.showParentTitle && options.parentTitleWithTitle) { @@ -394,14 +409,14 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } else { - var showArtist = options.artist === true; - var artistItems = item.ArtistItems; + let showArtist = options.artist === true; + const artistItems = item.ArtistItems; if (!showArtist && options.artist !== false) { if (!artistItems || !artistItems.length) { showArtist = true; - } else if (artistItems.length > 1 || containerAlbumArtistIds.indexOf(artistItems[0].Id) === -1) { + } else if (artistItems.length > 1 || !containerAlbumArtistIds.includes(artistItems[0].Id)) { showArtist = true; } } @@ -409,7 +424,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (showArtist) { if (artistItems && item.Type !== 'MusicAlbum') { - textlines.push(artistItems.map(function (a) { + textlines.push(artistItems.map(a => { return a.Name; }).join(', ')); } @@ -432,7 +447,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan cssClass += ' listItemBody-noleftpadding'; } - html += '
'; + html += `
`; const moreIcon = 'more_vert'; @@ -441,14 +456,16 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (options.mediaInfo !== false) { if (!enableSideMediaInfo) { - var mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; + const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; - html += '
' + mediaInfo.getPrimaryMediaInfoHtml(item, { + html += `
`; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { episodeTitle: false, originalAirDate: false, subtitles: false - }) + '
'; + }); + html += '
'; } } @@ -462,7 +479,8 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (options.mediaInfo !== false) { if (enableSideMediaInfo) { - html += '
' + mediaInfo.getPrimaryMediaInfoHtml(item, { + html += '
'; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { year: false, container: false, @@ -470,7 +488,8 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan criticRating: false, endsAt: false - }) + '
'; + }); + html += '
'; } } @@ -487,7 +506,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } if (options.moreButton !== false) { - html += ''; + html += ``; } if (options.infoButton) { @@ -500,15 +519,15 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan if (options.enableUserDataButtons !== false) { - var userData = item.UserData || {}; - var likes = userData.Likes == null ? '' : userData.Likes; + const userData = item.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; if (itemHelper.canMarkPlayed(item)) { - html += ''; + html += ``; } if (itemHelper.canRate(item)) { - html += ''; + html += ``; } } } @@ -524,7 +543,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan } } - html += ''; + html += ``; outerHtml += html; } @@ -532,7 +551,7 @@ define(['itemHelper', 'mediaInfo', 'indicators', 'connectionManager', 'layoutMan return outerHtml; } - return { - getListViewHtml: getListViewHtml - }; -}); +/* eslint-enable indent */ +export default { + getListViewHtml: getListViewHtml +}; diff --git a/src/components/loadingDialog/loadingDialog.js b/src/components/loadingDialog/loadingDialog.js deleted file mode 100644 index bd6a80fd5b..0000000000 --- a/src/components/loadingDialog/loadingDialog.js +++ /dev/null @@ -1,92 +0,0 @@ -define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) { - 'use strict'; - - function showDialog(instance, options, template) { - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - var enableTvLayout = layoutManager.tv; - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - var configuredButtons = []; - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.classList.add('align-items-center'); - dlg.classList.add('justify-items-center'); - - var formDialogContent = dlg.querySelector('.formDialogContent'); - formDialogContent.style['flex-grow'] = 'initial'; - formDialogContent.style['max-width'] = '50%'; - formDialogContent.style['max-height'] = '60%'; - - if (enableTvLayout) { - scrollHelper.centerFocus.on(formDialogContent, false); - dlg.querySelector('.formDialogHeader').style.marginTop = '15%'; - } else { - dlg.classList.add('dialog-fullscreen-lowres'); - } - - //dlg.querySelector('.btnCancel').addEventListener('click', function (e) { - // dialogHelper.close(dlg); - //}); - - dlg.querySelector('.formDialogHeaderTitle').innerHTML = options.title; - - dlg.querySelector('.text').innerHTML = options.text; - - instance.dlg = dlg; - - return dialogHelper.open(dlg).then(function () { - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - loading.hide(); - }); - } - - function LoadingDialog(options) { - this.options = options; - } - - LoadingDialog.prototype.show = function () { - var instance = this; - loading.show(); - - return new Promise(function (resolve, reject) { - require(['text!./../dialog/dialog.template.html'], function (template) { - showDialog(instance, instance.options, template); - resolve(); - }); - }); - }; - - LoadingDialog.prototype.setTitle = function (title) { - }; - - LoadingDialog.prototype.setText = function (text) { - }; - - LoadingDialog.prototype.hide = function () { - if (this.dlg) { - dialogHelper.close(this.dlg); - this.dlg = null; - } - }; - - LoadingDialog.prototype.destroy = function () { - this.dlg = null; - this.options = null; - }; - - return LoadingDialog; -}); diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index c569a7c78c..7de11c42f7 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -273,7 +273,7 @@ define(['datetime', 'globalize', 'appRouter', 'itemHelper', 'indicators', 'mater } } - if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && !showFolderRuntime && options.runtime !== false) { + if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { if (item.Type === 'Audio') { diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index 52f030cd23..61a4ba7f24 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -308,8 +308,8 @@ define(['itemHelper', 'dom', 'layoutManager', 'dialogHelper', 'datetime', 'loadi } }); - context.removeEventListener('submit', onEditorClick); - context.addEventListener('submit', onEditorClick); + context.removeEventListener('click', onEditorClick); + context.addEventListener('click', onEditorClick); var form = context.querySelector('form'); form.removeEventListener('submit', onSubmit); diff --git a/src/components/metadataEditor/personEditor.template.html b/src/components/metadataEditor/personEditor.template.html index 40b29767fa..d2ad6a78d2 100644 --- a/src/components/metadataEditor/personEditor.template.html +++ b/src/components/metadataEditor/personEditor.template.html @@ -7,7 +7,6 @@
-
@@ -23,6 +22,7 @@
+
${LabelPersonRoleHelp}
@@ -33,6 +33,5 @@ ${Save}
-
diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 83982413ae..05a2b68f42 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -255,7 +255,7 @@ define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'glo switch (id) { case 'addtocollection': require(['collectionEditor'], function (collectionEditor) { - new collectionEditor().show({ + new collectionEditor.showEditor({ items: items, serverId: serverId }); @@ -265,7 +265,7 @@ define(['browser', 'appStorage', 'apphost', 'loading', 'connectionManager', 'glo break; case 'playlist': require(['playlistEditor'], function (playlistEditor) { - new playlistEditor().show({ + new playlistEditor.showEditor({ items: items, serverId: serverId }); diff --git a/src/components/packagemanager.js b/src/components/packageManager.js similarity index 100% rename from src/components/packagemanager.js rename to src/components/packageManager.js diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 5eac56b5ce..0f275c88f7 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -241,6 +241,15 @@ import connectionManager from 'connectionManager'; navigator.mediaSession.setActionHandler('seekforward', function () { execute('fastForward'); }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekto', function (object) { + let item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + // Convert to ms + let duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + let wantedTime = object.seekTime * 1000; + playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + }); } events.on(playbackManager, 'playerchange', function () { diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 88d4e0d599..73f07a05f2 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -1907,11 +1907,8 @@ define(['events', 'datetime', 'appSettings', 'itemHelper', 'pluginManager', 'pla // Setting this to true may cause some incorrect sorting Recursive: false, SortBy: options.shuffle ? 'Random' : 'SortName', - MediaTypes: 'Photo,Video', - Limit: 500 - + MediaTypes: 'Photo,Video' }).then(function (result) { - var items = result.Items; var index = items.map(function (i) { diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 56d7142ad3..7b1e915e1f 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -1,13 +1,28 @@ -define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackManager', 'connectionManager', 'userSettings', 'appRouter', 'globalize', 'emby-input', 'paper-icon-button-light', 'emby-select', 'material-icons', 'css!./../formdialog', 'emby-button'], function (dom, shell, dialogHelper, loading, layoutManager, playbackManager, connectionManager, userSettings, appRouter, globalize) { - 'use strict'; +import dom from 'dom'; +import dialogHelper from 'dialogHelper'; +import loading from 'loading'; +import layoutManager from 'layoutManager'; +import playbackManager from 'playbackManager'; +import connectionManager from 'connectionManager'; +import * as userSettings from 'userSettings'; +import appRouter from 'appRouter'; +import globalize from 'globalize'; +import 'emby-input'; +import 'paper-icon-button-light'; +import 'emby-select'; +import 'material-icons'; +import 'css!./../formdialog'; +import 'emby-button'; - var currentServerId; +/* eslint-disable indent */ + + let currentServerId; function onSubmit(e) { - var panel = dom.parentWithClass(this, 'dialog'); + const panel = dom.parentWithClass(this, 'dialog'); - var playlistId = panel.querySelector('#selectPlaylistToAddTo').value; - var apiClient = connectionManager.getApiClient(currentServerId); + const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; + const apiClient = connectionManager.getApiClient(currentServerId); if (playlistId) { userSettings.set('playlisteditor-lastplaylistid', playlistId); @@ -23,7 +38,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan function createPlaylist(apiClient, dlg) { loading.show(); - var url = apiClient.getUrl('Playlists', { + const url = apiClient.getUrl('Playlists', { Name: dlg.querySelector('#txtNewPlaylistName').value, Ids: dlg.querySelector('.fldSelectedItemIds').value || '', userId: apiClient.getCurrentUserId() @@ -34,10 +49,10 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan type: 'POST', 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); redirectToPlaylist(apiClient, id); @@ -49,7 +64,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan } function addToPlaylist(apiClient, dlg, id) { - var itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; + const itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; if (id === 'queue') { playbackManager.queue({ @@ -63,7 +78,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan loading.show(); - var url = apiClient.getUrl('Playlists/' + id + '/Items', { + const url = apiClient.getUrl(`Playlists/${id}/Items`, { Ids: itemIds, userId: apiClient.getCurrentUserId() }); @@ -72,7 +87,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan type: 'POST', url: url - }).then(function () { + }).then(() => { loading.hide(); dlg.submitted = true; @@ -85,36 +100,36 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan } function populatePlaylists(editorOptions, panel) { - var select = panel.querySelector('#selectPlaylistToAddTo'); + const select = panel.querySelector('#selectPlaylistToAddTo'); loading.hide(); panel.querySelector('.newPlaylistInfo').classList.add('hide'); - var options = { + const options = { Recursive: true, IncludeItemTypes: 'Playlist', SortBy: 'SortName', EnableTotalRecordCount: false }; - var apiClient = connectionManager.getApiClient(currentServerId); - apiClient.getItems(apiClient.getCurrentUserId(), options).then(function (result) { - var html = ''; + const apiClient = connectionManager.getApiClient(currentServerId); + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { + let html = ''; if (editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) { - html += ''; + html += ``; } - html += ''; + html += ``; - html += result.Items.map(function (i) { - return ''; + html += result.Items.map(i => { + return ``; }); select.innerHTML = html; - var defaultValue = editorOptions.defaultValue; + let defaultValue = editorOptions.defaultValue; if (!defaultValue) { defaultValue = userSettings.get('playlisteditor-lastplaylistid') || ''; } @@ -132,29 +147,29 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan } function getEditorHtml(items) { - var html = ''; + let html = ''; html += '
'; html += '
'; html += '
'; html += '
'; - var autoFocus = items.length ? ' autofocus' : ''; - html += ''; + let autoFocus = items.length ? ' autofocus' : ''; + html += ``; html += '
'; html += '
'; html += '
'; autoFocus = items.length ? '' : ' autofocus'; - html += ''; + html += ``; html += '
'; // newPlaylistInfo html += '
'; html += '
'; - html += ''; + html += ``; html += '
'; html += ''; @@ -187,7 +202,7 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan } else { content.querySelector('.fldSelectPlaylist').classList.add('hide'); - var selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); + const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); selectPlaylistToAddTo.innerHTML = ''; selectPlaylistToAddTo.value = ''; triggerChange(selectPlaylistToAddTo); @@ -195,72 +210,70 @@ define(['dom', 'shell', 'dialogHelper', 'loading', 'layoutManager', 'playbackMan } 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 PlaylistEditor() { + export class showEditor { + constructor(options) { + const items = options.items || {}; + currentServerId = options.serverId; + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + const title = globalize.translate('HeaderAddToPlaylist'); + + html += '
'; + html += ''; + html += '

'; + html += title; + html += '

'; + + html += '
'; + + html += getEditorHtml(items); + + dlg.innerHTML = html; + + initEditor(dlg, options, items); + + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + return dialogHelper.open(dlg).then(() => { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (dlg.submitted) { + return Promise.resolve(); + } + + return Promise.reject(); + }); + } } - PlaylistEditor.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 = globalize.translate('HeaderAddToPlaylist'); - - html += '
'; - html += ''; - html += '

'; - html += title; - html += '

'; - - html += '
'; - - html += getEditorHtml(items); - - dlg.innerHTML = html; - - initEditor(dlg, options, items); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - return dialogHelper.open(dlg).then(function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (dlg.submitted) { - return Promise.resolve(); - } - - return Promise.reject(); - }); - }; - - return PlaylistEditor; -}); +/* eslint-enable indent */ +export default showEditor; diff --git a/src/components/prompt/prompt.template.html b/src/components/prompt/prompt.template.html index 981fa9f102..a07629ae7e 100644 --- a/src/components/prompt/prompt.template.html +++ b/src/components/prompt/prompt.template.html @@ -2,12 +2,12 @@ +

-
@@ -19,7 +19,6 @@
-
diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 089915a834..16aef3cc16 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -196,7 +196,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL context.querySelector('.nowPlayingPageImage').classList.remove('nowPlayingPageImageAudio'); } } else { - imgContainer.innerHTML = '
'; + imgContainer.innerHTML = '
'; } } @@ -589,7 +589,7 @@ define(['browser', 'datetime', 'backdrop', 'libraryBrowser', 'listView', 'imageL require(['playlistEditor'], function (playlistEditor) { getSaveablePlaylistItems().then(function (items) { var serverId = items.length ? items[0].ServerId : ApiClient.serverId(); - new playlistEditor().show({ + new playlistEditor.showEditor({ items: items.map(function (i) { return i.Id; }), diff --git a/src/components/serverRestartDialog.js b/src/components/serverRestartDialog.js deleted file mode 100644 index 4c52d5f01b..0000000000 --- a/src/components/serverRestartDialog.js +++ /dev/null @@ -1,158 +0,0 @@ -define(['loading', 'events', 'dialogHelper', 'dom', 'layoutManager', 'scrollHelper', 'globalize', 'require', 'material-icons', 'emby-button', 'paper-icon-button-light', 'emby-input', 'formDialogStyle', 'flexStyles'], function (loading, events, dialogHelper, dom, layoutManager, scrollHelper, globalize, require) { - 'use strict'; - - var currentApiClient; - var currentDlg; - var currentInstance; - - function reloadPageWhenServerAvailable(retryCount) { - var apiClient = currentApiClient; - if (!apiClient) { - return; - } - - // Don't use apiclient method because we don't want it reporting authentication under the old version - apiClient.getJSON(apiClient.getUrl('System/Info')).then(function (info) { - - // If this is back to false, the restart completed - if (!info.IsShuttingDown) { - currentInstance.restarted = true; - dialogHelper.close(currentDlg); - } else { - retryReload(retryCount); - } - - }, function () { - retryReload(retryCount); - }); - } - - function retryReload(retryCount) { - setTimeout(function () { - retryCount = retryCount || 0; - retryCount++; - - if (retryCount < 150) { - reloadPageWhenServerAvailable(retryCount); - } - }, 500); - } - - function startRestart(instance, apiClient, dlg) { - currentApiClient = apiClient; - currentDlg = dlg; - currentInstance = instance; - - apiClient.restartServer().then(function () { - setTimeout(reloadPageWhenServerAvailable, 250); - }); - } - - function showDialog(instance, options, template) { - - var dialogOptions = { - removeOnClose: true, - scrollY: false - }; - - var enableTvLayout = layoutManager.tv; - - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; - } - - var dlg = dialogHelper.createDialog(dialogOptions); - - var configuredButtons = []; - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.classList.add('align-items-center'); - dlg.classList.add('justify-items-center'); - - var formDialogContent = dlg.querySelector('.formDialogContent'); - formDialogContent.style['flex-grow'] = 'initial'; - - if (enableTvLayout) { - formDialogContent.style['max-width'] = '50%'; - formDialogContent.style['max-height'] = '60%'; - scrollHelper.centerFocus.on(formDialogContent, false); - } else { - formDialogContent.style.maxWidth = (Math.min((configuredButtons.length * 150) + 200, dom.getWindowSize().innerWidth - 50)) + 'px'; - dlg.classList.add('dialog-fullscreen-lowres'); - } - - dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('HeaderRestartingServer'); - - dlg.querySelector('.text').innerHTML = globalize.translate('RestartPleaseWaitMessage'); - - var i; - var length; - var html = ''; - for (i = 0, length = configuredButtons.length; i < length; i++) { - var item = configuredButtons[i]; - var autoFocus = i === 0 ? ' autofocus' : ''; - var buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize'; - - if (item.type) { - buttonClass += ' button-' + item.type; - } - html += ''; - } - - dlg.querySelector('.formDialogFooter').innerHTML = html; - - function onButtonClick() { - dialogHelper.close(dlg); - } - - var buttons = dlg.querySelectorAll('.btnOption'); - for (i = 0, length = buttons.length; i < length; i++) { - buttons[i].addEventListener('click', onButtonClick); - } - - var dlgPromise = dialogHelper.open(dlg); - - startRestart(instance, options.apiClient, dlg); - - return dlgPromise.then(function () { - - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - instance.destroy(); - loading.hide(); - - if (instance.restarted) { - events.trigger(instance, 'restarted'); - } - }); - } - - function ServerRestartDialog(options) { - this.options = options; - } - - ServerRestartDialog.prototype.show = function () { - var instance = this; - loading.show(); - - return new Promise(function (resolve, reject) { - require(['text!./../dialog/dialog.template.html'], function (template) { - showDialog(instance, instance.options, template).then(resolve, reject); - }); - }); - }; - - ServerRestartDialog.prototype.destroy = function () { - currentApiClient = null; - currentDlg = null; - currentInstance = null; - this.options = null; - }; - - return ServerRestartDialog; -}); diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index ab606ab1d2..f105d6c599 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -1,18 +1,30 @@ -define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'globalize', 'loading', 'dom', 'recordingHelper'], function (playbackManager, inputManager, connectionManager, appRouter, globalize, loading, dom, recordingHelper) { - 'use strict'; +/* eslint-disable indent */ + +/** + * Module shortcuts. + * @module components/shortcuts + */ + +import playbackManager from 'playbackManager'; +import inputManager from 'inputManager'; +import connectionManager from 'connectionManager'; +import appRouter from 'appRouter'; +import globalize from 'globalize'; +import dom from 'dom'; +import recordingHelper from 'recordingHelper'; function playAllFromHere(card, serverId, queue) { - var parent = card.parentNode; - var className = card.classList.length ? ('.' + card.classList[0]) : ''; - var cards = parent.querySelectorAll(className + '[data-id]'); + const parent = card.parentNode; + const className = card.classList.length ? (`.${card.classList[0]}`) : ''; + const cards = parent.querySelectorAll(`${className}[data-id]`); - var ids = []; + const ids = []; - var foundCard = false; - var startIndex; + let foundCard = false; + let startIndex; - for (var i = 0, length = cards.length; i < length; i++) { + for (let i = 0, length = cards.length; i < length; i++) { if (cards[i] === card) { foundCard = true; startIndex = i; @@ -22,12 +34,12 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } } - var itemsContainer = dom.parentWithClass(card, 'itemsContainer'); + const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); if (itemsContainer && itemsContainer.fetchData) { - var queryOptions = queue ? { StartIndex: startIndex } : {}; + const queryOptions = queue ? { StartIndex: startIndex } : {}; - return itemsContainer.fetchData(queryOptions).then(function (result) { + return itemsContainer.fetchData(queryOptions).then(result => { if (queue) { return playbackManager.queue({ @@ -64,7 +76,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function showProgramDialog(item) { - require(['recordingCreator'], function (recordingCreator) { + import('recordingCreator').then(({default:recordingCreator}) => { recordingCreator.show(item.Id, item.ServerId); }); @@ -73,11 +85,11 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function getItem(button) { button = dom.parentWithAttribute(button, 'data-id'); - var serverId = button.getAttribute('data-serverid'); - var id = button.getAttribute('data-id'); - var type = button.getAttribute('data-type'); + const serverId = button.getAttribute('data-serverid'); + const id = button.getAttribute('data-id'); + const type = button.getAttribute('data-type'); - var apiClient = connectionManager.getApiClient(serverId); + const apiClient = connectionManager.getApiClient(serverId); if (type === 'Timer') { return apiClient.getLiveTvTimer(id); @@ -99,19 +111,19 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function showContextMenu(card, options) { - getItem(card).then(function (item) { + getItem(card).then(item => { - var playlistId = card.getAttribute('data-playlistid'); - var collectionId = card.getAttribute('data-collectionid'); + const playlistId = card.getAttribute('data-playlistid'); + const collectionId = card.getAttribute('data-collectionid'); if (playlistId) { - var elem = dom.parentWithAttribute(card, 'data-playlistitemid'); + const elem = dom.parentWithAttribute(card, 'data-playlistitemid'); item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; } - require(['itemContextMenu'], function (itemContextMenu) { + import('itemContextMenu').then(({default: itemContextMenu}) => { - connectionManager.getApiClient(item.ServerId).getCurrentUser().then(function (user) { + connectionManager.getApiClient(item.ServerId).getCurrentUser().then(user => { itemContextMenu.show(Object.assign({ item: item, play: true, @@ -122,9 +134,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl collectionId: collectionId, user: user - }, options || {})).then(function (result) { - - var itemsContainer; + }, options || {})).then(result => { if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { executeAction(card, options.positionTo, result.command); @@ -157,9 +167,9 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function showPlayMenu(card, target) { - var item = getItemInfoFromCard(card); + const item = getItemInfoFromCard(card); - require(['playMenu'], function (playMenu) { + import('playMenu').then(({default: playMenu}) => { playMenu.show({ @@ -170,7 +180,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } function sendToast(text) { - require(['toast'], function (toast) { + import('toast').then(({default: toast}) => { toast(text); }); } @@ -179,19 +189,19 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl target = target || card; - var id = card.getAttribute('data-id'); + let id = card.getAttribute('data-id'); if (!id) { card = dom.parentWithAttribute(card, 'data-id'); id = card.getAttribute('data-id'); } - var item = getItemInfoFromCard(card); + const item = getItemInfoFromCard(card); - var serverId = item.ServerId; - var type = item.Type; + const serverId = item.ServerId; + const type = item.Type; - var playableItemId = type === 'Program' ? item.ChannelId : item.Id; + const playableItemId = type === 'Program' ? item.ChannelId : item.Id; if (item.MediaType === 'Photo' && action === 'link') { action = 'play'; @@ -213,7 +223,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl }); } else if (action === 'play' || action === 'resume') { - var startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0'); + const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0'); playbackManager.play({ ids: [playableItemId], @@ -244,7 +254,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); } else if (action === 'menu') { - var options = target.getAttribute('data-playoptions') === 'false' ? + const options = target.getAttribute('data-playoptions') === 'false' ? { shuffle: false, instantMix: false, @@ -261,7 +271,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } else if (action === 'playmenu') { showPlayMenu(card, target); } else if (action === 'edit') { - getItem(target).then(function (item) { + getItem(target).then(item => { editItem(item, serverId); }); } else if (action === 'playtrailer') { @@ -270,9 +280,9 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl getItem(target).then(addToPlaylist); } else if (action === 'custom') { - var customAction = target.getAttribute('data-customaction'); + const customAction = target.getAttribute('data-customaction'); - card.dispatchEvent(new CustomEvent('action-' + customAction, { + card.dispatchEvent(new CustomEvent(`action-${customAction}`, { detail: { playlistItemId: card.getAttribute('data-playlistitemid') }, @@ -283,7 +293,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } function addToPlaylist(item) { - require(['playlistEditor'], function (playlistEditor) { + import('playlistEditor').then(({default: playlistEditor}) => { new playlistEditor().show({ items: [item.Id], @@ -295,35 +305,35 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function playTrailer(item) { - var apiClient = connectionManager.getApiClient(item.ServerId); + const apiClient = connectionManager.getApiClient(item.ServerId); - apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(function (trailers) { + apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { playbackManager.play({ items: trailers }); }); } function editItem(item, serverId) { - var apiClient = connectionManager.getApiClient(serverId); + const apiClient = connectionManager.getApiClient(serverId); - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { - var serverId = apiClient.serverInfo().Id; + const serverId = apiClient.serverInfo().Id; if (item.Type === 'Timer') { if (item.ProgramId) { - require(['recordingCreator'], function (recordingCreator) { + import('recordingCreator').then(({default: recordingCreator}) => { recordingCreator.show(item.ProgramId, serverId).then(resolve, reject); }); } else { - require(['recordingEditor'], function (recordingEditor) { + import('recordingEditor').then(({default: recordingEditor}) => { recordingEditor.show(item.Id, serverId).then(resolve, reject); }); } } else { - require(['metadataEditor'], function (metadataEditor) { + import('metadataEditor').then(({default: metadataEditor}) => { metadataEditor.show(item.Id, serverId).then(resolve, reject); }); @@ -335,19 +345,19 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl if (type === 'Program' || timerId || seriesTimerId) { - var programId = type === 'Program' ? id : null; + const programId = type === 'Program' ? id : null; recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); } } - function onClick(e) { + export function onClick(e) { - var card = dom.parentWithClass(e.target, 'itemAction'); + const card = dom.parentWithClass(e.target, 'itemAction'); if (card) { - var actionElement = card; - var action = actionElement.getAttribute('data-action'); + let actionElement = card; + let action = actionElement.getAttribute('data-action'); if (!action) { actionElement = dom.parentWithAttribute(actionElement, 'data-action'); @@ -368,12 +378,12 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl function onCommand(e) { - var cmd = e.detail.command; + const cmd = e.detail.command; if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { - var target = e.target; - var card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); + const target = e.target; + const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); if (card) { e.preventDefault(); @@ -383,7 +393,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } } - function on(context, options) { + export function on(context, options) { options = options || {}; @@ -396,7 +406,7 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } } - function off(context, options) { + export function off(context, options) { options = options || {}; context.removeEventListener('click', onClick); @@ -406,23 +416,24 @@ define(['playbackManager', 'inputManager', 'connectionManager', 'appRouter', 'gl } } - function getShortcutAttributesHtml(item, serverId) { + export function getShortcutAttributesHtml(item, serverId) { - var html = 'data-id="' + item.Id + '" data-serverid="' + (serverId || item.ServerId) + '" data-type="' + item.Type + '" data-mediatype="' + item.MediaType + '" data-channelid="' + item.ChannelId + '" data-isfolder="' + item.IsFolder + '"'; + let html = `data-id="${item.Id}" data-serverid="${serverId || item.ServerId}" data-type="${item.Type}" data-mediatype="${item.MediaType}" data-channelid="${item.ChannelId}" data-isfolder="${item.IsFolder}"`; - var collectionType = item.CollectionType; + const collectionType = item.CollectionType; if (collectionType) { - html += ' data-collectiontype="' + collectionType + '"'; + html += ` data-collectiontype="${collectionType}"`; } return html; } - return { - on: on, - off: off, - onClick: onClick, - getShortcutAttributesHtml: getShortcutAttributesHtml - }; +/* eslint-enable indent */ + +export default { + on: on, + off: off, + onClick: onClick, + getShortcutAttributesHtml: getShortcutAttributesHtml +}; -}); diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 6ff88a00c9..b027c203a5 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -2,9 +2,20 @@ * Image viewer component * @module components/slideshow/slideshow */ -define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost) { +define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'focusManager', 'browser', 'apphost', 'dom', 'css!./style', 'material-icons', 'paper-icon-button-light'], function (dialogHelper, inputManager, connectionManager, layoutManager, focusManager, browser, appHost, dom) { 'use strict'; + /** + * Name of transition event. + */ + const transitionEndEventName = dom.whichTransitionEvent(); + + /** + * Flag to use fake image to fix blurry zoomed image. + * At least WebKit doesn't restore quality for zoomed images. + */ + const useFakeZoomImage = browser.safari; + /** * Retrieves an item's image URL from the API. * @param {object|string} item - Item used to generate the image URL. @@ -240,6 +251,41 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f } } + /** + * Handles zoom changes. + */ + function onZoomChange(scale, imageEl, slideEl) { + const zoomImage = slideEl.querySelector('.swiper-zoom-fakeimg'); + + if (zoomImage) { + zoomImage.style.width = zoomImage.style.height = scale * 100 + '%'; + + if (scale > 1) { + if (zoomImage.classList.contains('swiper-zoom-fakeimg-hidden')) { + // Await for Swiper style changes + setTimeout(() => { + const callback = () => { + imageEl.removeEventListener(transitionEndEventName, callback); + zoomImage.classList.remove('swiper-zoom-fakeimg-hidden'); + }; + + // Swiper set 'transition-duration: 300ms' for auto zoom + // and 'transition-duration: 0s' for touch zoom + const transitionDuration = parseFloat(imageEl.style.transitionDuration.replace(/[a-z]/i, '')); + + if (transitionDuration > 0) { + imageEl.addEventListener(transitionEndEventName, callback); + } else { + callback(); + } + }, 0); + } + } else { + zoomImage.classList.add('swiper-zoom-fakeimg-hidden'); + } + } + } + /** * Initializes the Swiper instance and binds the relevant events. * @param {HTMLElement} dialog - Element containing the dialog. @@ -260,8 +306,7 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f loop: false, zoom: { minRatio: 1, - toggle: true, - containerClass: 'slider-zoom-container' + toggle: true }, autoplay: !options.interactive, keyboard: { @@ -288,6 +333,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f swiperInstance.on('autoplayStart', onAutoplayStart); swiperInstance.on('autoplayStop', onAutoplayStop); + + if (useFakeZoomImage) { + swiperInstance.on('zoomChange', onZoomChange); + } }); } @@ -328,7 +377,10 @@ define(['dialogHelper', 'inputManager', 'connectionManager', 'layoutManager', 'f function getSwiperSlideHtmlFromSlide(item) { var html = ''; html += '
'; - html += '
'; + html += '
'; + if (useFakeZoomImage) { + html += `
`; + } html += ''; html += '
'; if (item.title || item.subtitle) { diff --git a/src/components/slideshow/style.css b/src/components/slideshow/style.css index f1fea508d7..af50eb9cd7 100644 --- a/src/components/slideshow/style.css +++ b/src/components/slideshow/style.css @@ -40,16 +40,6 @@ text-shadow: 3px 3px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; } -.swiper-slide-img { - max-height: 100%; - max-width: 100%; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - margin: auto; -} - .slideshowButtonIcon { color: #fff; opacity: 0.7; @@ -135,13 +125,18 @@ color: #ccc; } -.swiper-slide { - display: flex; - flex-direction: column; +.swiper-zoom-fakeimg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: contain; + z-index: 1; + pointer-events: none; } -.slider-zoom-container { - margin: auto; - max-height: 100%; - max-width: 100%; +.swiper-zoom-fakeimg-hidden { + display: none; } diff --git a/src/components/syncplay/groupSelectionMenu.js b/src/components/syncPlay/groupSelectionMenu.js similarity index 100% rename from src/components/syncplay/groupSelectionMenu.js rename to src/components/syncPlay/groupSelectionMenu.js diff --git a/src/components/syncplay/playbackPermissionManager.js b/src/components/syncPlay/playbackPermissionManager.js similarity index 100% rename from src/components/syncplay/playbackPermissionManager.js rename to src/components/syncPlay/playbackPermissionManager.js diff --git a/src/components/syncplay/syncPlayManager.js b/src/components/syncPlay/syncPlayManager.js similarity index 99% rename from src/components/syncplay/syncPlayManager.js rename to src/components/syncPlay/syncPlayManager.js index f04d1aeb8c..6116884d7c 100644 --- a/src/components/syncplay/syncPlayManager.js +++ b/src/components/syncPlay/syncPlayManager.js @@ -1,6 +1,6 @@ /** * Module that manages the SyncPlay feature. - * @module components/syncplay/syncPlayManager + * @module components/syncPlay/syncPlayManager */ import events from 'events'; diff --git a/src/components/syncplay/timeSyncManager.js b/src/components/syncPlay/timeSyncManager.js similarity index 98% rename from src/components/syncplay/timeSyncManager.js rename to src/components/syncPlay/timeSyncManager.js index ca92939576..d3b59589ee 100644 --- a/src/components/syncplay/timeSyncManager.js +++ b/src/components/syncPlay/timeSyncManager.js @@ -1,6 +1,6 @@ /** * Module that manages time syncing with server. - * @module components/syncplay/timeSyncManager + * @module components/syncPlay/timeSyncManager */ import events from 'events'; @@ -65,8 +65,6 @@ class TimeSyncManager { this.pings = 0; // number of pings this.measurement = null; // current time sync this.measurements = []; - - this.startPing(); } /** diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index 6c83e4a3ba..dfc9569f34 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -43,10 +43,6 @@ define(['browser', 'dom', 'layoutManager', 'css!components/viewManager/viewConta var newView = newViewInfo.elem; var modulesToLoad = []; - if (isPluginpage) { - modulesToLoad.push('legacyDashboard'); - } - if (newViewInfo.hasjQuerySelect) { modulesToLoad.push('legacySelectMenu'); } diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index a8e514e06e..8e17264f9f 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -21,10 +21,11 @@ define(['viewContainer', 'focusManager', 'queryString', 'layoutManager'], functi if (!newView.initComplete) { newView.initComplete = true; + var controller; if (typeof options.controllerFactory === 'function') { - - // Use controller method - var controller = new options.controllerFactory(newView, eventDetail.detail.params); + controller = new options.controllerFactory(newView, eventDetail.detail.params); + } else if (options.controllerFactory && typeof options.controllerFactory.default === 'function') { + controller = new options.controllerFactory.default(newView, eventDetail.detail.params); } if (!options.controllerFactory || dispatchPageEvents) { diff --git a/src/components/viewsettings/viewsettings.js b/src/components/viewSettings/viewSettings.js similarity index 98% rename from src/components/viewsettings/viewsettings.js rename to src/components/viewSettings/viewSettings.js index 441c35157b..087ba3e373 100644 --- a/src/components/viewsettings/viewsettings.js +++ b/src/components/viewSettings/viewSettings.js @@ -57,7 +57,7 @@ define(['require', 'dialogHelper', 'loading', 'apphost', 'layoutManager', 'conne return new Promise(function (resolve, reject) { - require(['text!./viewsettings.template.html'], function (template) { + require(['text!./viewSettings.template.html'], function (template) { var dialogOptions = { removeOnClose: true, diff --git a/src/components/viewsettings/viewsettings.template.html b/src/components/viewSettings/viewSettings.template.html similarity index 100% rename from src/components/viewsettings/viewsettings.template.html rename to src/components/viewSettings/viewSettings.template.html diff --git a/src/config.json b/src/config.json new file mode 120000 index 0000000000..f1bd2db71e --- /dev/null +++ b/src/config.json @@ -0,0 +1 @@ +config.template.json \ No newline at end of file diff --git a/src/config.template.json b/src/config.template.json index 1e79270943..4b22a699d6 100644 --- a/src/config.template.json +++ b/src/config.template.json @@ -1,3 +1,3 @@ { - "multiserver": true + "multiserver": false } diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index f91f4e01ef..6a378903d3 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -252,12 +252,6 @@ define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globa html += '
'; html += '
'; - if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { - html += '
' + session.TranscodingInfo.Framerate + ' fps
'; - } else { - html += '
'; - } - html += '
'; var nowPlayingName = DashboardPage.getNowPlayingName(session); html += '
'; @@ -573,7 +567,6 @@ define(['datetime', 'events', 'itemHelper', 'serverNotifications', 'dom', 'globa row.querySelector('.sessionNowPlayingTime').innerHTML = DashboardPage.getSessionNowPlayingTime(session); row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); row.querySelector('.sessionAppSecondaryText').innerHTML = DashboardPage.getAppSecondaryText(session); - row.querySelector('.sessionTranscodingFramerate').innerHTML = session.TranscodingInfo && session.TranscodingInfo.Framerate ? session.TranscodingInfo.Framerate + ' fps' : ''; var nowPlayingName = DashboardPage.getNowPlayingName(session); var nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 81eb099f50..54ef75a6d1 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -23,8 +23,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in $('.chkMediaType', page).each(function () { this.checked = -1 != (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')); }); - $('#chkEnableAlbumArtInDidl', page).checked = profile.EnableAlbumArtInDidl; - $('#chkEnableSingleImageLimit', page).checked = profile.EnableSingleAlbumArtLimit; + $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); + $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); var idInfo = profile.Identification || {}; renderIdentificationHeaders(page, idInfo.Headers || []); @@ -51,11 +51,11 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); - $('#chkIgnoreTranscodeByteRangeRequests', page).checked = profile.IgnoreTranscodeByteRangeRequests; + $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); - $('#chkRequiresPlainFolders', page).checked = profile.RequiresPlainFolders; - $('#chkRequiresPlainVideoItems', page).checked = profile.RequiresPlainVideoItems; + $('#chkRequiresPlainFolders', page).prop('checked', profile.RequiresPlainFolders); + $('#chkRequiresPlainVideoItems', page).prop('checked', profile.RequiresPlainVideoItems); $('#txtProtocolInfo', page).val(profile.ProtocolInfo || ''); $('#txtXDlnaCap', page).val(profile.XDlnaCap || ''); $('#txtXDlnaDoc', page).val(profile.XDlnaDoc || ''); @@ -357,9 +357,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); - $('#chkEnableMpegtsM2TsMode', popup).checked = transcodingProfile.EnableMpegtsM2TsMode || false; - $('#chkEstimateContentLength', popup).checked = transcodingProfile.EstimateContentLength || false; - $('#chkReportByteRangeRequests', popup).checked = 'Bytes' == transcodingProfile.TranscodeSeekInfo; + $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); + $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); + $('#chkReportByteRangeRequests', popup).prop('checked', 'Bytes' == transcodingProfile.TranscodeSeekInfo); $('.radioTabButton:first', popup).trigger('click'); openPopup(popup[0]); } @@ -376,9 +376,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); currentSubProfile.Context = 'Streaming'; - currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).checked; - currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).checked; - currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).checked ? 'Bytes' : 'Auto'; + currentSubProfile.EnableMpegtsM2TsMode = $('#chkEnableMpegtsM2TsMode', page).is(':checked'); + currentSubProfile.EstimateContentLength = $('#chkEstimateContentLength', page).is(':checked'); + currentSubProfile.TranscodeSeekInfo = $('#chkReportByteRangeRequests', page).is(':checked') ? 'Bytes' : 'Auto'; if (isSubProfileNew) { currentProfile.TranscodingProfiles.push(currentSubProfile); @@ -647,8 +647,8 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in function updateProfile(page, profile) { profile.Name = $('#txtName', page).val(); - profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).checked; - profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).checked; + profile.EnableAlbumArtInDidl = $('#chkEnableAlbumArtInDidl', page).is(':checked'); + profile.EnableSingleAlbumArtLimit = $('#chkEnableSingleImageLimit', page).is(':checked'); profile.SupportedMediaTypes = $('.chkMediaType:checked', page).get().map(function (c) { return c.getAttribute('data-value'); }).join(','); @@ -675,9 +675,9 @@ define(['jQuery', 'loading', 'globalize', 'emby-select', 'emby-button', 'emby-in profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); - profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).checked; - profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).checked; - profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).checked; + profile.RequiresPlainFolders = $('#chkRequiresPlainFolders', page).is(':checked'); + profile.RequiresPlainVideoItems = $('#chkRequiresPlainVideoItems', page).is(':checked'); + profile.IgnoreTranscodeByteRangeRequests = $('#chkIgnoreTranscodeByteRangeRequests', page).is(':checked'); profile.MaxStreamingBitrate = $('#txtMaxAllowedBitrate', page).val(); profile.MusicStreamingTranscodingBitrate = $('#txtMusicStreamingTranscodingBitrate', page).val(); profile.ProtocolInfo = $('#txtProtocolInfo', page).val(); diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js index da254c0bd9..5bbfea5d4b 100644 --- a/src/controllers/dashboard/dlna/settings.js +++ b/src/controllers/dashboard/dlna/settings.js @@ -5,8 +5,8 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); - $('#chkEnableServer', page).checked = config.EnableServer; - $('#chkBlastAliveMessages', page).checked = config.BlastAliveMessages; + $('#chkEnableServer', page).prop('checked', config.EnableServer); + $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); var usersHtml = users.map(function (u) { return ''; @@ -22,8 +22,8 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); - config.EnableServer = $('#chkEnableServer', form).checked; - config.BlastAliveMessages = $('#chkBlastAliveMessages', form).checked; + config.EnableServer = $('#chkEnableServer', form).is(':checked'); + config.BlastAliveMessages = $('#chkBlastAliveMessages', form).is(':checked'); config.BlastAliveMessageIntervalSeconds = $('#txtBlastInterval', form).val(); config.DefaultUserId = $('#selectUser', form).val(); ApiClient.updateNamedConfiguration('dlna', config).then(Dashboard.processServerConfigurationUpdateResult); diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index 65f4e4401d..0f54f9d70f 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -5,6 +5,8 @@ define(['jQuery', 'loading', 'globalize', 'dom', 'libraryMenu'], function ($, lo Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { c.checked = -1 !== (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')); }); + page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; + page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); $('#selectThreadCount', page).val(config.EncodingThreadCount); @@ -67,6 +69,8 @@ define(['jQuery', 'loading', 'globalize', 'dom', 'libraryMenu'], function ($, lo }), function (c) { return c.getAttribute('data-codec'); }); + config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked; + config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked; config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; ApiClient.updateNamedConfiguration('encoding', config).then(function () { updateEncoder(form); diff --git a/src/controllers/dashboard/logs.js b/src/controllers/dashboard/logs.js index e0b000a130..7bf2785d50 100644 --- a/src/controllers/dashboard/logs.js +++ b/src/controllers/dashboard/logs.js @@ -1,6 +1,12 @@ -define(['datetime', 'loading', 'apphost', 'listViewStyle', 'emby-button', 'flexStyles'], function(datetime, loading, appHost) { - 'use strict'; - return function(view, params) { +import datetime from 'datetime'; +import loading from 'loading'; +import 'emby-button'; +import 'listViewStyle'; +import 'flexStyles'; + +/* eslint-disable indent */ + + export default function(view, params) { view.addEventListener('viewbeforeshow', function() { loading.show(); var apiClient = ApiClient; @@ -29,5 +35,6 @@ define(['datetime', 'loading', 'apphost', 'listViewStyle', 'emby-button', 'flexS loading.hide(); }); }); - }; -}); + } + +/* eslint-enable indent */ diff --git a/src/controllers/dashboard/notifications/notification.js b/src/controllers/dashboard/notifications/notification.js index 370fc6a369..af9301c13f 100644 --- a/src/controllers/dashboard/notifications/notification.js +++ b/src/controllers/dashboard/notifications/notification.js @@ -50,7 +50,7 @@ define(['jQuery', 'emby-checkbox'], function ($) { fillItems($('.monitorUsersList', page), users, 'chkMonitor', 'chkMonitor', notificationConfig.DisabledMonitorUsers); fillItems($('.sendToUsersList', page), users, 'chkSendTo', 'chkSendTo', notificationConfig.SendToUsers, true); fillItems($('.servicesList', page), services, 'chkService', 'chkService', notificationConfig.DisabledServices); - $('#chkEnabled', page).checked = notificationConfig.Enabled || false; + $('#chkEnabled', page).prop('checked', notificationConfig.Enabled || false); $('#selectUsers', page).val(notificationConfig.SendToUserMode).trigger('change'); }); } @@ -73,7 +73,7 @@ define(['jQuery', 'emby-checkbox'], function ($) { notificationOptions.Options.push(notificationConfig); } - notificationConfig.Enabled = $('#chkEnabled', page).checked; + notificationConfig.Enabled = $('#chkEnabled', page).is(':checked'); notificationConfig.SendToUserMode = $('#selectUsers', page).val(); notificationConfig.DisabledMonitorUsers = $('.chkMonitor', page).get().filter(function (c) { return !c.checked; diff --git a/src/controllers/dashboard/plugins/available.js b/src/controllers/dashboard/plugins/available.js index 82fea00b58..37df8801b5 100644 --- a/src/controllers/dashboard/plugins/available.js +++ b/src/controllers/dashboard/plugins/available.js @@ -123,6 +123,9 @@ define(['loading', 'libraryMenu', 'globalize', 'cardStyle', 'emby-button', 'emby }, { href: 'availableplugins.html', name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') }]; } diff --git a/src/controllers/dashboard/plugins/installed.js b/src/controllers/dashboard/plugins/installed.js index 5ca739f711..5ac9737d69 100644 --- a/src/controllers/dashboard/plugins/installed.js +++ b/src/controllers/dashboard/plugins/installed.js @@ -37,20 +37,24 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button' })[0]; var configPageUrl = configPage ? Dashboard.getConfigurationPageUrl(configPage.Name) : null; var html = ''; - html += "
"; + html += "
"; html += '
'; html += '
'; html += '
'; - html += configPageUrl ? '' : ''; html += '
'; - html += '
'; - html += ''; - html += '
'; + + if (configPage || plugin.CanUninstall) { + html += '
'; + html += ''; + html += '
'; + } + html += "
"; - html += configPage.DisplayName || plugin.Name; + html += configPage && configPage.DisplayName ? configPage.DisplayName : plugin.Name; html += '
'; html += "
"; html += plugin.Version; @@ -104,6 +108,7 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button' var card = dom.parentWithClass(elem, 'card'); var id = card.getAttribute('data-id'); var name = card.getAttribute('data-name'); + var removable = card.getAttribute('data-removable'); var configHref = card.querySelector('.cardContent').getAttribute('href'); var menuItems = []; @@ -115,11 +120,13 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button' }); } - menuItems.push({ - name: globalize.translate('ButtonUninstall'), - id: 'delete', - icon: 'delete' - }); + if (removable === 'true') { + menuItems.push({ + name: globalize.translate('ButtonUninstall'), + id: 'delete', + icon: 'delete' + }); + } require(['actionsheet'], function (actionsheet) { actionsheet.show({ @@ -153,6 +160,9 @@ define(['loading', 'libraryMenu', 'dom', 'globalize', 'cardStyle', 'emby-button' }, { href: 'availableplugins.html', name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') }]; } diff --git a/src/controllers/dashboard/plugins/repositories.js b/src/controllers/dashboard/plugins/repositories.js new file mode 100644 index 0000000000..3087cdd927 --- /dev/null +++ b/src/controllers/dashboard/plugins/repositories.js @@ -0,0 +1,153 @@ +import loading from 'loading'; +import libraryMenu from 'libraryMenu'; +import globalize from 'globalize'; +import dialogHelper from 'dialogHelper'; +import 'emby-button'; +import 'emby-checkbox'; +import 'emby-select'; +import 'formDialogStyle'; +import 'listViewStyle'; + +let repositories = []; + +function reloadList(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Repositories')).then(list => { + repositories = list; + populateList({ + listElement: page.querySelector('#repositories'), + noneElement: page.querySelector('#none'), + repositories: repositories + }); + }).catch(error => { + console.error('error loading repositories'); + page.querySelector('#none').classList.remove('hide'); + loading.hide(); + }); +} + +function saveList(page) { + loading.show(); + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Repositories'), + data: JSON.stringify(repositories), + contentType: 'application/json' + }).then(response => { + reloadList(page); + }).catch(error => { + console.error('error saving repositories'); + loading.hide(); + }); +} + +function populateList(options) { + var html = ''; + + html += '
'; + for (var i = 0; i < options.repositories.length; i++) { + html += getRepositoryHtml(options.repositories[i]); + } + + html += '
'; + if (!options.repositories.length) { + options.noneElement.classList.remove('hide'); + } + + options.listElement.innerHTML = html; + loading.hide(); +} + +function getRepositoryHtml(repository) { + var html = ''; + + html += '
'; + html += ``; + html += ''; + html += ''; + html += '
'; + html += `

${repository.Name}

`; + html += `
${repository.Url}
`; + html += '
'; + html += ``; + html += '
'; + + return html; +} + +function getTabs() { + return [{ + href: 'installedplugins.html', + name: globalize.translate('TabMyPlugins') + }, { + href: 'availableplugins.html', + name: globalize.translate('TabCatalog') + }, { + href: 'repositories.html', + name: globalize.translate('TabRepositories') + }]; +} + +export default function(view, params) { + view.addEventListener('viewshow', function () { + libraryMenu.setTabs('plugins', 2, getTabs); + reloadList(this); + + var save = this; + $('#repositories', view).on('click', '.btnDelete', function() { + var button = this; + repositories = repositories.filter(function (r) { + return r.Url !== button.id; + }); + + saveList(save); + }); + }); + + view.querySelector('.btnNewRepository').addEventListener('click', () => { + let dialog = dialogHelper.createDialog({ + scrollY: false, + size: 'large', + modal: false, + removeOnClose: true + }); + + let html = ''; + + html += '
'; + html += ''; + html += `

${globalize.translate('HeaderNewRepository')}

`; + html += '
'; + html += '
'; + html += '
'; + html += ``; + html += `
${globalize.translate('LabelRepositoryNameHelp')}
`; + html += '
'; + html += '
'; + html += ``; + html += `
${globalize.translate('LabelRepositoryUrlHelp')}
`; + html += '
'; + html += ``; + html += '
'; + html += ''; + + dialog.innerHTML = html; + dialog.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dialog); + }); + + dialog.querySelector('.newPluginForm').addEventListener('submit', () => { + repositories.push({ + Name: dialog.querySelector('#txtRepositoryName').value, + Url: dialog.querySelector('#txtRepositoryUrl').value, + Enabled: true + }); + + saveList(view); + dialogHelper.close(dialog); + return false; + }); + + dialogHelper.open(dialog); + }); +} diff --git a/src/controllers/dashboard/users/useredit.js b/src/controllers/dashboard/users/useredit.js index c71c81d9e0..af187412d0 100644 --- a/src/controllers/dashboard/users/useredit.js +++ b/src/controllers/dashboard/users/useredit.js @@ -27,7 +27,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, } $('.deleteAccess', page).html(html).trigger('create'); - $('#chkEnableDeleteAllFolders', page).checked = user.Policy.EnableContentDeletion; + $('#chkEnableDeleteAllFolders', page).prop('checked', user.Policy.EnableContentDeletion); }); } @@ -85,23 +85,23 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, libraryMenu.setTitle(user.Name); page.querySelector('.username').innerHTML = user.Name; $('#txtUserName', page).val(user.Name); - $('#chkIsAdmin', page).checked = user.Policy.IsAdministrator; - $('#chkDisabled', page).checked = user.Policy.IsDisabled; - $('#chkIsHidden', page).checked = user.Policy.IsHidden; - $('#chkRemoteControlSharedDevices', page).checked = user.Policy.EnableSharedDeviceControl; - $('#chkEnableRemoteControlOtherUsers', page).checked = user.Policy.EnableRemoteControlOfOtherUsers; - $('#chkEnableDownloading', page).checked = user.Policy.EnableContentDownloading; - $('#chkManageLiveTv', page).checked = user.Policy.EnableLiveTvManagement; - $('#chkEnableLiveTvAccess', page).checked = user.Policy.EnableLiveTvAccess; - $('#chkEnableMediaPlayback', page).checked = user.Policy.EnableMediaPlayback; - $('#chkEnableAudioPlaybackTranscoding', page).checked = user.Policy.EnableAudioPlaybackTranscoding; - $('#chkEnableVideoPlaybackTranscoding', page).checked = user.Policy.EnableVideoPlaybackTranscoding; - $('#chkEnableVideoPlaybackRemuxing', page).checked = user.Policy.EnablePlaybackRemuxing; - $('#chkForceRemoteSourceTranscoding', page).checked = user.Policy.ForceRemoteSourceTranscoding; - $('#chkRemoteAccess', page).checked = null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess; - $('#chkEnableSyncTranscoding', page).checked = user.Policy.EnableSyncTranscoding; - $('#chkEnableConversion', page).checked = user.Policy.EnableMediaConversion || false; - $('#chkEnableSharing', page).checked = user.Policy.EnablePublicSharing; + $('#chkIsAdmin', page).prop('checked', user.Policy.IsAdministrator); + $('#chkDisabled', page).prop('checked', user.Policy.IsDisabled); + $('#chkIsHidden', page).prop('checked', user.Policy.IsHidden); + $('#chkRemoteControlSharedDevices', page).prop('checked', user.Policy.EnableSharedDeviceControl); + $('#chkEnableRemoteControlOtherUsers', page).prop('checked', user.Policy.EnableRemoteControlOfOtherUsers); + $('#chkEnableDownloading', page).prop('checked', user.Policy.EnableContentDownloading); + $('#chkManageLiveTv', page).prop('checked', user.Policy.EnableLiveTvManagement); + $('#chkEnableLiveTvAccess', page).prop('checked', user.Policy.EnableLiveTvAccess); + $('#chkEnableMediaPlayback', page).prop('checked', user.Policy.EnableMediaPlayback); + $('#chkEnableAudioPlaybackTranscoding', page).prop('checked', user.Policy.EnableAudioPlaybackTranscoding); + $('#chkEnableVideoPlaybackTranscoding', page).prop('checked', user.Policy.EnableVideoPlaybackTranscoding); + $('#chkEnableVideoPlaybackRemuxing', page).prop('checked', user.Policy.EnablePlaybackRemuxing); + $('#chkForceRemoteSourceTranscoding', page).prop('checked', user.Policy.ForceRemoteSourceTranscoding); + $('#chkRemoteAccess', page).prop('checked', null == user.Policy.EnableRemoteAccess || user.Policy.EnableRemoteAccess); + $('#chkEnableSyncTranscoding', page).prop('checked', user.Policy.EnableSyncTranscoding); + $('#chkEnableConversion', page).prop('checked', user.Policy.EnableMediaConversion || false); + $('#chkEnableSharing', page).prop('checked', user.Policy.EnablePublicSharing); $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); $('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); $('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess); @@ -119,28 +119,28 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, function saveUser(user, page) { user.Name = $('#txtUserName', page).val(); - user.Policy.IsAdministrator = $('#chkIsAdmin', page).checked; - user.Policy.IsHidden = $('#chkIsHidden', page).checked; - user.Policy.IsDisabled = $('#chkDisabled', page).checked; - user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).checked; - user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).checked; - user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).checked; - user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).checked; - user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).checked; - user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).checked; - user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).checked; - user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).checked; - user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).checked; - user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).checked; - user.Policy.EnableSyncTranscoding = $('#chkEnableSyncTranscoding', page).checked; - user.Policy.EnableMediaConversion = $('#chkEnableConversion', page).checked; - user.Policy.EnablePublicSharing = $('#chkEnableSharing', page).checked; - user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).checked; + user.Policy.IsAdministrator = $('#chkIsAdmin', page).is(':checked'); + user.Policy.IsHidden = $('#chkIsHidden', page).is(':checked'); + user.Policy.IsDisabled = $('#chkDisabled', page).is(':checked'); + user.Policy.EnableRemoteControlOfOtherUsers = $('#chkEnableRemoteControlOtherUsers', page).is(':checked'); + user.Policy.EnableLiveTvManagement = $('#chkManageLiveTv', page).is(':checked'); + user.Policy.EnableLiveTvAccess = $('#chkEnableLiveTvAccess', page).is(':checked'); + user.Policy.EnableSharedDeviceControl = $('#chkRemoteControlSharedDevices', page).is(':checked'); + user.Policy.EnableMediaPlayback = $('#chkEnableMediaPlayback', page).is(':checked'); + user.Policy.EnableAudioPlaybackTranscoding = $('#chkEnableAudioPlaybackTranscoding', page).is(':checked'); + user.Policy.EnableVideoPlaybackTranscoding = $('#chkEnableVideoPlaybackTranscoding', page).is(':checked'); + user.Policy.EnablePlaybackRemuxing = $('#chkEnableVideoPlaybackRemuxing', page).is(':checked'); + user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked'); + user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked'); + user.Policy.EnableSyncTranscoding = $('#chkEnableSyncTranscoding', page).is(':checked'); + user.Policy.EnableMediaConversion = $('#chkEnableConversion', page).is(':checked'); + user.Policy.EnablePublicSharing = $('#chkEnableSharing', page).is(':checked'); + user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked'); user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0')); user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0'); user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value; user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value; - user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).checked; + user.Policy.EnableContentDeletion = $('#chkEnableDeleteAllFolders', page).is(':checked'); user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : $('.chkFolder', page).get().filter(function (c) { return c.checked; }).map(function (c) { diff --git a/src/controllers/dashboard/users/userlibraryaccess.js b/src/controllers/dashboard/users/userlibraryaccess.js index 1fdb6cb599..5ea24e3da3 100644 --- a/src/controllers/dashboard/users/userlibraryaccess.js +++ b/src/controllers/dashboard/users/userlibraryaccess.js @@ -47,7 +47,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, $('.channelAccessContainer', page).hide(); } - $('#chkEnableAllChannels', page).checked = user.Policy.EnableAllChannels; + $('#chkEnableAllChannels', page).prop('checked', user.Policy.EnableAllChannels); } function loadDevices(page, user, devices) { @@ -63,7 +63,7 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, html += '
'; $('.deviceAccess', page).show().html(html); - $('#chkEnableAllDevices', page).checked = user.Policy.EnableAllDevices; + $('#chkEnableAllDevices', page).prop('checked', user.Policy.EnableAllDevices); if (user.Policy.IsAdministrator) { page.querySelector('.deviceAccessContainer').classList.add('hide'); @@ -90,19 +90,19 @@ define(['jQuery', 'loading', 'libraryMenu', 'globalize'], function ($, loading, } function saveUser(user, page) { - user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).checked; + user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked'); user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : $('.chkFolder', page).get().filter(function (c) { return c.checked; }).map(function (c) { return c.getAttribute('data-id'); }); - user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).checked; + user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked'); user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : $('.chkChannel', page).get().filter(function (c) { return c.checked; }).map(function (c) { return c.getAttribute('data-id'); }); - user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).checked; + user.Policy.EnableAllDevices = $('#chkEnableAllDevices', page).is(':checked'); user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $('.chkDevice', page).get().filter(function (c) { return c.checked; }).map(function (c) { diff --git a/src/controllers/dashboard/users/usernew.js b/src/controllers/dashboard/users/usernew.js index 1e4e2bbee2..ef4cd74f86 100644 --- a/src/controllers/dashboard/users/usernew.js +++ b/src/controllers/dashboard/users/usernew.js @@ -13,7 +13,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading html += '
'; $('.folderAccess', page).html(html).trigger('create'); - $('#chkEnableAllFolders', page).checked = false; + $('#chkEnableAllFolders', page).prop('checked', false); } function loadChannels(page, channels) { @@ -35,7 +35,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading $('.channelAccessContainer', page).hide(); } - $('#chkEnableAllChannels', page).checked = false; + $('#chkEnableAllChannels', page).prop('checked', false); } function loadUser(page) { @@ -58,7 +58,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading user.Name = $('#txtUsername', page).val(); user.Password = $('#txtPassword', page).val(); ApiClient.createUser(user).then(function (user) { - user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).checked; + user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked'); user.Policy.EnabledFolders = []; if (!user.Policy.EnableAllFolders) { @@ -69,7 +69,7 @@ define(['jQuery', 'loading', 'globalize', 'emby-checkbox'], function ($, loading }); } - user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).checked; + user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked'); user.Policy.EnabledChannels = []; if (!user.Policy.EnableAllChannels) { diff --git a/src/controllers/itemDetails.js b/src/controllers/itemDetails.js index cbb8a1b43e..c9b6b7fc1c 100644 --- a/src/controllers/itemDetails.js +++ b/src/controllers/itemDetails.js @@ -141,29 +141,29 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti return; } - playbackManager.getPlaybackMediaSources(item).then(function (mediaSources) { - instance._currentPlaybackMediaSources = mediaSources; - page.querySelector('.trackSelections').classList.remove('hide'); - select.setLabel(globalize.translate('LabelVersion')); - var currentValue = select.value; - var selectedId = mediaSources[0].Id; - select.innerHTML = mediaSources.map(function (v) { - var selected = v.Id === selectedId ? ' selected' : ''; - return ''; - }).join(''); + var mediaSources = item.MediaSources; + instance._currentPlaybackMediaSources = mediaSources; + page.querySelector('.trackSelections').classList.remove('hide'); + select.setLabel(globalize.translate('LabelVersion')); + var currentValue = select.value; + var selectedId = mediaSources[0].Id; + select.innerHTML = mediaSources.map(function (v) { + var selected = v.Id === selectedId ? ' selected' : ''; + return ''; + }).join(''); - if (mediaSources.length > 1) { - page.querySelector('.selectSourceContainer').classList.remove('hide'); - } else { - page.querySelector('.selectSourceContainer').classList.add('hide'); - } + if (mediaSources.length > 1) { + page.querySelector('.selectSourceContainer').classList.remove('hide'); + } else { + page.querySelector('.selectSourceContainer').classList.add('hide'); + } + + if (select.value !== currentValue || forceReload) { + renderVideoSelections(page, mediaSources); + renderAudioSelections(page, mediaSources); + renderSubtitleSelections(page, mediaSources); + } - if (select.value !== currentValue || forceReload) { - renderVideoSelections(page, mediaSources); - renderAudioSelections(page, mediaSources); - renderSubtitleSelections(page, mediaSources); - } - }); } function renderVideoSelections(page, mediaSources) { @@ -400,6 +400,7 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } else if (item.Album) { parentNameHtml.push(item.Album); } + // FIXME: This whole section needs some refactoring, so it becames easier to scale across all form factors. See GH #1022 var html = ''; var tvShowHtml = parentNameHtml[0]; @@ -415,9 +416,9 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } } else { if (layoutManager.mobile) { - html = '

' + parentNameHtml.join('
') + '

'; + html = '

' + parentNameHtml.join('
') + '

'; } else { - html = '

' + tvShowHtml + '

'; + html = '

' + tvShowHtml + '

'; } } } @@ -425,20 +426,19 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti var name = itemHelper.getDisplayName(item, { includeParentInfo: false }); - var offset = parentNameLast ? '.25em' : '.5em'; if (html && !parentNameLast) { if (!layoutManager.mobile && tvSeasonHtml) { - html += '

' + tvSeasonHtml + ' - ' + name + '

'; + html += '

' + tvSeasonHtml + ' - ' + name + '

'; } else { - html += '

' + name + '

'; + html += '

' + name + '

'; } } else { - html = '

' + name + '

' + html; + html = '

' + name + '

' + html; } if (item.OriginalTitle && item.OriginalTitle != item.Name) { - html += '

' + item.OriginalTitle + '

'; + html += '

' + item.OriginalTitle + '

'; } container.innerHTML = html; @@ -1106,10 +1106,10 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti var externalLinksElem = page.querySelector('.itemExternalLinks'); renderOverview([overview], item); + var i; var itemMiscInfo; itemMiscInfo = page.querySelectorAll('.itemMiscInfo-primary'); - for (i = 0; i < itemMiscInfo.length; i++) { mediaInfo.fillPrimaryMediaInfo(itemMiscInfo[i], item, { interactive: true, @@ -1125,7 +1125,6 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti } itemMiscInfo = page.querySelectorAll('.itemMiscInfo-secondary'); - for (i = 0; i < itemMiscInfo.length; i++) { mediaInfo.fillSecondaryMediaInfo(itemMiscInfo[i], item, { interactive: true @@ -1840,7 +1839,8 @@ define(['loading', 'appRouter', 'layoutManager', 'connectionManager', 'userSetti chaptercardbuilder.buildChapterCards(item, chapters, { itemsContainer: scenesContent, backdropShape: 'overflowBackdrop', - squareShape: 'overflowSquare' + squareShape: 'overflowSquare', + imageBlurhashes: item.ImageBlurHashes }); }); } else { diff --git a/src/controllers/list.js b/src/controllers/list.js index d05616ec9d..401df3f4d1 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -386,7 +386,7 @@ define(['globalize', 'listView', 'layoutManager', 'userSettings', 'focusManager' var instance = this; require(['playlistEditor'], function (playlistEditor) { - new playlistEditor().show({ + new playlistEditor.showEditor({ items: [], serverId: instance.params.serverId }); diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 7f22d3dd13..62906d9d21 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -86,7 +86,7 @@ define(['cardBuilder', 'imageLoader', 'libraryBrowser', 'loading', 'events', 'us } function showFilterMenu(context) { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(), mode: 'livetvchannels', diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index 82d0b697db..c6daf53a4f 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -155,8 +155,8 @@ define(['jQuery', 'globalize', 'scripts/taskbutton', 'dom', 'libraryMenu', 'layo } function mapChannels(page, providerId) { - require(['components/channelMapper/channelMapper'], function (channelmapper) { - new channelmapper({ + require(['components/channelMapper/channelMapper'], function (channelMapper) { + new channelMapper.default({ serverId: ApiClient.serverInfo().Id, providerId: providerId }).show(); diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index e9ae599b92..65abca46e0 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -171,7 +171,12 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB } if (!result.Items.length) { - html = '

' + globalize.translate('MessageNoCollectionsAvailable') + '

'; + html = ''; + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + globalize.translate('MessageNoCollectionsAvailable') + '

'; + html += '
'; } var itemsContainer = tabContent.querySelector('.itemsContainer'); @@ -237,7 +242,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB tabContent.querySelector('.btnNewCollection').addEventListener('click', function () { require(['collectionEditor'], function (collectionEditor) { var serverId = ApiClient.serverInfo().Id; - new collectionEditor().show({ + new collectionEditor.showEditor({ items: [], serverId: serverId }); diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js index e8e49ff9da..ab410c1bd4 100644 --- a/src/controllers/movies/moviegenres.js +++ b/src/controllers/movies/moviegenres.js @@ -165,6 +165,15 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader html += '
'; } + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + globalize.translate('MessageNoGenresAvailable') + '

'; + html += '
'; + } + elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index 89bcc215e6..c22b52c47e 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -270,7 +270,7 @@ define(['loading', 'layoutManager', 'userSettings', 'events', 'libraryBrowser', query = userSettings.loadQuerySettings(savedQueryKey, query); self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: query, mode: 'movies', diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js index 25d41d4fba..5daad8d7c3 100644 --- a/src/controllers/movies/movietrailers.js +++ b/src/controllers/movies/movietrailers.js @@ -158,7 +158,12 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' } if (!result.Items.length) { - html = '

' + globalize.translate('MessageNoTrailersFound') + '

'; + html = ''; + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + globalize.translate('MessageNoTrailersFound') + '

'; + html += '
'; } var itemsContainer = tabContent.querySelector('.itemsContainer'); @@ -180,7 +185,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'movies', diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index ecb51f9dc3..645daf4ad9 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -186,7 +186,7 @@ define(['layoutManager', 'playbackManager', 'loading', 'events', 'libraryBrowser var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(), mode: 'albums', diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index bd9341be6d..7a889ff8b9 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -170,7 +170,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: self.mode, diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 8e50cd720c..aa63ec51fe 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -124,7 +124,7 @@ define(['events', 'libraryBrowser', 'imageLoader', 'listView', 'loading', 'userS var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'songs', diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index ca9a807cbf..eeede20661 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -164,7 +164,7 @@ define(['loading', 'events', 'libraryBrowser', 'imageLoader', 'listView', 'cardB var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'episodes', diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index de38763e99..7d09307fc2 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -161,6 +161,15 @@ define(['layoutManager', 'loading', 'libraryBrowser', 'cardBuilder', 'lazyLoader html += '
'; } + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

' + globalize.translate('MessageNothingHere') + '

'; + html += '

' + globalize.translate('MessageNoGenresAvailable') + '

'; + html += '
'; + } + elem.innerHTML = html; lazyLoader.lazyChildren(elem, fillItemsContainer); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 753516f902..0bd5e4b52e 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -197,7 +197,7 @@ define(['layoutManager', 'loading', 'events', 'libraryBrowser', 'imageLoader', ' var isLoading = false; self.showFilterMenu = function () { - require(['components/filterdialog/filterdialog'], function (filterDialogFactory) { + require(['components/filterdialog/filterdialog'], function ({default: filterDialogFactory}) { var filterDialog = new filterDialogFactory({ query: getQuery(tabContent), mode: 'series', diff --git a/src/controllers/user/display.js b/src/controllers/user/display.js index 3aeb7db8ce..26c75f209a 100644 --- a/src/controllers/user/display.js +++ b/src/controllers/user/display.js @@ -1,6 +1,9 @@ define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySettings, userSettings, autoFocuser) { 'use strict'; + // Shortcuts + const UserSettings = userSettings.UserSettings; + return function (view, params) { function onBeforeUnload(e) { if (hasChanges) { @@ -11,7 +14,7 @@ define(['displaySettings', 'userSettings', 'autoFocuser'], function (DisplaySett var settingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); view.addEventListener('viewshow', function () { window.addEventListener('beforeunload', onBeforeUnload); diff --git a/src/controllers/user/home.js b/src/controllers/user/home.js index aa7d147c31..8f826c425d 100644 --- a/src/controllers/user/home.js +++ b/src/controllers/user/home.js @@ -1,6 +1,9 @@ define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (HomescreenSettings, dom, globalize, loading, userSettings, autoFocuser) { 'use strict'; + // Shortcuts + const UserSettings = userSettings.UserSettings; + return function (view, params) { function onBeforeUnload(e) { if (hasChanges) { @@ -11,7 +14,7 @@ define(['homescreenSettings', 'dom', 'globalize', 'loading', 'userSettings', 'au var homescreenSettingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); view.addEventListener('viewshow', function () { window.addEventListener('beforeunload', onBeforeUnload); diff --git a/src/controllers/user/menu.js b/src/controllers/user/menu.js index df5864dbdb..586cd6744c 100644 --- a/src/controllers/user/menu.js +++ b/src/controllers/user/menu.js @@ -43,12 +43,6 @@ define(['apphost', 'connectionManager', 'layoutManager', 'listViewStyle', 'emby- page.querySelector('.adminSection').classList.add('hide'); } - if (layoutManager.mobile) { - page.querySelector('.headerUsername').classList.add('hide'); - page.querySelector('.adminSection').classList.add('hide'); - page.querySelector('.userSection').classList.add('hide'); - } - ApiClient.getUser(userId).then(function(user) { page.querySelector('.headerUsername').innerHTML = user.Name; if (!user.Policy.IsAdministrator) { diff --git a/src/controllers/user/playback.js b/src/controllers/user/playback.js index e945a46aab..02a718eb8c 100644 --- a/src/controllers/user/playback.js +++ b/src/controllers/user/playback.js @@ -1,6 +1,9 @@ define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'autoFocuser', 'listViewStyle'], function (PlaybackSettings, dom, globalize, loading, userSettings, autoFocuser) { 'use strict'; + // Shortcuts + const UserSettings = userSettings.UserSettings; + return function (view, params) { function onBeforeUnload(e) { if (hasChanges) { @@ -11,7 +14,7 @@ define(['playbackSettings', 'dom', 'globalize', 'loading', 'userSettings', 'auto var settingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); view.addEventListener('viewshow', function () { window.addEventListener('beforeunload', onBeforeUnload); diff --git a/src/controllers/user/subtitles.js b/src/controllers/user/subtitles.js index 152301f31a..7e7e7fb8a9 100644 --- a/src/controllers/user/subtitles.js +++ b/src/controllers/user/subtitles.js @@ -1,6 +1,9 @@ define(['subtitleSettings', 'userSettings', 'autoFocuser'], function (SubtitleSettings, userSettings, autoFocuser) { 'use strict'; + // Shortcuts + const UserSettings = userSettings.UserSettings; + return function (view, params) { function onBeforeUnload(e) { if (hasChanges) { @@ -11,7 +14,7 @@ define(['subtitleSettings', 'userSettings', 'autoFocuser'], function (SubtitleSe var subtitleSettingsInstance; var hasChanges; var userId = params.userId || ApiClient.getCurrentUserId(); - var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new userSettings(); + var currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); view.addEventListener('viewshow', function () { window.addEventListener('beforeunload', onBeforeUnload); diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index 03ba2b93aa..1cef349bf0 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -109,9 +109,7 @@ define(['layoutManager', 'browser', 'dom', 'css!./emby-input', 'registerElement' } EmbyInputPrototype.attachedCallback = function () { - this.labelElement.htmlFor = this.id; - onChange.call(this); }; diff --git a/src/encodingsettings.html b/src/encodingsettings.html index 8971957b71..858375b145 100644 --- a/src/encodingsettings.html +++ b/src/encodingsettings.html @@ -19,6 +19,7 @@ +
${LabelHardwareAccelerationTypeHelp} @@ -35,36 +36,49 @@

${LabelEnableHardwareDecodingFor}

+
+ +
+
+ +
+