diff --git a/.escheckrc b/.escheckrc index 90d6edddb9..195d28b9fc 100644 --- a/.escheckrc +++ b/.escheckrc @@ -6,7 +6,6 @@ "./dist/libraries/pdf.worker.js", "./dist/libraries/worker-bundle.js", "./dist/libraries/wasm-gen/libarchive.js", - "./dist/node_modules.@jellyfin.libass-wasm.*.chunk.js", "./dist/serviceworker.js" ] } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 186dbcd12c..9f8563f6a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,2 @@ -.ci @dkanada @EraYaN +* @jellyfin/web .github @jellyfin/core -fedora @joshuaboniface -debian @joshuaboniface -.copr @joshuaboniface -deployment @joshuaboniface diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a607dd0da6..3748ee8324 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -19,13 +19,13 @@ jobs: language: [ 'javascript' ] steps: - name: Checkout repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Initialize CodeQL - uses: github/codeql-action/init@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9 + uses: github/codeql-action/init@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9 + uses: github/codeql-action/autobuild@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@04df1262e6247151b5ac09cd2c303ac36ad3f62b # v2.2.9 + uses: github/codeql-action/analyze@7df0ce34898d659f95c0c4a09eaa8d4e32ee64db # v2.2.12 diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 2f10d09e03..20ec075c5b 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -12,13 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Notify as seen - uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2.1.1 + uses: peter-evans/create-or-update-comment@3383acd359705b10cb1eeef05c0e88c056ea4666 # v3.0.0 with: token: ${{ secrets.JF_BOT_TOKEN }} comment-id: ${{ github.event.comment.id }} reactions: '+1' - name: Checkout the latest code - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 with: token: ${{ secrets.JF_BOT_TOKEN }} fetch-depth: 0 @@ -26,3 +26,11 @@ jobs: uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8 env: GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }} + - name: Comment on failure + if: failure() + uses: peter-evans/create-or-update-comment@3383acd359705b10cb1eeef05c0e88c056ea4666 # v3.0.0 + with: + token: ${{ secrets.JF_BOT_TOKEN }} + issue-number: ${{ github.event.issue.number }} + body: | + I'm sorry @${{ github.event.comment.user.login }}, I'm afraid I can't do that. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8a9f26a3cf..2f6e8a3b06 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup node environment uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -37,7 +37,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup node environment uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -58,7 +58,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup node environment uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 @@ -82,7 +82,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup node environment uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index 8c2686a7ed..bd9975f12c 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - name: Setup node environment uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index f3dbfec6da..0f2420a648 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -60,6 +60,8 @@ - [edvwib](https://github.com/edvwib) - [Rob Farraher](https://github.com/farraherbg) - [Pier-Luc Ducharme](https://github.com/pl-ducharme) + - [Anantharaju S](https://github.com/Anantharajus) + - [Merlin Sievers](https://github.com/dann-merlin) # Emby Contributors diff --git a/babel.config.js b/babel.config.js index fcd9dcb4ac..e31ea23126 100644 --- a/babel.config.js +++ b/babel.config.js @@ -12,14 +12,7 @@ module.exports = { corejs: 3 } ], - '@babel/preset-react', - [ - '@babel/preset-typescript', - { - isTSX: true, - allExtensions: true - } - ] + '@babel/preset-react' ], plugins: [ '@babel/plugin-proposal-class-properties', diff --git a/deployment/Dockerfile.centos b/deployment/Dockerfile.centos index 3d64fbf640..566180ad22 100644 --- a/deployment/Dockerfile.centos +++ b/deployment/Dockerfile.centos @@ -12,7 +12,7 @@ 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 autoconf automake glibc-devel gcc-c++ make \ + && yum install -y rpmdevtools git autoconf automake glibc-devel gcc-c++ make \ && curl -fsSL https://rpm.nodesource.com/setup_16.x | bash - \ && yum install -y nodejs diff --git a/package-lock.json b/package-lock.json index 95d298c710..f27a00c8ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,23 +15,24 @@ "@fontsource/noto-sans-kr": "4.5.12", "@fontsource/noto-sans-sc": "4.5.12", "@fontsource/noto-sans-tc": "4.5.12", - "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "unstable", "@loadable/component": "5.15.3", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.29.1", + "core-js": "3.30.0", "date-fns": "2.29.3", "dompurify": "3.0.1", "epubjs": "0.3.93", "escape-html": "1.0.3", + "event-target-polyfill": "github:ThaUnknown/event-target-polyfill", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", "headroom.js": "0.12.0", "history": "5.3.0", - "hls.js": "1.2.4", + "hls.js": "1.4.0", "intersection-observer": "0.12.2", + "jassub": "1.5.12", "jellyfin-apiclient": "1.10.0", "jquery": "3.6.4", "jstree": "3.3.15", @@ -40,10 +41,10 @@ "marked": "4.3.0", "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", - "pdfjs-dist": "2.16.105", + "pdfjs-dist": "3.5.141", "react": "17.0.2", "react-dom": "17.0.2", - "react-router-dom": "6.9.0", + "react-router-dom": "6.10.0", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", "sortablejs": "1.15.0", @@ -54,22 +55,21 @@ "workbox-precaching": "6.5.4" }, "devDependencies": { - "@babel/core": "7.21.3", + "@babel/core": "7.21.4", "@babel/eslint-parser": "7.21.3", "@babel/eslint-plugin": "7.19.1", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-transform-modules-umd": "7.18.6", - "@babel/preset-env": "7.20.2", + "@babel/preset-env": "7.21.4", "@babel/preset-react": "7.18.6", - "@babel/preset-typescript": "7.21.0", "@types/escape-html": "1.0.2", "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.7", - "@types/react": "17.0.53", + "@types/react": "17.0.58", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.56.0", - "@typescript-eslint/parser": "5.56.0", + "@typescript-eslint/eslint-plugin": "5.58.0", + "@typescript-eslint/parser": "5.58.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.14", "babel-loader": "9.1.2", @@ -79,10 +79,10 @@ "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", "css-loader": "6.7.3", - "cssnano": "5.1.15", - "es-check": "7.1.0", - "eslint": "8.36.0", - "eslint-plugin-compat": "4.1.2", + "cssnano": "6.0.0", + "es-check": "7.1.1", + "eslint": "8.38.0", + "eslint-plugin-compat": "4.1.4", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", @@ -95,23 +95,23 @@ "html-webpack-plugin": "5.5.0", "mini-css-extract-plugin": "2.7.5", "postcss": "8.4.21", - "postcss-loader": "7.1.0", - "postcss-preset-env": "8.0.1", + "postcss-loader": "7.2.4", + "postcss-preset-env": "8.3.1", "postcss-scss": "4.0.6", - "sass": "1.60.0", - "sass-loader": "13.2.1", + "sass": "1.62.0", + "sass-loader": "13.2.2", "source-map-loader": "4.0.1", "style-loader": "3.3.2", - "stylelint": "15.3.0", + "stylelint": "15.4.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.3", - "stylelint-scss": "4.5.0", + "stylelint-scss": "4.6.0", "ts-loader": "9.4.2", - "typescript": "5.0.2", - "webpack": "5.76.3", + "typescript": "5.0.4", + "webpack": "5.79.0", "webpack-cli": "5.0.1", - "webpack-dev-server": "4.13.1", + "webpack-dev-server": "4.13.2", "webpack-merge": "5.8.0", "workbox-webpack-plugin": "6.5.4", "worker-loader": "3.0.8" @@ -175,9 +175,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "dependencies": { "@babel/highlight": "^7.18.6" @@ -187,30 +187,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-module-transforms": "^7.21.2", "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", + "@babel/parser": "^7.21.4", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -260,12 +260,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "dev": true, "dependencies": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -300,13 +300,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -356,13 +356,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", + "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" + "regexpu-core": "^5.3.1" }, "engines": { "node": ">=6.9.0" @@ -640,9 +640,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -667,14 +667,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -718,13 +718,13 @@ } }, "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -783,12 +783,12 @@ } }, "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -866,13 +866,13 @@ } }, "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -899,14 +899,14 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1139,28 +1139,13 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1170,14 +1155,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" }, "engines": { "node": ">=6.9.0" @@ -1202,9 +1187,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1217,15 +1202,15 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-compilation-targets": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-optimise-call-expression": "^7.18.6", "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-replace-supers": "^7.20.7", @@ -1240,12 +1225,13 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -1255,9 +1241,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1317,12 +1303,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", + "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1395,12 +1381,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", + "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-module-transforms": "^7.21.2", "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-simple-access": "^7.20.2" }, @@ -1446,13 +1432,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", "dev": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1493,9 +1479,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1588,13 +1574,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" }, "engines": { "node": ">=6.9.0" @@ -1634,13 +1620,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" }, "engines": { "node": ">=6.9.0" @@ -1694,23 +1680,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz", - "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", @@ -1743,31 +1712,31 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", + "@babel/helper-validator-option": "^7.21.0", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", "@babel/plugin-proposal-dynamic-import": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1784,40 +1753,40 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", "@babel/plugin-transform-dotall-regex": "^7.18.6", "@babel/plugin-transform-duplicate-keys": "^7.18.9", "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-for-of": "^7.21.0", "@babel/plugin-transform-function-name": "^7.18.9", "@babel/plugin-transform-literals": "^7.18.9", "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", "@babel/plugin-transform-new-target": "^7.18.6", "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-parameters": "^7.21.3", "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", "@babel/plugin-transform-reserved-words": "^7.18.6", "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-spread": "^7.20.7", "@babel/plugin-transform-sticky-regex": "^7.18.6", "@babel/plugin-transform-template-literals": "^7.18.9", "@babel/plugin-transform-typeof-symbol": "^7.18.9", "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.4", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -1867,22 +1836,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true }, "node_modules/@babel/runtime": { "version": "7.20.7", @@ -1910,19 +1868,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1931,9 +1889,9 @@ } }, "node_modules/@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.19.4", @@ -1953,10 +1911,34 @@ "node": ">=0.1.90" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.1.tgz", - "integrity": "sha512-SAAi5DpgJJWkfTvWSaqkgyIsTawa83hMwKrktkj6ra2h+q6ZN57vOGZ6ySHq6RSo+CbP64fA3aPChPBRDDUgtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.2.tgz", + "integrity": "sha512-xm7Mgwej/wBfLoK0K5LfntmPJzoULayl1XZY9JYgQgT29JiqNw++sLnx95u5y9zCihblzkyaRYJrsRMhIBzRdg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -1966,14 +1948,14 @@ "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0" + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" } }, "node_modules/@csstools/color-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-1.0.0.tgz", - "integrity": "sha512-tgqtiV8sU/VaWYjOB3O7PWs7HR/MmOLl2kTYRW2qSsTSEniJq7xmyAYFB1LPpXvvQcE5u2ih2dK9fyc8BnrAGQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.0.0.tgz", + "integrity": "sha512-VcPjEnp07RNgz/D+oI2uIALg+IPCSl6mj0XhA3pl3F2bM2B95vgzatExmmzSg/X0zkh+R2v+jFY/J2pV/bnwpw==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -1983,10 +1965,10 @@ "url": "https://opencollective.com/csstools" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz", - "integrity": "sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==", + "node_modules/@csstools/css-calc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.1.tgz", + "integrity": "sha512-Nh+iLCtjlooTzuR0lpmB8I6hPX/VupcGQ3Z1U2+wgJJ4fa8+cWkub+lCsbZcYPzBGsZLEL8fQAg+Na5dwEFJxg==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -1996,13 +1978,51 @@ "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@csstools/css-tokenizer": "^2.0.0" + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.1.2.tgz", + "integrity": "sha512-MjW/VspbFSkvbuou7tUUu2+FAlAR7VJ/PA69M9EGKltThbONC8nyW33wHRzNvLzRLGstZLEO5X5oR7IMhMDi0A==", + "dev": true, + "dependencies": { + "@csstools/color-helpers": "^2.0.0", + "@csstools/css-calc": "^1.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz", + "integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==", + "dev": true, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^2.1.1" } }, "node_modules/@csstools/css-tokenizer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.0.tgz", - "integrity": "sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz", + "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -2013,9 +2033,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz", - "integrity": "sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz", + "integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -2025,8 +2045,8 @@ "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0" + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" } }, "node_modules/@csstools/postcss-cascade-layers": { @@ -2050,13 +2070,37 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.0.1.tgz", - "integrity": "sha512-d7379loVBgIiKTQMOUduUctq3CWMeqNpGkLhzuejvuGyA+bWYT1p7n2GzmIwgXwP0CF8DIFtDgvrsvHn3i+tWw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.2.1.tgz", + "integrity": "sha512-T52iiqmzyKk09B9iNTQbuWa9Tn83SztXY7o6r2+j+o1BS/7+CiCjTgN2HgzybDmx8cr6XYhQ1BzqgV9tJzhrmw==", "dev": true, "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-1.0.1.tgz", + "integrity": "sha512-NSVrzjVcI4TMzDfh6GKZXvEuelT81xpXzruuTNJrwKMTZXEBHY9G2gvmr0eC0wwmL8EF1TblXyPPfBbZobvfXg==", + "dev": true, + "dependencies": { + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2070,9 +2114,9 @@ } }, "node_modules/@csstools/postcss-font-format-keywords": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.1.tgz", - "integrity": "sha512-NRwT5g/L+lDkridDiHfjNGyHvdSHJOdcXPPZXZOpSfr/AwRxTJ+wsbKAzyBb1stalkr9KjICDr+ofpkk96r0Wg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.2.tgz", + "integrity": "sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -2088,14 +2132,43 @@ "postcss": "^8.4" } }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-3.0.4.tgz", + "integrity": "sha512-GgKoY7OlvL65UPigEdlrvMAUCR5kOQCjtue2/36TPrBNoRS6KM2KOqmjIVsxEwYYwK+L28pdnM8r10m03hhZxA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "dependencies": { + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, "node_modules/@csstools/postcss-hwb-function": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.1.0.tgz", - "integrity": "sha512-B4uBMznejB5VM7rMB2C3KQdM3JwPAy3CxbI9DIMziCZzlaB1a59uV7NimuINndumgtzpVt++CdpY0XffURZ+eA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.2.1.tgz", + "integrity": "sha512-eiqB4DIs+xqProAly7KwIgE04oze1YHb0QSCw/Y7062d9gpw+Cdif3Y0Z+Te+U7JROYdO0/0j91A6Qy8fo/Rlw==", "dev": true, "dependencies": { - "@csstools/color-helpers": "^1.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2109,9 +2182,9 @@ } }, "node_modules/@csstools/postcss-ic-unit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.1.tgz", - "integrity": "sha512-718aUIKZJDkbQrINOv6B0I70EZpTB9LzPykGVE/U3gnlXc4tjgvr6/r/G3Hopyn1D5R4BJYcMPI06tVzAgLSMQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.2.tgz", + "integrity": "sha512-N84qGTJkfLTPj2qOG5P4CIqGjpZBbjOEMKMn+UjO5wlb9lcBTfBsxCF0lQsFdWJUzBHYFOz19dL66v71WF3Pig==", "dev": true, "dependencies": { "@csstools/postcss-progressive-custom-properties": "^2.0.0", @@ -2129,9 +2202,9 @@ } }, "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.1.0.tgz", - "integrity": "sha512-MSt4kjWvIuv7GWGEV2WNkcOTLXdYpBRbW/2YF9MAmrKKYui65P/LpsLfSwCWq4vdwWH1HSxFi4Qp6bGCEAZ8ag==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.2.0.tgz", + "integrity": "sha512-uooelBL99jMg8ZD6xy0Pj1hSalchWmplcin00eI+JHpv1jW2iwbi1cn2UnVsToM4JLwJSZFzTSWCgSpmlyhe3A==", "dev": true, "dependencies": { "@csstools/selector-specificity": "^2.0.0", @@ -2184,12 +2257,34 @@ } }, "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.2.tgz", - "integrity": "sha512-nnKFywBqRMYjv5jyjSplD/nbAnboUEGFfdxKw1o34Y1nvycgqjQavhKkmxbORxroBBIDwC5y6SfgENcPPUcOxQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.3.tgz", + "integrity": "sha512-6zqcyRg9HSqIHIPMYdt6THWhRmE5/tyHKJQLysn2TeDf/ftq7Em9qwMTx98t2C/7UxIsYS8lOiHHxAVjWn2WUg==", "dev": true, "dependencies": { - "@csstools/css-tokenizer": "^2.0.0" + "@csstools/css-tokenizer": "^2.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.2.tgz", + "integrity": "sha512-DsEykSINZTqlBefi1uSQBym1Rj0NQOj92dLRd5jUQpSy8yBVaXXmkiUgBUbb+gQh8imAdqPpz2v4sAUnw8yXXA==", + "dev": true, + "dependencies": { + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2203,14 +2298,14 @@ } }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.1.tgz", - "integrity": "sha512-V9yQqXdje6OfqDf6EL5iGOpi6N0OEczwYK83rql9UapQwFEryXlAehR5AqH8QqLYb6+y31wUXK6vMxCp0920Zg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.2.tgz", + "integrity": "sha512-rOSR5p+5m0joXUoitYgCyMqNCu97yfLsLG3cnNaM8VeJRCWHGEu5hE9Gv0M7n9A4wo2pYF8QqaxkTlWbSJY9Fg==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", - "@csstools/media-query-list-parser": "^2.0.0" + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2224,9 +2319,9 @@ } }, "node_modules/@csstools/postcss-nested-calc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.1.tgz", - "integrity": "sha512-6C5yoF99zFb/C2Sa9Y5V0Y/2dnrjK5xe+h59L0LfdVhfanmJPrttwmfTua9etFRA1TGV46aoVMLEZ1NoHjWikg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.2.tgz", + "integrity": "sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -2262,13 +2357,15 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.0.1.tgz", - "integrity": "sha512-MTj3w6G1TYW0k43sXjw25fY/S+LHXpFIym5NW0oO/hjHFzuz5Uwz93aUvdo/UrrFmxSQeQAYCxmq6NlH3Pf1Hw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.2.1.tgz", + "integrity": "sha512-g4wrVopp6xXr1KetUK4Lj36P+PFPwvUUtd2gaqo7X/0xgJHmMtKMPhD9p77H9bmIpPtkIYQ8b7+7cdmrWNEVAw==", "dev": true, "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2301,9 +2398,9 @@ } }, "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.1.tgz", - "integrity": "sha512-wf2dcsqSQlBHc4HMMqdXdxDx4uYuqH+L08kKj+pmT+743C06STcUEu7ORFFEnqGWlOJ1kmA5BJ3pQU0EdMuA+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.2.tgz", + "integrity": "sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -2320,12 +2417,14 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.0.1.tgz", - "integrity": "sha512-VimD+M69GsZF/XssivjUwo6jXLgi86ar/gRSH7bautnCULSLxCr/HuY32N4rLRUr7qWF8oF/JTv06ceb66Q1jA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.1.tgz", + "integrity": "sha512-YCvdF0GCZK35nhLgs7ippcxDlRVe5QsSht3+EghqTjnYnyl3BbWIN6fYQ1dKWYTJ+7Bgi41TgqQFfJDcp9Xy/w==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2339,12 +2438,12 @@ } }, "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.0.tgz", - "integrity": "sha512-++RtufygqFaeheLH1g8Y2Oi/l+xSt61+c0lQGjrpLUW4hhFo77V4Zsshm0oof7lqnpVXPaizlU0SnXf+f4GA7A==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.3.tgz", + "integrity": "sha512-PADJidg/tdhDk120aWlGuDxsp5ZTc9Nx7GhJ8t0qBCk5fOgLK6V3DsB9X6sOAhDokIihXKzjtUu15puac5McWw==", "dev": true, "dependencies": { - "@csstools/color-helpers": "^1.0.0", + "@csstools/color-helpers": "^2.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -2359,12 +2458,14 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.0.1.tgz", - "integrity": "sha512-uGmmVWGHozyWe6+I4w321fKUC034OB1OYW0ZP4ySHA23n+r9y93K+1yrmW+hThpSfApKhaWySoD4I71LLlFUYQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.1.1.tgz", + "integrity": "sha512-XcXmHEFfHXhvYz40FtDlA4Fp4NQln2bWTsCwthd2c+MCnYArUYU3YaMqzR5CrKP3pMoGYTBnp5fMqf1HxItNyw==", "dev": true, "dependencies": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" }, "engines": { "node": "^14 || ^16 || >=18" @@ -2394,9 +2495,9 @@ } }, "node_modules/@csstools/selector-specificity": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", - "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -2406,7 +2507,6 @@ "url": "https://opencollective.com/csstools" }, "peerDependencies": { - "postcss": "^8.4", "postcss-selector-parser": "^6.0.10" } }, @@ -2464,14 +2564,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -2567,9 +2667,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2638,25 +2738,22 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "node_modules/@jellyfin/libass-wasm": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz", - "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" - }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202303130502", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz", - "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==", + "version": "0.0.0-unstable.202304122102", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202304122102.tgz", + "integrity": "sha512-KToOmK3GmbjovtFPgb3dYx8cV6bopo46fhTkHDnKLqsmwqBz5/QKk7Z8NbR+5YaojNAP4LUYnenZmMK9HQ2YeA==", "dependencies": { - "axios": "1.3.4", "compare-versions": "5.0.3" + }, + "peerDependencies": { + "axios": "^1.3.4" } }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2670,7 +2767,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2679,7 +2776,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2688,7 +2785,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -2698,13 +2795,13 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -2736,10 +2833,60 @@ "react": "^16.3.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "optional": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@mdn/browser-compat-data": { - "version": "5.2.36", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.36.tgz", - "integrity": "sha512-cS+xbp4jq+W04pFqw5639Grzj82JevJZst4b55Mk2NGc9wY7uXD6hlM6H0hkK5mLKXXZKsT1xq79W6LsSG4crw==", + "version": "5.2.51", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.51.tgz", + "integrity": "sha512-E72TUHMgX9+coOwyRf2C6OwAGlYgFS2UhpgokIYzzwrvnXDN8Qh7c+2L2KAnRcvf7RTZV52u8DhS4ajknyjlNw==", "dev": true }, "node_modules/@mrmlnc/readdir-enhanced": { @@ -2800,9 +2947,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", - "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", "engines": { "node": ">=14" } @@ -2907,6 +3054,33 @@ "node": ">=10.13.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -2955,7 +3129,7 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", - "devOptional": true, + "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2965,17 +3139,17 @@ "version": "3.7.3", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "devOptional": true, + "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "devOptional": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true }, "node_modules/@types/express": { "version": "4.17.13", @@ -3029,7 +3203,7 @@ "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "devOptional": true + "dev": true }, "node_modules/@types/json5": { "version": "0.0.29", @@ -3092,7 +3266,7 @@ "version": "14.14.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "devOptional": true + "dev": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -3119,9 +3293,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "version": "17.0.58", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz", + "integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -3236,15 +3410,15 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", - "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", + "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/type-utils": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/type-utils": "5.58.0", + "@typescript-eslint/utils": "5.58.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3270,9 +3444,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3285,14 +3459,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", - "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz", + "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", "debug": "^4.3.4" }, "engines": { @@ -3312,13 +3486,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", - "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz", + "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0" + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3329,13 +3503,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", - "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz", + "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/utils": "5.58.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3356,9 +3530,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", - "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz", + "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3369,13 +3543,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", - "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz", + "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3425,9 +3599,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3440,17 +3614,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", - "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz", + "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -3466,9 +3640,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3481,12 +3655,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", - "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz", + "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/types": "5.58.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3498,12 +3672,15 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@uupaa/dynamic-import-polyfill": { @@ -3516,7 +3693,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -3526,25 +3703,25 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3555,13 +3732,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3573,7 +3750,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "devOptional": true, + "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -3582,7 +3759,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "devOptional": true, + "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3591,13 +3768,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "devOptional": true + "dev": true }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3613,7 +3790,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -3626,7 +3803,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -3638,7 +3815,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -3652,7 +3829,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "devOptional": true, + "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -3714,13 +3891,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "devOptional": true + "dev": true }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "devOptional": true + "dev": true }, "node_modules/abab": { "version": "2.0.6", @@ -3728,6 +3905,12 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3745,7 +3928,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "devOptional": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -3757,7 +3940,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "devOptional": true, + "dev": true, "peerDependencies": { "acorn": "^8" } @@ -3771,6 +3954,28 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -3832,7 +4037,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -3862,6 +4067,46 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -4039,20 +4284,14 @@ } }, "node_modules/ast-metadata-inferer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.7.0.tgz", - "integrity": "sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz", + "integrity": "sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==", "dev": true, "dependencies": { - "@mdn/browser-compat-data": "^3.3.14" + "@mdn/browser-compat-data": "^5.2.34" } }, - "node_modules/ast-metadata-inferer/node_modules/@mdn/browser-compat-data": { - "version": "3.3.14", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-3.3.14.tgz", - "integrity": "sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==", - "dev": true - }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -4077,7 +4316,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "peer": true }, "node_modules/at-least-node": { "version": "1.0.0", @@ -4158,6 +4398,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "peer": true, "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4252,7 +4493,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true }, "node_modules/base": { "version": "0.11.2", @@ -4332,7 +4573,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "devOptional": true, + "dev": true, "engines": { "node": "*" } @@ -4467,7 +4708,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4489,7 +4730,7 @@ "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", @@ -4517,13 +4758,13 @@ "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "devOptional": true + "dev": true }, "node_modules/buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "devOptional": true + "dev": true }, "node_modules/builtin-modules": { "version": "3.2.0", @@ -4673,10 +4914,10 @@ "dev": true }, "node_modules/caniuse-lite": { - "version": "1.0.30001466", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", - "integrity": "sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==", - "devOptional": true, + "version": "1.0.30001480", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", + "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4685,9 +4926,28 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/ccount": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz", @@ -4779,11 +5039,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0" } @@ -4934,6 +5203,15 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -4960,6 +5238,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5051,7 +5330,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "devOptional": true }, "node_modules/confusing-browser-globals": { "version": "1.0.11", @@ -5068,6 +5347,12 @@ "node": ">=0.8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5210,9 +5495,9 @@ } }, "node_modules/core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", + "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5252,6 +5537,13 @@ "node": ">=4" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -5294,9 +5586,9 @@ } }, "node_modules/css-blank-pseudo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-5.0.1.tgz", - "integrity": "sha512-uEWT+613bR0lxUAz7BDdk4yZJ1BfzIJ9rmyOFj+p53ZP8rm0BC3nA2YsyswyxjFZsrfRDxe2WERDfKiEZNSXag==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-5.0.2.tgz", + "integrity": "sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -5334,9 +5626,9 @@ } }, "node_modules/css-has-pseudo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-5.0.1.tgz", - "integrity": "sha512-zhsGCKVkBohliMlcsZsv5WF/i4FQ3pkVMtl4yYa7Qpv/PVQebTjh7cjMoT5grW+DBZzunmgHe6skdWawgCYuPQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-5.0.2.tgz", + "integrity": "sha512-q+U+4QdwwB7T9VEW/LyO6CFrLAeLqOykC5mDqJXc7aKZAhDbq7BvGT13VGJe+IwBfdN2o3Xdw2kJ5IxwV1Sc9Q==", "dev": true, "dependencies": { "@csstools/selector-specificity": "^2.0.1", @@ -5396,9 +5688,9 @@ } }, "node_modules/css-prefers-color-scheme": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-8.0.1.tgz", - "integrity": "sha512-RPRyqJsk5KIjP2+WGhcGCaAJB8ojLbX1mVE8fG9127jQmnp1FNMfNMkERk/w6c4smgC/i5KxcY+Rtaa6/bMdKQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-8.0.2.tgz", + "integrity": "sha512-OvFghizHJ45x7nsJJUSYLyQNTzsCU8yWjxAc/nhPQg1pbs18LMoET8N3kOweFDPy0JV0OSXN2iqRFhPBHYOeMA==", "dev": true, "engines": { "node": "^14 || ^16 || >=18" @@ -5468,25 +5760,16 @@ } }, "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "dev": true, "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-tree/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { @@ -5502,9 +5785,9 @@ } }, "node_modules/cssdb": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz", - "integrity": "sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.5.4.tgz", + "integrity": "sha512-fGD+J6Jlq+aurfE1VDXlLS4Pt0VtNlu2+YgfGOdMxRyl/HQ9bDiHTwSck1Yz8A97Dt/82izSK6Bp/4nVqacOsg==", "dev": true, "funding": { "type": "opencollective", @@ -5524,17 +5807,16 @@ } }, "node_modules/cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.0.tgz", + "integrity": "sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==", "dev": true, "dependencies": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" + "cssnano-preset-default": "^6.0.0", + "lilconfig": "^2.1.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "funding": { "type": "opencollective", @@ -5545,72 +5827,93 @@ } }, "node_modules/cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.0.tgz", + "integrity": "sha512-BDxlaFzObRDXUiCCBQUNQcI+f1/aX2mgoNtXGjV6PG64POcHoDUoX+LgMWw+Q4609QhxwkcSnS65YFs42RA6qQ==", "dev": true, "dependencies": { "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" + "postcss-colormin": "^6.0.0", + "postcss-convert-values": "^6.0.0", + "postcss-discard-comments": "^6.0.0", + "postcss-discard-duplicates": "^6.0.0", + "postcss-discard-empty": "^6.0.0", + "postcss-discard-overridden": "^6.0.0", + "postcss-merge-longhand": "^6.0.0", + "postcss-merge-rules": "^6.0.0", + "postcss-minify-font-values": "^6.0.0", + "postcss-minify-gradients": "^6.0.0", + "postcss-minify-params": "^6.0.0", + "postcss-minify-selectors": "^6.0.0", + "postcss-normalize-charset": "^6.0.0", + "postcss-normalize-display-values": "^6.0.0", + "postcss-normalize-positions": "^6.0.0", + "postcss-normalize-repeat-style": "^6.0.0", + "postcss-normalize-string": "^6.0.0", + "postcss-normalize-timing-functions": "^6.0.0", + "postcss-normalize-unicode": "^6.0.0", + "postcss-normalize-url": "^6.0.0", + "postcss-normalize-whitespace": "^6.0.0", + "postcss-ordered-values": "^6.0.0", + "postcss-reduce-initial": "^6.0.0", + "postcss-reduce-transforms": "^6.0.0", + "postcss-svgo": "^6.0.0", + "postcss-unique-selectors": "^6.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz", + "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, "dependencies": { - "css-tree": "^1.1.2" + "css-tree": "~2.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" } }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, "node_modules/csstype": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz", @@ -5660,7 +5963,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -5713,6 +6016,18 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-equal": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", @@ -5872,10 +6187,17 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "peer": true, "engines": { "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -5895,12 +6217,31 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -5913,6 +6254,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -6003,11 +6350,6 @@ } ] }, - "node_modules/dommatrix": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", - "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" - }, "node_modules/dompurify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", @@ -6158,19 +6500,19 @@ "version": "1.4.302", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.302.tgz", "integrity": "sha512-Uk7C+7aPBryUR1Fwvk9VmipBcN9fVsqBO57jV2ZjTm+IZ6BMNqu7EDVEg2HxCNufk6QcWlFsBkhQyQroB2VWKw==", - "devOptional": true + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "devOptional": true }, "node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 4" } @@ -6194,7 +6536,7 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "devOptional": true, + "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -6289,9 +6631,9 @@ } }, "node_modules/es-check": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-7.1.0.tgz", - "integrity": "sha512-t099vm9tNqNHF28Q/mRcqYxmkbkoo/Qu2ZI5/D+eFeqNUjI3jwkIyHyexXiAtstbZ1FQELi0QCuUaYCtiffi4Q==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/es-check/-/es-check-7.1.1.tgz", + "integrity": "sha512-rgwR2wdJp437Exq28Emwc4x5+Qn6ORDliN9daWo0wTCg5jOQxJsIZieqxVi4AfDEIN4OwMwYhld9b13mnRocUQ==", "dev": true, "dependencies": { "acorn": "8.8.2", @@ -6367,10 +6709,10 @@ "dev": true }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "devOptional": true + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true }, "node_modules/es-shim-unscopables": { "version": "1.0.0", @@ -6436,7 +6778,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -6456,15 +6798,15 @@ } }, "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -6475,8 +6817,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -6576,21 +6918,22 @@ } }, "node_modules/eslint-plugin-compat": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.2.tgz", - "integrity": "sha512-DNrQgDi5L4mAL4FdFboKBlSRg6MWfd75eA7K91lMjtP5ryN+O11qT2FDn7Z6zqy6sZ4sJawUR5V75qzB6l0CBg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", + "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", "dev": true, "dependencies": { - "@mdn/browser-compat-data": "^5.2.34", - "ast-metadata-inferer": "^0.7.0", + "@mdn/browser-compat-data": "^5.2.47", + "@tsconfig/node14": "^1.0.3", + "ast-metadata-inferer": "^0.8.0", "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001451", + "caniuse-lite": "^1.0.30001473", "find-up": "^5.0.0", "lodash.memoize": "4.1.2", "semver": "7.3.8" }, "engines": { - "node": ">=16.x" + "node": ">=14.x" }, "peerDependencies": { "eslint": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" @@ -6910,7 +7253,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "devOptional": true, + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -7012,9 +7355,9 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -7022,15 +7365,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -7204,14 +7553,14 @@ } }, "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7221,12 +7570,15 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -7267,7 +7619,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -7279,7 +7631,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=4.0" } @@ -7288,7 +7640,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=4.0" } @@ -7326,6 +7678,11 @@ "es5-ext": "~0.10.14" } }, + "node_modules/event-target-polyfill": { + "version": "0.0.3", + "resolved": "git+ssh://git@github.com/ThaUnknown/event-target-polyfill.git#5cb9a0ed6774af1b905b525964316911375726a7", + "license": "MIT" + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -7336,7 +7693,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.8.x" } @@ -7673,7 +8030,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true + "dev": true }, "node_modules/fast-glob": { "version": "3.2.12", @@ -7695,7 +8052,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -7946,6 +8303,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -8013,6 +8371,30 @@ "node": ">=10" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -8023,7 +8405,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "devOptional": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -8078,6 +8460,26 @@ "integrity": "sha1-szmUr0V6gRVwDUEPMXczy+egkEs=", "dev": true }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -8148,7 +8550,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8291,7 +8693,7 @@ "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true + "dev": true }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -8404,6 +8806,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -8496,9 +8904,9 @@ } }, "node_modules/hls.js": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.4.tgz", - "integrity": "sha512-yC3K79Kzq1W+OgjT12JxKMDXv9DbfvulppxmPBl7D04SaTyd2IwWk5eNASQV1mUaPlKbjr16yI9292qpSGo0ig==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.0.tgz", + "integrity": "sha512-VEjg7Rx5FlE9TB3MIn0HPgq3J+vR7EoQnjaqMCk/ISEaCOSZlAFh4g867f1QkSxZiq9kHeUZo+iH16X7VS3jKA==" }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", @@ -8835,6 +9243,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -8963,7 +9384,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -9336,7 +9757,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -9679,6 +10100,14 @@ "node": ">=0.10.0" } }, + "node_modules/jassub": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/jassub/-/jassub-1.5.12.tgz", + "integrity": "sha512-CJiuNCXMMGqfmVVlaDyxqaKfOy3RIHW4HBwVWvbq8pl/d1/y1fgTarfR31whUUupHZCe7Tfq8XB7WDgdu6IHaA==", + "dependencies": { + "rvfc-polyfill": "^1.0.4" + } + }, "node_modules/jellyfin-apiclient": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/jellyfin-apiclient/-/jellyfin-apiclient-1.10.0.tgz", @@ -9691,7 +10120,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, + "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -9705,7 +10134,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } @@ -9714,7 +10143,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -9782,7 +10211,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true + "dev": true }, "node_modules/json-schema": { "version": "0.4.0", @@ -9806,7 +10235,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true, + "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -9966,9 +10395,9 @@ } }, "node_modules/lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true, "engines": { "node": ">=10" @@ -9984,7 +10413,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.11.5" } @@ -9993,7 +10422,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "devOptional": true, + "dev": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -10129,7 +10558,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -10150,7 +10579,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "devOptional": true, "dependencies": { "semver": "^6.0.0" }, @@ -10161,6 +10590,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -10255,9 +10691,9 @@ } }, "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, "node_modules/media-typer": { @@ -10371,7 +10807,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -10444,6 +10880,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -10482,7 +10930,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -10510,6 +10958,40 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -10551,7 +11033,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true + }, + "node_modules/nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "optional": true }, "node_modules/nanoid": { "version": "3.3.4", @@ -10617,7 +11105,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true + "dev": true }, "node_modules/next-tick": { "version": "1.0.0", @@ -10634,6 +11122,48 @@ "tslib": "^2.0.3" } }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", @@ -10649,6 +11179,21 @@ "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -10694,18 +11239,6 @@ "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", "dev": true }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10718,6 +11251,18 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nth-check": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", @@ -10956,7 +11501,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "devOptional": true, "dependencies": { "wrappy": "1" } @@ -11197,7 +11742,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -11237,28 +11782,34 @@ "resolved": "https://registry.npmjs.org/path-webpack/-/path-webpack-0.0.3.tgz", "integrity": "sha1-/23sdJ7sWpRgXATV9j/FVgegOhY=" }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "engines": { + "node": ">=8" + } + }, "node_modules/pdfjs-dist": { - "version": "2.16.105", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz", - "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==", + "version": "3.5.141", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.5.141.tgz", + "integrity": "sha512-lYIvyi5grtYOIatsfCifIKwxHeAJ8eHyP22DTdvY4pm0yWVSFQnMafpgCPSw8gaNRDDdcHnBVOkqMsyK8SRxZg==", "dependencies": { - "dommatrix": "^1.0.3", + "path2d-polyfill": "^2.0.1", "web-streams-polyfill": "^3.2.1" }, - "peerDependencies": { - "worker-loader": "^3.0.8" + "engines": { + "node": ">=16" }, - "peerDependenciesMeta": { - "worker-loader": { - "optional": true - } + "optionalDependencies": { + "canvas": "^2.11.0" } }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -11360,9 +11911,9 @@ } }, "node_modules/postcss-attribute-case-insensitive": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.1.tgz", - "integrity": "sha512-XNVoIdu/Pskb5OhkM+iHicEVuASeqAjOTCaW8Wcbrd1UVwRukOJr5+zWzFjYxJj55Z/67ViVm9n/1hwF7MGByQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz", + "integrity": "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -11407,9 +11958,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-5.0.1.tgz", - "integrity": "sha512-Q9YDNQddKrl6YBs3229v+ckQINLyAaPfjJqG3jp5NUlP0UMm9+JeuLO1IMpeZy0l+rIE64y4OjUq0o+xhrnnrA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-5.0.2.tgz", + "integrity": "sha512-M6ygxWOyd6eWf3sd1Lv8xi4SeF4iBPfJvkfMU4ITh8ExJc1qhbvh/U8Cv/uOvBgUVOMDdScvCdlg8+hREQzs7w==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -11426,9 +11977,9 @@ } }, "node_modules/postcss-color-hex-alpha": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.1.tgz", - "integrity": "sha512-1ZTJvmOZXTCsedKeY+Puqwx6AgoyB1KnzSD/CGDIl1NWvDfxi1jYky4R9konw2SAYw0SOeU33EU27ihE59Fp8Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz", + "integrity": "sha512-SfPjgr//VQ/DOCf80STIAsdAs7sbIbxATvVmd+Ec7JvR8onz9pjawhq3BJM3Pie40EE3TyB0P6hft16D33Nlyg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -11445,9 +11996,9 @@ } }, "node_modules/postcss-color-rebeccapurple": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-8.0.1.tgz", - "integrity": "sha512-bzZYxBDx/uUGW9HeldOA7J69GdymOZJNz3pG8av27YSgJt9dobl4l+hI/3KAosoRJml/iWceT97pJQj3O/dQDw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-8.0.2.tgz", + "integrity": "sha512-xWf/JmAxVoB5bltHpXk+uGRoGFwu4WDAR7210el+iyvTdqiKpDhtcT8N3edXMoVJY0WHFMrKMUieql/wRNiXkw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -11464,9 +12015,9 @@ } }, "node_modules/postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz", + "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==", "dev": true, "dependencies": { "browserslist": "^4.21.4", @@ -11475,38 +12026,38 @@ "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz", + "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==", "dev": true, "dependencies": { "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-custom-media": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.1.tgz", - "integrity": "sha512-veQwzQkHgBkizxYCV/EBsiK8sFIJA0oQMQL9mpQ3gqFGc2dWlNWURHk4J44i9Q0dFeFCK81vV/Xpj7fyfNQKSA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.3.tgz", + "integrity": "sha512-W1C4Fu6KAZ7sKYQCuGMr8gyaE4BtjTQGPLVS4m0WCaWM6l7PgVbvmDeb4ClBc5R/7kdwESYf0hdxGtEPhi9CLA==", "dev": true, "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", - "@csstools/media-query-list-parser": "^2.0.0" + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" }, "engines": { "node": "^14 || ^16 || >=18" @@ -11520,14 +12071,14 @@ } }, "node_modules/postcss-custom-properties": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.1.1.tgz", - "integrity": "sha512-FK4dBiHdzWOosLu3kEAHaYpfcrnMfVV4nP6PT6EFIfWXrtHH9LY8idfTYnEDpq/vgE33mr8ykhs7BjlgcT9agg==", + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.1.5.tgz", + "integrity": "sha512-98DXk81zTGqMVkGANysMHbGIg3voH383DYo3/+c+Abzay3nao+vM/f4Jgzsakk9S7BDsEw5DiW7sFy5G4W2wLA==", "dev": true, "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -11542,14 +12093,14 @@ } }, "node_modules/postcss-custom-selectors": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.1.tgz", - "integrity": "sha512-CPs3BSdQfKqdrJ3d+3In9ppBPA8GpRy4Bd50jU+BDD6WEZOx8TTIB9i67BfRc2AVEAbRZwDMesreF95598dwhw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.3.tgz", + "integrity": "sha512-GTVscax6O/8s7agFF0HsOoIyjrnAbLjgCUle8tn+0oDGJuVx7p56U7ClSRoC49poxFuMfu2B4Q8GnxSCOeuFKw==", "dev": true, "dependencies": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", "postcss-selector-parser": "^6.0.4" }, "engines": { @@ -11564,9 +12115,9 @@ } }, "node_modules/postcss-dir-pseudo-class": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.1.tgz", - "integrity": "sha512-VjiqVOTz1op7bsiw7qd5CjZ0txA5yJY/oo1wb3f37qdleRTZQ9hzhAtLDqXimn0ZKh9XbtYawc4pmVBnV+LyMA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.2.tgz", + "integrity": "sha512-cMnslilYxBf9k3qejnovrUONZx1rXeUZJw06fgIUBzABJe3D2LiLL5WAER7Imt3nrkaIgG05XZBztueLEf5P8w==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -11583,57 +12134,57 @@ } }, "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz", + "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz", + "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz", + "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz", + "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-double-position-gradients": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.1.tgz", - "integrity": "sha512-XE+eKvX96E9cmldwKeRmK8AMxfQfuuHN9Yjerymau5i+fgC/vEY+B+Ke2vnEv4E8EXu8MKdLxi4DzmodusW19Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.2.tgz", + "integrity": "sha512-GXL1RmFREDK4Q9aYvI2RhVrA6a6qqSMQQ5ke8gSH1xgV6exsqbcJpIumC7AOgooH6/WIG3/K/T8xxAiVHy/tJg==", "dev": true, "dependencies": { "@csstools/postcss-progressive-custom-properties": "^2.0.0", @@ -11651,9 +12202,9 @@ } }, "node_modules/postcss-focus-visible": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-8.0.1.tgz", - "integrity": "sha512-azd1NMrLBe5bfKyomui9AMcgIR2zzlqXCTnKjshNDSClmmSO5MauTyflJUqmIwjIhD16+FbPyGV8Nxsly87BjA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-8.0.2.tgz", + "integrity": "sha512-f/Vd+EC/GaKElknU59esVcRYr/Y3t1ZAQyL4u2xSOgkDy4bMCmG7VP5cGvj3+BTLNE9ETfEuz2nnt4qkZwTTeA==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -11670,9 +12221,9 @@ } }, "node_modules/postcss-focus-within": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-7.0.1.tgz", - "integrity": "sha512-iSpk018Yqn0xwltFR7NHjagyt+e/6u8w50uEnGOcFOddLay5zQFjpJBg6euEZu7wY5WDq83DPpdO99eL+8Er8g==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-7.0.2.tgz", + "integrity": "sha512-AHAJ89UQBcqBvFgQJE9XasGuwMNkKsGj4D/f9Uk60jFmEBHpAL14DrnSk3Rj+SwZTr/WUG+mh+Rvf8fid/346w==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -11770,9 +12321,9 @@ } }, "node_modules/postcss-image-set-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-5.0.1.tgz", - "integrity": "sha512-JnmN9Wo7WjlvM7fg00wzC4d/1kOqau+6v6hteLLqEyBjCuzoFZUU0Te3JphDyxc65RtPNsCujDwYbbs6+vYxCQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-5.0.2.tgz", + "integrity": "sha512-Sszjwo0ubETX0Fi5MvpYzsONwrsjeabjMoc5YqHvURFItXgIu3HdCjcVuVKGMPGzKRhgaknmdM5uVWInWPJmeg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -11811,13 +12362,15 @@ } }, "node_modules/postcss-lab-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-5.0.1.tgz", - "integrity": "sha512-TuvrxsRIA3oWjjjI9T1ZEAolrtrLzYwYDw14GFivy0BkRqUTi4IithbM1aZkZGbAxV4lLwD6rL7MHhfDslUEzg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-5.2.1.tgz", + "integrity": "sha512-u71Adr4nWi+4EmSZq5EV/fg9d1dYO6W26RNtT9LISEyjhH1q23vJIUkSqRwHgD6v7xxsxLOY5cSdVyaNE6rqzw==", "dev": true, "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" }, "engines": { "node": "^14 || ^16 || >=18" @@ -11882,12 +12435,13 @@ } }, "node_modules/postcss-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.1.0.tgz", - "integrity": "sha512-vTD2DJ8vJD0Vr1WzMQkRZWRjcynGh3t7NeoLg+Sb1TeuK7etiZfL/ZwHbaVa3M+Qni7Lj/29voV9IggnIUjlIw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", "dev": true, "dependencies": { - "cosmiconfig": "^8.0.0", + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", "klona": "^2.0.6", "semver": "^7.3.8" }, @@ -11900,7 +12454,17 @@ }, "peerDependencies": { "postcss": "^7.0.0 || ^8.0.1", + "ts-node": ">=10", + "typescript": ">=4", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + }, + "typescript": { + "optional": true + } } }, "node_modules/postcss-loader/node_modules/argparse": { @@ -11927,6 +12491,22 @@ "url": "https://github.com/sponsors/d-fischer" } }, + "node_modules/postcss-loader/node_modules/cosmiconfig-typescript-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", + "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=7", + "ts-node": ">=10", + "typescript": ">=3" + } + }, "node_modules/postcss-loader/node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -11983,9 +12563,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -11998,9 +12578,9 @@ } }, "node_modules/postcss-logical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-6.0.1.tgz", - "integrity": "sha512-0LIzRgbT42n0q8txcM9SrLkYLjr1LTbRTy80bnKiYXY8tnYGdjkBymwb5XE87o4csW1z8dhKD1VRI6cHBQBQtw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-6.1.0.tgz", + "integrity": "sha512-qb1+LpClhYjxac8SfOcWotnY3unKZesDqIOm+jnGt8rTl7xaIWpE2bPGZHxflOip1E/4ETo79qlJyRL3yrHn1g==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" @@ -12162,18 +12742,6 @@ "unist-util-stringify-position": "^1.1.1" } }, - "node_modules/postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -12181,98 +12749,98 @@ "dev": true }, "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", + "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" + "stylehacks": "^6.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.0.tgz", + "integrity": "sha512-rCXkklftzEkniyv3f4mRCQzxD6oE4Quyh61uyWTUbCJ26Pv2hoz+fivJSsSBWxDBeScR4fKCfF3HHTcD7Ybqnw==", "dev": true, "dependencies": { "browserslist": "^4.21.4", "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-selector-parser": "^6.0.5" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz", + "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz", + "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==", "dev": true, "dependencies": { "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz", + "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==", "dev": true, "dependencies": { "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz", + "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.5" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" @@ -12338,9 +12906,9 @@ } }, "node_modules/postcss-nesting": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-11.1.0.tgz", - "integrity": "sha512-TVBCeKlUmMyX3sNeSg10yATb2XmAoosp0E1zdlpjrD+L2FrQPmrRTxlRFQh/R0Y4WlQ0butfDwRhzlYuj7y/TA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-11.2.2.tgz", + "integrity": "sha512-aOTiUniAB1bcPE6GGiynWRa6PZFPhOTAm5q3q5cem6QeSijIHHkWr6gs65ukCZMXeak8yXeZVbBJET3VM+HlhA==", "dev": true, "dependencies": { "@csstools/selector-specificity": "^2.0.0", @@ -12358,143 +12926,142 @@ } }, "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz", + "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==", "dev": true, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz", + "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz", + "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz", + "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz", + "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz", + "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz", + "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==", "dev": true, "dependencies": { "browserslist": "^4.21.4", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz", + "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==", "dev": true, "dependencies": { - "normalize-url": "^6.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz", + "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-opacity-percentage": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", - "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz", + "integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==", "dev": true, "funding": [ { @@ -12507,23 +13074,23 @@ } ], "engines": { - "node": "^12 || ^14 || >=16" + "node": "^14 || ^16 || >=18" }, "peerDependencies": { "postcss": "^8.2" } }, "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz", + "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==", "dev": true, "dependencies": { - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" @@ -12577,63 +13144,65 @@ } }, "node_modules/postcss-preset-env": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-8.0.1.tgz", - "integrity": "sha512-IUbymw0JlUbyVG+I85963PNWgPp3KhnFa1sxU7M/2dGthxV8e297P0VV5W9XcyypoH4hirH2fp1c6fmqh6YnSg==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-8.3.1.tgz", + "integrity": "sha512-k3Y8BXbVLBAufrla3CNmQJhMS1iRuT9LFlysYvzs1rU5E78+ShX2u0EUL6KpMi0pDJO3wZcuVYSR8cgukfoRtg==", "dev": true, "dependencies": { - "@csstools/postcss-cascade-layers": "^3.0.0", - "@csstools/postcss-color-function": "^2.0.0", - "@csstools/postcss-font-format-keywords": "^2.0.0", - "@csstools/postcss-hwb-function": "^2.0.0", - "@csstools/postcss-ic-unit": "^2.0.0", - "@csstools/postcss-is-pseudo-class": "^3.0.0", - "@csstools/postcss-logical-float-and-clear": "^1.0.0", - "@csstools/postcss-logical-resize": "^1.0.0", - "@csstools/postcss-logical-viewport-units": "^1.0.0", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.0", - "@csstools/postcss-nested-calc": "^2.0.0", - "@csstools/postcss-normalize-display-values": "^2.0.0", - "@csstools/postcss-oklab-function": "^2.0.0", - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "@csstools/postcss-scope-pseudo-class": "^2.0.0", - "@csstools/postcss-stepped-value-functions": "^2.0.0", - "@csstools/postcss-text-decoration-shorthand": "^2.0.0", - "@csstools/postcss-trigonometric-functions": "^2.0.0", - "@csstools/postcss-unset-value": "^2.0.0", - "autoprefixer": "^10.4.13", - "browserslist": "^4.21.4", - "css-blank-pseudo": "^5.0.0", - "css-has-pseudo": "^5.0.0", - "css-prefers-color-scheme": "^8.0.0", - "cssdb": "^7.4.0", - "postcss-attribute-case-insensitive": "^6.0.0", + "@csstools/postcss-cascade-layers": "^3.0.1", + "@csstools/postcss-color-function": "^2.2.1", + "@csstools/postcss-color-mix-function": "^1.0.1", + "@csstools/postcss-font-format-keywords": "^2.0.2", + "@csstools/postcss-gradients-interpolation-method": "^3.0.3", + "@csstools/postcss-hwb-function": "^2.2.1", + "@csstools/postcss-ic-unit": "^2.0.2", + "@csstools/postcss-is-pseudo-class": "^3.2.0", + "@csstools/postcss-logical-float-and-clear": "^1.0.1", + "@csstools/postcss-logical-resize": "^1.0.1", + "@csstools/postcss-logical-viewport-units": "^1.0.3", + "@csstools/postcss-media-minmax": "^1.0.2", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.2", + "@csstools/postcss-nested-calc": "^2.0.2", + "@csstools/postcss-normalize-display-values": "^2.0.1", + "@csstools/postcss-oklab-function": "^2.2.1", + "@csstools/postcss-progressive-custom-properties": "^2.1.0", + "@csstools/postcss-scope-pseudo-class": "^2.0.2", + "@csstools/postcss-stepped-value-functions": "^2.1.1", + "@csstools/postcss-text-decoration-shorthand": "^2.2.3", + "@csstools/postcss-trigonometric-functions": "^2.1.1", + "@csstools/postcss-unset-value": "^2.0.1", + "autoprefixer": "^10.4.14", + "browserslist": "^4.21.5", + "css-blank-pseudo": "^5.0.2", + "css-has-pseudo": "^5.0.2", + "css-prefers-color-scheme": "^8.0.2", + "cssdb": "^7.5.3", + "postcss-attribute-case-insensitive": "^6.0.2", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^5.0.0", - "postcss-color-hex-alpha": "^9.0.0", - "postcss-color-rebeccapurple": "^8.0.0", - "postcss-custom-media": "^9.1.0", - "postcss-custom-properties": "^13.1.0", - "postcss-custom-selectors": "^7.1.0", - "postcss-dir-pseudo-class": "^7.0.0", - "postcss-double-position-gradients": "^4.0.0", - "postcss-focus-visible": "^8.0.0", - "postcss-focus-within": "^7.0.0", + "postcss-color-functional-notation": "^5.0.2", + "postcss-color-hex-alpha": "^9.0.2", + "postcss-color-rebeccapurple": "^8.0.2", + "postcss-custom-media": "^9.1.3", + "postcss-custom-properties": "^13.1.5", + "postcss-custom-selectors": "^7.1.3", + "postcss-dir-pseudo-class": "^7.0.2", + "postcss-double-position-gradients": "^4.0.2", + "postcss-focus-visible": "^8.0.2", + "postcss-focus-within": "^7.0.2", "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^4.0.0", - "postcss-image-set-function": "^5.0.0", + "postcss-gap-properties": "^4.0.1", + "postcss-image-set-function": "^5.0.2", "postcss-initial": "^4.0.1", - "postcss-lab-function": "^5.0.0", - "postcss-logical": "^6.0.0", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^11.0.0", - "postcss-opacity-percentage": "^1.1.3", - "postcss-overflow-shorthand": "^4.0.0", + "postcss-lab-function": "^5.2.1", + "postcss-logical": "^6.1.0", + "postcss-nesting": "^11.2.1", + "postcss-opacity-percentage": "^2.0.0", + "postcss-overflow-shorthand": "^4.0.1", "postcss-page-break": "^3.0.4", - "postcss-place": "^8.0.0", - "postcss-pseudo-class-any-link": "^8.0.0", + "postcss-place": "^8.0.1", + "postcss-pseudo-class-any-link": "^8.0.2", "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.0", + "postcss-selector-not": "^7.0.1", "postcss-value-parser": "^4.2.0" }, "engines": { @@ -12648,9 +13217,9 @@ } }, "node_modules/postcss-pseudo-class-any-link": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-8.0.1.tgz", - "integrity": "sha512-CYcLGofbGDhx6BmNFQGFH0cqW+qlXVk9PR4LZ8Y7g24m6TopYKt6FSwhMGAIyme6lQxgB32XMhpYRwZAcPnMXA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-8.0.2.tgz", + "integrity": "sha512-FYTIuRE07jZ2CW8POvctRgArQJ43yxhr5vLmImdKUvjFCkR09kh8pIdlCwdx/jbFm7MiW4QP58L4oOUv3grQYA==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.10" @@ -12667,31 +13236,31 @@ } }, "node_modules/postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", + "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==", "dev": true, "dependencies": { "browserslist": "^4.21.4", "caniuse-api": "^3.0.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz", + "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" @@ -12936,16 +13505,16 @@ } }, "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz", + "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==", "dev": true, "dependencies": { "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" + "svgo": "^3.0.2" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >= 18" }, "peerDependencies": { "postcss": "^8.2.15" @@ -12961,15 +13530,15 @@ } }, "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz", + "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.5" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" @@ -13053,13 +13622,14 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -13112,7 +13682,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "devOptional": true, + "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -13193,11 +13763,11 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-router": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", - "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", "dependencies": { - "@remix-run/router": "1.4.0" + "@remix-run/router": "1.5.0" }, "engines": { "node": ">=14" @@ -13207,12 +13777,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", - "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", "dependencies": { - "@remix-run/router": "1.4.0", - "react-router": "6.9.0" + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" }, "engines": { "node": ">=14" @@ -13374,9 +13944,9 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", "dev": true, "dependencies": { "@babel/runtime": "^7.8.4" @@ -13413,28 +13983,22 @@ } }, "node_modules/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dev": true, "dependencies": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" + "unicode-match-property-value-ecmascript": "^2.1.0" }, "engines": { "node": ">=4" } }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, "node_modules/regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -13712,6 +14276,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rvfc-polyfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/rvfc-polyfill/-/rvfc-polyfill-1.0.4.tgz", + "integrity": "sha512-BemRbBDZiLB8pxoPT+2q6R30ykY1e75XBE/L1A0Ubd/3KdUoCQLqI/z4v4oNFNlN3/Rs93d3b6WoybnXhdebkw==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -13756,9 +14325,9 @@ "dev": true }, "node_modules/sass": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.60.0.tgz", - "integrity": "sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==", + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", + "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -13769,13 +14338,13 @@ "sass": "sass.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/sass-loader": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.1.tgz", - "integrity": "sha512-VQUrgUa5/waIzMrzyuko3sj5WD9NMsYph91cNICx+OaODbRtLl6To2fswLx8MH2qNxXFqRtpvdPQIa7mE93YOA==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", + "integrity": "sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==", "dev": true, "dependencies": { "klona": "^2.0.6", @@ -13871,7 +14440,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, + "devOptional": true, "bin": { "semver": "bin/semver.js" } @@ -13940,10 +14509,10 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "devOptional": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -14023,6 +14592,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -14121,7 +14696,38 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "devOptional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } }, "node_modules/simple-swizzle": { "version": "0.2.2", @@ -14428,7 +15034,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, + "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -14438,7 +15044,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -14564,13 +15170,6 @@ "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==" }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", - "dev": true - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -14648,7 +15247,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14753,7 +15352,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -14835,34 +15434,34 @@ "dev": true }, "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz", + "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==", "dev": true, "dependencies": { "browserslist": "^4.21.4", "postcss-selector-parser": "^6.0.4" }, "engines": { - "node": "^10 || ^12 || >=14.0" + "node": "^14 || ^16 || >=18.0" }, "peerDependencies": { "postcss": "^8.2.15" } }, "node_modules/stylelint": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.3.0.tgz", - "integrity": "sha512-9UYBYk7K9rtlKcTUDZrtntE840sZM00qyYBQHHe7tjwMNUsPsGvR6Fd43IxHEAhRrDLzpy3TVaHb6CReBB3eFg==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.4.0.tgz", + "integrity": "sha512-TlOvpG3MbcFwHmK0q2ykhmpKo7Dq892beJit0NPdpyY9b1tFah/hGhqnAz/bRm2PDhDbJLKvjzkEYYBEz7Dxcg==", "dev": true, "dependencies": { - "@csstools/css-parser-algorithms": "^2.0.1", + "@csstools/css-parser-algorithms": "^2.1.0", "@csstools/css-tokenizer": "^2.1.0", "@csstools/media-query-list-parser": "^2.0.1", - "@csstools/selector-specificity": "^2.1.1", + "@csstools/selector-specificity": "^2.2.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.1.0", + "cosmiconfig": "^8.1.3", "css-functions-list": "^3.1.0", "css-tree": "^2.3.1", "debug": "^4.3.4", @@ -17147,16 +17746,16 @@ } }, "node_modules/stylelint-scss": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.5.0.tgz", - "integrity": "sha512-/+rQ8FePOiwT5xblOHkujYzRYfSjmE6HYhLpqJShL+9wH6/HaAVj4mWpXlpEsM3ZgIpOblG9Y+/BycSJzWgjNw==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.6.0.tgz", + "integrity": "sha512-M+E0BQim6G4XEkaceEhfVjP/41C9Klg5/tTPTCQVlgw/jm2tvB+OXJGaU0TDP5rnTCB62aX6w+rT+gqJW/uwjA==", "dev": true, "dependencies": { - "lodash": "^4.17.21", + "dlv": "^1.1.3", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.6", - "postcss-value-parser": "^4.1.0" + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "stylelint": "^14.5.1 || ^15.0.0" @@ -17201,19 +17800,6 @@ "url": "https://github.com/sponsors/d-fischer" } }, - "node_modules/stylelint/node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, "node_modules/stylelint/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -17280,12 +17866,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/stylelint/node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, "node_modules/stylelint/node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -17442,24 +18022,122 @@ "dev": true }, "node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" }, "bin": { "svgo": "bin/svgo" }, "engines": { - "node": ">=10.13.0" + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/svgo/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/svgo/node_modules/entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/swiper": { @@ -17505,11 +18183,40 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } }, + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -17550,10 +18257,10 @@ } }, "node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "devOptional": true, + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -17568,16 +18275,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", - "devOptional": true, + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" }, "engines": { "node": ">= 10.13.0" @@ -17605,7 +18312,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -17621,7 +18328,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -17630,13 +18337,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -17650,20 +18357,11 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true + "dev": true }, "node_modules/text-hex": { "version": "1.0.0", @@ -17912,6 +18610,50 @@ "node": ">=8" } }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -18006,9 +18748,9 @@ } }, "node_modules/typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -18070,9 +18812,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", "dev": true, "engines": { "node": ">=4" @@ -18250,7 +18992,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", @@ -18276,7 +19018,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -18332,6 +19074,13 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -18379,7 +19128,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "devOptional": true, + "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -18392,7 +19141,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true + "dev": true }, "node_modules/wbuf": { "version": "1.7.3", @@ -18423,13 +19172,13 @@ "dev": true }, "node_modules/webpack": { - "version": "5.76.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", - "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", - "devOptional": true, + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", + "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", + "@types/estree": "^1.0.0", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -18438,7 +19187,7 @@ "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -18449,7 +19198,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -18559,9 +19308,9 @@ "dev": true }, "node_modules/webpack-dev-server": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz", - "integrity": "sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -18618,9 +19367,9 @@ } }, "node_modules/webpack-dev-server/node_modules/colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "node_modules/webpack-dev-server/node_modules/rimraf": { @@ -18655,7 +19404,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "devOptional": true, + "dev": true, "engines": { "node": ">=10.13.0" } @@ -18664,7 +19413,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -18680,7 +19429,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -18689,19 +19438,19 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true + "dev": true }, "node_modules/webpack/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -18825,6 +19574,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -19145,7 +19903,7 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", - "devOptional": true, + "dev": true, "dependencies": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -19165,7 +19923,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -19181,7 +19939,7 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -19190,13 +19948,13 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "node_modules/worker-loader/node_modules/schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -19214,7 +19972,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "devOptional": true }, "node_modules/write": { "version": "1.0.3", @@ -19293,16 +20051,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } + "devOptional": true }, "node_modules/yargs-parser": { "version": "20.2.9", @@ -19313,6 +20062,16 @@ "node": ">=10" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -19369,36 +20128,36 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "requires": { "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.20.10", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz", - "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", "dev": true }, "@babel/core": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.3.tgz", - "integrity": "sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", - "@babel/helper-compilation-targets": "^7.20.7", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-module-transforms": "^7.21.2", "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.3", + "@babel/parser": "^7.21.4", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.3", - "@babel/types": "^7.21.3", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -19427,12 +20186,12 @@ } }, "@babel/generator": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.3.tgz", - "integrity": "sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "dev": true, "requires": { - "@babel/types": "^7.21.3", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -19458,13 +20217,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -19504,13 +20263,13 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", + "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" + "regexpu-core": "^5.3.1" } }, "@babel/helper-define-polyfill-provider": { @@ -19716,9 +20475,9 @@ } }, "@babel/parser": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.3.tgz", - "integrity": "sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -19731,14 +20490,14 @@ } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz", + "integrity": "sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-proposal-optional-chaining": "^7.20.7" } }, "@babel/plugin-proposal-async-generator-functions": { @@ -19764,13 +20523,13 @@ } }, "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", + "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, @@ -19805,12 +20564,12 @@ } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", + "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, @@ -19858,13 +20617,13 @@ } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, @@ -19879,14 +20638,14 @@ } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", + "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, @@ -20044,33 +20803,24 @@ "@babel/helper-plugin-utils": "^7.14.5" } }, - "@babel/plugin-syntax-typescript": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", - "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0" - } - }, "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.20.7.tgz", + "integrity": "sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz", + "integrity": "sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9" } }, "@babel/plugin-transform-block-scoped-functions": { @@ -20083,24 +20833,24 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.11.tgz", - "integrity": "sha512-tA4N427a7fjf1P0/2I4ScsHGc5jcHPbb30xMbaTke2gxDuWpUfXDuX1FEymJwKk4tuGUvGcejAR6HdZVqmmPyw==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz", + "integrity": "sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-classes": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.7.tgz", - "integrity": "sha512-LWYbsiXTPKl+oBlXUGlwNlJZetXD5Am+CyBdqhPsDVjM9Jc8jwBJFrKhHf900Kfk2eZG1y9MAG3UNajol7A4VQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz", + "integrity": "sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", "@babel/helper-compilation-targets": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", + "@babel/helper-function-name": "^7.21.0", "@babel/helper-optimise-call-expression": "^7.18.6", "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-replace-supers": "^7.20.7", @@ -20109,18 +20859,19 @@ } }, "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.20.7.tgz", + "integrity": "sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/template": "^7.20.7" } }, "@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" @@ -20156,12 +20907,12 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.0.tgz", + "integrity": "sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-function-name": { @@ -20204,12 +20955,12 @@ } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.20.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.20.11.tgz", - "integrity": "sha512-S8e1f7WQ7cimJQ51JkAaDrEtohVEitXjgCGAS2N8S31Y42E+kWwfSz83LYz57QdBm7q9diARVqanIaH2oVgQnw==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz", + "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.20.11", + "@babel/helper-module-transforms": "^7.21.2", "@babel/helper-plugin-utils": "^7.20.2", "@babel/helper-simple-access": "^7.20.2" } @@ -20237,13 +20988,13 @@ } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz", + "integrity": "sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" + "@babel/helper-create-regexp-features-plugin": "^7.20.5", + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-transform-new-target": { @@ -20266,9 +21017,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" @@ -20325,13 +21076,13 @@ } }, "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz", + "integrity": "sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" + "@babel/helper-plugin-utils": "^7.20.2", + "regenerator-transform": "^0.15.1" } }, "@babel/plugin-transform-reserved-words": { @@ -20353,13 +21104,13 @@ } }, "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz", + "integrity": "sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0" } }, "@babel/plugin-transform-sticky-regex": { @@ -20389,17 +21140,6 @@ "@babel/helper-plugin-utils": "^7.18.9" } }, - "@babel/plugin-transform-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz", - "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.21.0", - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/plugin-syntax-typescript": "^7.20.0" - } - }, "@babel/plugin-transform-unicode-escapes": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", @@ -20420,31 +21160,31 @@ } }, "@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", + "@babel/helper-validator-option": "^7.21.0", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", "@babel/plugin-proposal-dynamic-import": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -20461,40 +21201,40 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", "@babel/plugin-transform-dotall-regex": "^7.18.6", "@babel/plugin-transform-duplicate-keys": "^7.18.9", "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-for-of": "^7.21.0", "@babel/plugin-transform-function-name": "^7.18.9", "@babel/plugin-transform-literals": "^7.18.9", "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", "@babel/plugin-transform-new-target": "^7.18.6", "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-parameters": "^7.21.3", "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", "@babel/plugin-transform-reserved-words": "^7.18.6", "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-spread": "^7.20.7", "@babel/plugin-transform-sticky-regex": "^7.18.6", "@babel/plugin-transform-template-literals": "^7.18.9", "@babel/plugin-transform-typeof-symbol": "^7.18.9", "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.4", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -20529,16 +21269,11 @@ "@babel/plugin-transform-react-pure-annotations": "^7.18.6" } }, - "@babel/preset-typescript": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", - "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.21.0", - "@babel/plugin-transform-typescript": "^7.21.0" - } + "@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true }, "@babel/runtime": { "version": "7.20.7", @@ -20560,27 +21295,27 @@ } }, "@babel/traverse": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.3.tgz", - "integrity": "sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.3", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.3", - "@babel/types": "^7.21.3", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.3.tgz", - "integrity": "sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.19.4", @@ -20594,36 +21329,76 @@ "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@csstools/cascade-layer-name-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.1.tgz", - "integrity": "sha512-SAAi5DpgJJWkfTvWSaqkgyIsTawa83hMwKrktkj6ra2h+q6ZN57vOGZ6ySHq6RSo+CbP64fA3aPChPBRDDUgtw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.2.tgz", + "integrity": "sha512-xm7Mgwej/wBfLoK0K5LfntmPJzoULayl1XZY9JYgQgT29JiqNw++sLnx95u5y9zCihblzkyaRYJrsRMhIBzRdg==", "dev": true, "requires": {} }, "@csstools/color-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-1.0.0.tgz", - "integrity": "sha512-tgqtiV8sU/VaWYjOB3O7PWs7HR/MmOLl2kTYRW2qSsTSEniJq7xmyAYFB1LPpXvvQcE5u2ih2dK9fyc8BnrAGQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.0.0.tgz", + "integrity": "sha512-VcPjEnp07RNgz/D+oI2uIALg+IPCSl6mj0XhA3pl3F2bM2B95vgzatExmmzSg/X0zkh+R2v+jFY/J2pV/bnwpw==", "dev": true }, + "@csstools/css-calc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-1.1.1.tgz", + "integrity": "sha512-Nh+iLCtjlooTzuR0lpmB8I6hPX/VupcGQ3Z1U2+wgJJ4fa8+cWkub+lCsbZcYPzBGsZLEL8fQAg+Na5dwEFJxg==", + "dev": true, + "requires": {} + }, + "@csstools/css-color-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-1.1.2.tgz", + "integrity": "sha512-MjW/VspbFSkvbuou7tUUu2+FAlAR7VJ/PA69M9EGKltThbONC8nyW33wHRzNvLzRLGstZLEO5X5oR7IMhMDi0A==", + "dev": true, + "requires": { + "@csstools/color-helpers": "^2.0.0", + "@csstools/css-calc": "^1.1.1" + } + }, "@csstools/css-parser-algorithms": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz", - "integrity": "sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz", + "integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==", "dev": true, "requires": {} }, "@csstools/css-tokenizer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.0.tgz", - "integrity": "sha512-dtqFyoJBHUxGi9zPZdpCKP1xk8tq6KPHJ/NY4qWXiYo6IcSGwzk3L8x2XzZbbyOyBs9xQARoGveU2AsgLj6D2A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz", + "integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==", "dev": true }, "@csstools/media-query-list-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz", - "integrity": "sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz", + "integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==", "dev": true, "requires": {} }, @@ -20638,38 +21413,65 @@ } }, "@csstools/postcss-color-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.0.1.tgz", - "integrity": "sha512-d7379loVBgIiKTQMOUduUctq3CWMeqNpGkLhzuejvuGyA+bWYT1p7n2GzmIwgXwP0CF8DIFtDgvrsvHn3i+tWw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-2.2.1.tgz", + "integrity": "sha512-T52iiqmzyKk09B9iNTQbuWa9Tn83SztXY7o6r2+j+o1BS/7+CiCjTgN2HgzybDmx8cr6XYhQ1BzqgV9tJzhrmw==", "dev": true, "requires": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" + } + }, + "@csstools/postcss-color-mix-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-1.0.1.tgz", + "integrity": "sha512-NSVrzjVcI4TMzDfh6GKZXvEuelT81xpXzruuTNJrwKMTZXEBHY9G2gvmr0eC0wwmL8EF1TblXyPPfBbZobvfXg==", + "dev": true, + "requires": { + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" } }, "@csstools/postcss-font-format-keywords": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.1.tgz", - "integrity": "sha512-NRwT5g/L+lDkridDiHfjNGyHvdSHJOdcXPPZXZOpSfr/AwRxTJ+wsbKAzyBb1stalkr9KjICDr+ofpkk96r0Wg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-2.0.2.tgz", + "integrity": "sha512-iKYZlIs6JsNT7NKyRjyIyezTCHLh4L4BBB3F5Nx7Dc4Z/QmBgX+YJFuUSar8IM6KclGiAUFGomXFdYxAwJydlA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, + "@csstools/postcss-gradients-interpolation-method": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-3.0.4.tgz", + "integrity": "sha512-GgKoY7OlvL65UPigEdlrvMAUCR5kOQCjtue2/36TPrBNoRS6KM2KOqmjIVsxEwYYwK+L28pdnM8r10m03hhZxA==", + "dev": true, + "requires": { + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" + } + }, "@csstools/postcss-hwb-function": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.1.0.tgz", - "integrity": "sha512-B4uBMznejB5VM7rMB2C3KQdM3JwPAy3CxbI9DIMziCZzlaB1a59uV7NimuINndumgtzpVt++CdpY0XffURZ+eA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-2.2.1.tgz", + "integrity": "sha512-eiqB4DIs+xqProAly7KwIgE04oze1YHb0QSCw/Y7062d9gpw+Cdif3Y0Z+Te+U7JROYdO0/0j91A6Qy8fo/Rlw==", "dev": true, "requires": { - "@csstools/color-helpers": "^1.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" } }, "@csstools/postcss-ic-unit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.1.tgz", - "integrity": "sha512-718aUIKZJDkbQrINOv6B0I70EZpTB9LzPykGVE/U3gnlXc4tjgvr6/r/G3Hopyn1D5R4BJYcMPI06tVzAgLSMQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-2.0.2.tgz", + "integrity": "sha512-N84qGTJkfLTPj2qOG5P4CIqGjpZBbjOEMKMn+UjO5wlb9lcBTfBsxCF0lQsFdWJUzBHYFOz19dL66v71WF3Pig==", "dev": true, "requires": { "@csstools/postcss-progressive-custom-properties": "^2.0.0", @@ -20677,9 +21479,9 @@ } }, "@csstools/postcss-is-pseudo-class": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.1.0.tgz", - "integrity": "sha512-MSt4kjWvIuv7GWGEV2WNkcOTLXdYpBRbW/2YF9MAmrKKYui65P/LpsLfSwCWq4vdwWH1HSxFi4Qp6bGCEAZ8ag==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-3.2.0.tgz", + "integrity": "sha512-uooelBL99jMg8ZD6xy0Pj1hSalchWmplcin00eI+JHpv1jW2iwbi1cn2UnVsToM4JLwJSZFzTSWCgSpmlyhe3A==", "dev": true, "requires": { "@csstools/selector-specificity": "^2.0.0", @@ -20703,29 +21505,41 @@ } }, "@csstools/postcss-logical-viewport-units": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.2.tgz", - "integrity": "sha512-nnKFywBqRMYjv5jyjSplD/nbAnboUEGFfdxKw1o34Y1nvycgqjQavhKkmxbORxroBBIDwC5y6SfgENcPPUcOxQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-1.0.3.tgz", + "integrity": "sha512-6zqcyRg9HSqIHIPMYdt6THWhRmE5/tyHKJQLysn2TeDf/ftq7Em9qwMTx98t2C/7UxIsYS8lOiHHxAVjWn2WUg==", "dev": true, "requires": { - "@csstools/css-tokenizer": "^2.0.0" + "@csstools/css-tokenizer": "^2.1.1" + } + }, + "@csstools/postcss-media-minmax": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-1.0.2.tgz", + "integrity": "sha512-DsEykSINZTqlBefi1uSQBym1Rj0NQOj92dLRd5jUQpSy8yBVaXXmkiUgBUbb+gQh8imAdqPpz2v4sAUnw8yXXA==", + "dev": true, + "requires": { + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" } }, "@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.1.tgz", - "integrity": "sha512-V9yQqXdje6OfqDf6EL5iGOpi6N0OEczwYK83rql9UapQwFEryXlAehR5AqH8QqLYb6+y31wUXK6vMxCp0920Zg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-1.0.2.tgz", + "integrity": "sha512-rOSR5p+5m0joXUoitYgCyMqNCu97yfLsLG3cnNaM8VeJRCWHGEu5hE9Gv0M7n9A4wo2pYF8QqaxkTlWbSJY9Fg==", "dev": true, "requires": { - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", - "@csstools/media-query-list-parser": "^2.0.0" + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" } }, "@csstools/postcss-nested-calc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.1.tgz", - "integrity": "sha512-6C5yoF99zFb/C2Sa9Y5V0Y/2dnrjK5xe+h59L0LfdVhfanmJPrttwmfTua9etFRA1TGV46aoVMLEZ1NoHjWikg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-2.0.2.tgz", + "integrity": "sha512-jbwrP8rN4e7LNaRcpx3xpMUjhtt34I9OV+zgbcsYAAk6k1+3kODXJBf95/JMYWhu9g1oif7r06QVUgfWsKxCFw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -20741,13 +21555,15 @@ } }, "@csstools/postcss-oklab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.0.1.tgz", - "integrity": "sha512-MTj3w6G1TYW0k43sXjw25fY/S+LHXpFIym5NW0oO/hjHFzuz5Uwz93aUvdo/UrrFmxSQeQAYCxmq6NlH3Pf1Hw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-2.2.1.tgz", + "integrity": "sha512-g4wrVopp6xXr1KetUK4Lj36P+PFPwvUUtd2gaqo7X/0xgJHmMtKMPhD9p77H9bmIpPtkIYQ8b7+7cdmrWNEVAw==", "dev": true, "requires": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" } }, "@csstools/postcss-progressive-custom-properties": { @@ -20760,40 +21576,44 @@ } }, "@csstools/postcss-scope-pseudo-class": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.1.tgz", - "integrity": "sha512-wf2dcsqSQlBHc4HMMqdXdxDx4uYuqH+L08kKj+pmT+743C06STcUEu7ORFFEnqGWlOJ1kmA5BJ3pQU0EdMuA+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-2.0.2.tgz", + "integrity": "sha512-6Pvo4uexUCXt+Hz5iUtemQAcIuCYnL+ePs1khFR6/xPgC92aQLJ0zGHonWoewiBE+I++4gXK3pr+R1rlOFHe5w==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" } }, "@csstools/postcss-stepped-value-functions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.0.1.tgz", - "integrity": "sha512-VimD+M69GsZF/XssivjUwo6jXLgi86ar/gRSH7bautnCULSLxCr/HuY32N4rLRUr7qWF8oF/JTv06ceb66Q1jA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-2.1.1.tgz", + "integrity": "sha512-YCvdF0GCZK35nhLgs7ippcxDlRVe5QsSht3+EghqTjnYnyl3BbWIN6fYQ1dKWYTJ+7Bgi41TgqQFfJDcp9Xy/w==", "dev": true, "requires": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" } }, "@csstools/postcss-text-decoration-shorthand": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.0.tgz", - "integrity": "sha512-++RtufygqFaeheLH1g8Y2Oi/l+xSt61+c0lQGjrpLUW4hhFo77V4Zsshm0oof7lqnpVXPaizlU0SnXf+f4GA7A==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-2.2.3.tgz", + "integrity": "sha512-PADJidg/tdhDk120aWlGuDxsp5ZTc9Nx7GhJ8t0qBCk5fOgLK6V3DsB9X6sOAhDokIihXKzjtUu15puac5McWw==", "dev": true, "requires": { - "@csstools/color-helpers": "^1.0.0", + "@csstools/color-helpers": "^2.0.0", "postcss-value-parser": "^4.2.0" } }, "@csstools/postcss-trigonometric-functions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.0.1.tgz", - "integrity": "sha512-uGmmVWGHozyWe6+I4w321fKUC034OB1OYW0ZP4ySHA23n+r9y93K+1yrmW+hThpSfApKhaWySoD4I71LLlFUYQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-2.1.1.tgz", + "integrity": "sha512-XcXmHEFfHXhvYz40FtDlA4Fp4NQln2bWTsCwthd2c+MCnYArUYU3YaMqzR5CrKP3pMoGYTBnp5fMqf1HxItNyw==", "dev": true, "requires": { - "postcss-value-parser": "^4.2.0" + "@csstools/css-calc": "^1.1.1", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1" } }, "@csstools/postcss-unset-value": { @@ -20804,9 +21624,9 @@ "requires": {} }, "@csstools/selector-specificity": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz", - "integrity": "sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "dev": true, "requires": {} }, @@ -20851,14 +21671,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.5.1", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -20928,9 +21748,9 @@ } }, "@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", "dev": true }, "@fontsource/noto-sans": { @@ -20986,17 +21806,11 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, - "@jellyfin/libass-wasm": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz", - "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" - }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202303130502", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz", - "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==", + "version": "0.0.0-unstable.202304122102", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202304122102.tgz", + "integrity": "sha512-KToOmK3GmbjovtFPgb3dYx8cV6bopo46fhTkHDnKLqsmwqBz5/QKk7Z8NbR+5YaojNAP4LUYnenZmMK9HQ2YeA==", "requires": { - "axios": "1.3.4", "compare-versions": "5.0.3" } }, @@ -21004,7 +21818,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -21015,19 +21829,19 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true + "dev": true }, "@jridgewell/set-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true + "dev": true }, "@jridgewell/source-map": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -21037,13 +21851,13 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "dev": true }, "@jridgewell/trace-mapping": { "version": "0.3.17", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -21065,10 +21879,47 @@ "react-is": "^16.12.0" } }, + "@mapbox/node-pre-gyp": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz", + "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==", + "optional": true, + "requires": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "dependencies": { + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, "@mdn/browser-compat-data": { - "version": "5.2.36", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.36.tgz", - "integrity": "sha512-cS+xbp4jq+W04pFqw5639Grzj82JevJZst4b55Mk2NGc9wY7uXD6hlM6H0hkK5mLKXXZKsT1xq79W6LsSG4crw==", + "version": "5.2.51", + "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.2.51.tgz", + "integrity": "sha512-E72TUHMgX9+coOwyRf2C6OwAGlYgFS2UhpgokIYzzwrvnXDN8Qh7c+2L2KAnRcvf7RTZV52u8DhS4ajknyjlNw==", "dev": true }, "@mrmlnc/readdir-enhanced": { @@ -21117,9 +21968,9 @@ } }, "@remix-run/router": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.4.0.tgz", - "integrity": "sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==" }, "@rollup/plugin-babel": { "version": "5.3.1", @@ -21192,6 +22043,33 @@ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true, + "peer": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "peer": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true, + "peer": true + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -21240,7 +22118,7 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz", "integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==", - "devOptional": true, + "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" @@ -21250,17 +22128,17 @@ "version": "3.7.3", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.3.tgz", "integrity": "sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==", - "devOptional": true, + "dev": true, "requires": { "@types/eslint": "*", "@types/estree": "*" } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "devOptional": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true }, "@types/express": { "version": "4.17.13", @@ -21314,7 +22192,7 @@ "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "devOptional": true + "dev": true }, "@types/json5": { "version": "0.0.29", @@ -21376,7 +22254,7 @@ "version": "14.14.37", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==", - "devOptional": true + "dev": true }, "@types/normalize-package-data": { "version": "2.4.1", @@ -21403,9 +22281,9 @@ "dev": true }, "@types/react": { - "version": "17.0.53", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz", - "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==", + "version": "17.0.58", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz", + "integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==", "dev": true, "requires": { "@types/prop-types": "*", @@ -21519,15 +22397,15 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz", - "integrity": "sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz", + "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/type-utils": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/type-utils": "5.58.0", + "@typescript-eslint/utils": "5.58.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -21537,9 +22415,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -21548,53 +22426,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", - "integrity": "sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz", + "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", - "integrity": "sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz", + "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0" + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0" } }, "@typescript-eslint/type-utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", - "integrity": "sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz", + "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.56.0", - "@typescript-eslint/utils": "5.56.0", + "@typescript-eslint/typescript-estree": "5.58.0", + "@typescript-eslint/utils": "5.58.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.56.0.tgz", - "integrity": "sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz", + "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz", - "integrity": "sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz", + "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/visitor-keys": "5.56.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/visitor-keys": "5.58.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21623,9 +22501,9 @@ } }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -21634,25 +22512,25 @@ } }, "@typescript-eslint/utils": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", - "integrity": "sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz", + "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.56.0", - "@typescript-eslint/types": "5.56.0", - "@typescript-eslint/typescript-estree": "5.56.0", + "@typescript-eslint/scope-manager": "5.58.0", + "@typescript-eslint/types": "5.58.0", + "@typescript-eslint/typescript-estree": "5.58.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -21661,19 +22539,19 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.56.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", - "integrity": "sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q==", + "version": "5.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz", + "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.56.0", + "@typescript-eslint/types": "5.58.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true } } @@ -21688,7 +22566,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/helper-numbers": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1" @@ -21698,25 +22576,25 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-api-error": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-buffer": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-numbers": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/floating-point-hex-parser": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -21727,13 +22605,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "devOptional": true + "dev": true }, "@webassemblyjs/helper-wasm-section": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21745,7 +22623,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "devOptional": true, + "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } @@ -21754,7 +22632,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "devOptional": true, + "dev": true, "requires": { "@xtuc/long": "4.2.2" } @@ -21763,13 +22641,13 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "devOptional": true + "dev": true }, "@webassemblyjs/wasm-edit": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21785,7 +22663,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.1", @@ -21798,7 +22676,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-buffer": "1.11.1", @@ -21810,7 +22688,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/helper-api-error": "1.11.1", @@ -21824,7 +22702,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "devOptional": true, + "dev": true, "requires": { "@webassemblyjs/ast": "1.11.1", "@xtuc/long": "4.2.2" @@ -21860,13 +22738,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "devOptional": true + "dev": true }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "devOptional": true + "dev": true }, "abab": { "version": "2.0.6", @@ -21874,6 +22752,12 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -21888,13 +22772,13 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "devOptional": true + "dev": true }, "acorn-import-assertions": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "devOptional": true, + "dev": true, "requires": {} }, "acorn-jsx": { @@ -21904,6 +22788,22 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "peer": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "requires": { + "debug": "4" + } + }, "ajv": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", @@ -21944,7 +22844,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "devOptional": true }, "ansi-styles": { "version": "3.2.1", @@ -21965,6 +22865,42 @@ "picomatch": "^2.0.4" } }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "optional": true + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "peer": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -22097,20 +23033,12 @@ "dev": true }, "ast-metadata-inferer": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.7.0.tgz", - "integrity": "sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/ast-metadata-inferer/-/ast-metadata-inferer-0.8.0.tgz", + "integrity": "sha512-jOMKcHht9LxYIEQu+RVd22vtgrPaVCtDRQ/16IGmurdzxvYbDd5ynxjnyrzLnieG96eTcAyaoj/wN/4/1FyyeA==", "dev": true, "requires": { - "@mdn/browser-compat-data": "^3.3.14" - }, - "dependencies": { - "@mdn/browser-compat-data": { - "version": "3.3.14", - "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-3.3.14.tgz", - "integrity": "sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==", - "dev": true - } + "@mdn/browser-compat-data": "^5.2.34" } }, "ast-types-flow": { @@ -22134,7 +23062,8 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "peer": true }, "at-least-node": { "version": "1.0.0", @@ -22178,6 +23107,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "peer": true, "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -22252,7 +23182,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true }, "base": { "version": "0.11.2", @@ -22319,7 +23249,7 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "devOptional": true + "dev": true }, "binary-extensions": { "version": "2.2.0", @@ -22433,7 +23363,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -22452,7 +23382,7 @@ "version": "4.21.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", - "devOptional": true, + "dev": true, "requires": { "caniuse-lite": "^1.0.30001449", "electron-to-chromium": "^1.4.284", @@ -22464,7 +23394,7 @@ "version": "2.0.10", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", - "devOptional": true + "dev": true } } }, @@ -22472,7 +23402,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "devOptional": true + "dev": true }, "builtin-modules": { "version": "3.2.0", @@ -22589,10 +23519,21 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001466", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001466.tgz", - "integrity": "sha512-ewtFBSfWjEmxUgNBSZItFSmVtvk9zkwkl1OfRZlKA8slltRN+/C/tuGVrF9styXkN36Yu3+SeJ1qkXxDEyNZ5w==", - "devOptional": true + "version": "1.0.30001480", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001480.tgz", + "integrity": "sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==", + "dev": true + }, + "canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "optional": true, + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + } }, "ccount": { "version": "1.1.0", @@ -22651,11 +23592,17 @@ "readdirp": "~3.6.0" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "optional": true + }, "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "devOptional": true + "dev": true }, "class-utils": { "version": "0.3.6", @@ -22777,6 +23724,12 @@ "simple-swizzle": "^0.2.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "optional": true + }, "colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -22803,6 +23756,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "peer": true, "requires": { "delayed-stream": "~1.0.0" } @@ -22881,7 +23835,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "devOptional": true }, "confusing-browser-globals": { "version": "1.0.11", @@ -22895,6 +23849,12 @@ "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "optional": true + }, "content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -22990,9 +23950,9 @@ } }, "core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==" + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.0.tgz", + "integrity": "sha512-hQotSSARoNh1mYPi9O2YaWeiq/cEB95kOrFb4NCrO4RIFt1qqNpKsaE+vy/L3oiqvND5cThqXzUU3r9F7Efztg==" }, "core-js-compat": { "version": "3.25.3", @@ -23020,6 +23980,13 @@ "parse-json": "^4.0.0" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "peer": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -23047,9 +24014,9 @@ "dev": true }, "css-blank-pseudo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-5.0.1.tgz", - "integrity": "sha512-uEWT+613bR0lxUAz7BDdk4yZJ1BfzIJ9rmyOFj+p53ZP8rm0BC3nA2YsyswyxjFZsrfRDxe2WERDfKiEZNSXag==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-5.0.2.tgz", + "integrity": "sha512-aCU4AZ7uEcVSUzagTlA9pHciz7aWPKA/YzrEkpdSopJ2pvhIxiQ5sYeMz1/KByxlIo4XBdvMNJAVKMg/GRnhfw==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" @@ -23069,9 +24036,9 @@ "dev": true }, "css-has-pseudo": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-5.0.1.tgz", - "integrity": "sha512-zhsGCKVkBohliMlcsZsv5WF/i4FQ3pkVMtl4yYa7Qpv/PVQebTjh7cjMoT5grW+DBZzunmgHe6skdWawgCYuPQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-5.0.2.tgz", + "integrity": "sha512-q+U+4QdwwB7T9VEW/LyO6CFrLAeLqOykC5mDqJXc7aKZAhDbq7BvGT13VGJe+IwBfdN2o3Xdw2kJ5IxwV1Sc9Q==", "dev": true, "requires": { "@csstools/selector-specificity": "^2.0.1", @@ -23107,9 +24074,9 @@ } }, "css-prefers-color-scheme": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-8.0.1.tgz", - "integrity": "sha512-RPRyqJsk5KIjP2+WGhcGCaAJB8ojLbX1mVE8fG9127jQmnp1FNMfNMkERk/w6c4smgC/i5KxcY+Rtaa6/bMdKQ==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-8.0.2.tgz", + "integrity": "sha512-OvFghizHJ45x7nsJJUSYLyQNTzsCU8yWjxAc/nhPQg1pbs18LMoET8N3kOweFDPy0JV0OSXN2iqRFhPBHYOeMA==", "dev": true, "requires": {} }, @@ -23157,21 +24124,13 @@ } }, "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", "dev": true, "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" } }, "css-what": { @@ -23181,9 +24140,9 @@ "dev": true }, "cssdb": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz", - "integrity": "sha512-0Q8NOMpXJ3iTDDbUv9grcmQAfdDx4qz+fN/+Md2FGbevT+6+bJNQ2LjB2YIUlLbpBTM32idU1Sb+tb/uGt6/XQ==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.5.4.tgz", + "integrity": "sha512-fGD+J6Jlq+aurfE1VDXlLS4Pt0VtNlu2+YgfGOdMxRyl/HQ9bDiHTwSck1Yz8A97Dt/82izSK6Bp/4nVqacOsg==", "dev": true }, "cssesc": { @@ -23193,67 +24152,84 @@ "dev": true }, "cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.0.0.tgz", + "integrity": "sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==", "dev": true, "requires": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" + "cssnano-preset-default": "^6.0.0", + "lilconfig": "^2.1.0" } }, "cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.0.0.tgz", + "integrity": "sha512-BDxlaFzObRDXUiCCBQUNQcI+f1/aX2mgoNtXGjV6PG64POcHoDUoX+LgMWw+Q4609QhxwkcSnS65YFs42RA6qQ==", "dev": true, "requires": { "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" + "postcss-colormin": "^6.0.0", + "postcss-convert-values": "^6.0.0", + "postcss-discard-comments": "^6.0.0", + "postcss-discard-duplicates": "^6.0.0", + "postcss-discard-empty": "^6.0.0", + "postcss-discard-overridden": "^6.0.0", + "postcss-merge-longhand": "^6.0.0", + "postcss-merge-rules": "^6.0.0", + "postcss-minify-font-values": "^6.0.0", + "postcss-minify-gradients": "^6.0.0", + "postcss-minify-params": "^6.0.0", + "postcss-minify-selectors": "^6.0.0", + "postcss-normalize-charset": "^6.0.0", + "postcss-normalize-display-values": "^6.0.0", + "postcss-normalize-positions": "^6.0.0", + "postcss-normalize-repeat-style": "^6.0.0", + "postcss-normalize-string": "^6.0.0", + "postcss-normalize-timing-functions": "^6.0.0", + "postcss-normalize-unicode": "^6.0.0", + "postcss-normalize-url": "^6.0.0", + "postcss-normalize-whitespace": "^6.0.0", + "postcss-ordered-values": "^6.0.0", + "postcss-reduce-initial": "^6.0.0", + "postcss-reduce-transforms": "^6.0.0", + "postcss-svgo": "^6.0.0", + "postcss-unique-selectors": "^6.0.0" } }, "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.0.tgz", + "integrity": "sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==", "dev": true, "requires": {} }, "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, "requires": { - "css-tree": "^1.1.2" + "css-tree": "~2.2.0" + }, + "dependencies": { + "css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "requires": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + } + }, + "mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + } } }, "csstype": { @@ -23295,7 +24271,7 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -23330,6 +24306,15 @@ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-equal": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.0.tgz", @@ -23459,7 +24444,14 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "peer": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "optional": true }, "depd": { "version": "1.1.2", @@ -23473,12 +24465,25 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true }, + "detect-libc": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "optional": true + }, "detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "peer": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -23488,6 +24493,12 @@ "path-type": "^4.0.0" } }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", @@ -23561,11 +24572,6 @@ } } }, - "dommatrix": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", - "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" - }, "dompurify": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", @@ -23682,19 +24688,19 @@ "version": "1.4.302", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.302.tgz", "integrity": "sha512-Uk7C+7aPBryUR1Fwvk9VmipBcN9fVsqBO57jV2ZjTm+IZ6BMNqu7EDVEg2HxCNufk6QcWlFsBkhQyQroB2VWKw==", - "devOptional": true + "dev": true }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "devOptional": true }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "devOptional": true + "dev": true }, "enabled": { "version": "2.0.0", @@ -23712,7 +24718,7 @@ "version": "5.10.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "devOptional": true, + "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -23789,9 +24795,9 @@ } }, "es-check": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/es-check/-/es-check-7.1.0.tgz", - "integrity": "sha512-t099vm9tNqNHF28Q/mRcqYxmkbkoo/Qu2ZI5/D+eFeqNUjI3jwkIyHyexXiAtstbZ1FQELi0QCuUaYCtiffi4Q==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/es-check/-/es-check-7.1.1.tgz", + "integrity": "sha512-rgwR2wdJp437Exq28Emwc4x5+Qn6ORDliN9daWo0wTCg5jOQxJsIZieqxVi4AfDEIN4OwMwYhld9b13mnRocUQ==", "dev": true, "requires": { "acorn": "8.8.2", @@ -23850,10 +24856,10 @@ } }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "devOptional": true + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", + "dev": true }, "es-shim-unscopables": { "version": "1.0.0", @@ -23913,7 +24919,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true + "dev": true }, "escape-html": { "version": "1.0.3", @@ -23927,15 +24933,15 @@ "dev": true }, "eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -23946,8 +24952,8 @@ "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -24033,9 +25039,9 @@ "dev": true }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -24043,9 +25049,9 @@ } }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true }, "estraverse": { @@ -24217,15 +25223,16 @@ } }, "eslint-plugin-compat": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.2.tgz", - "integrity": "sha512-DNrQgDi5L4mAL4FdFboKBlSRg6MWfd75eA7K91lMjtP5ryN+O11qT2FDn7Z6zqy6sZ4sJawUR5V75qzB6l0CBg==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-compat/-/eslint-plugin-compat-4.1.4.tgz", + "integrity": "sha512-RxySWBmzfIROLFKgeJBJue2BU/6vM2KJWXWAUq+oW4QtrsZXRxbjgxmO1OfF3sHcRuuIenTS/wgo3GyUWZF24w==", "dev": true, "requires": { - "@mdn/browser-compat-data": "^5.2.34", - "ast-metadata-inferer": "^0.7.0", + "@mdn/browser-compat-data": "^5.2.47", + "@tsconfig/node14": "^1.0.3", + "ast-metadata-inferer": "^0.8.0", "browserslist": "^4.21.5", - "caniuse-lite": "^1.0.30001451", + "caniuse-lite": "^1.0.30001473", "find-up": "^5.0.0", "lodash.memoize": "4.1.2", "semver": "7.3.8" @@ -24457,7 +25464,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "devOptional": true, + "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -24470,20 +25477,20 @@ "dev": true }, "espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", "dev": true } } @@ -24515,7 +25522,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "requires": { "estraverse": "^5.2.0" }, @@ -24524,7 +25531,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "devOptional": true + "dev": true } } }, @@ -24532,7 +25539,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "devOptional": true + "dev": true }, "estree-walker": { "version": "1.0.1", @@ -24561,6 +25568,10 @@ "es5-ext": "~0.10.14" } }, + "event-target-polyfill": { + "version": "git+ssh://git@github.com/ThaUnknown/event-target-polyfill.git#5cb9a0ed6774af1b905b525964316911375726a7", + "from": "event-target-polyfill@github:ThaUnknown/event-target-polyfill" + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -24571,7 +25582,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "devOptional": true + "dev": true }, "execa": { "version": "5.1.1", @@ -24844,7 +25855,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true + "dev": true }, "fast-glob": { "version": "3.2.12", @@ -24863,7 +25874,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "dev": true }, "fast-levenshtein": { "version": "2.0.6", @@ -25064,6 +26075,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "peer": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -25109,6 +26121,26 @@ "universalify": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -25119,7 +26151,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "devOptional": true }, "fsevents": { "version": "2.3.2", @@ -25158,6 +26190,23 @@ "integrity": "sha1-szmUr0V6gRVwDUEPMXczy+egkEs=", "dev": true }, + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -25207,7 +26256,7 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, + "devOptional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -25318,7 +26367,7 @@ "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true + "dev": true }, "grapheme-splitter": { "version": "1.0.4", @@ -25400,6 +26449,12 @@ "has-symbols": "^1.0.2" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "optional": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -25478,9 +26533,9 @@ } }, "hls.js": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.2.4.tgz", - "integrity": "sha512-yC3K79Kzq1W+OgjT12JxKMDXv9DbfvulppxmPBl7D04SaTyd2IwWk5eNASQV1mUaPlKbjr16yI9292qpSGo0ig==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.4.0.tgz", + "integrity": "sha512-VEjg7Rx5FlE9TB3MIn0HPgq3J+vR7EoQnjaqMCk/ISEaCOSZlAFh4g867f1QkSxZiq9kHeUZo+iH16X7VS3jKA==" }, "hoist-non-react-statics": { "version": "3.3.2", @@ -25723,6 +26778,16 @@ } } }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -25816,7 +26881,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, + "devOptional": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -26079,7 +27144,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "devOptional": true }, "is-glob": { "version": "4.0.3", @@ -26311,6 +27376,14 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, + "jassub": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/jassub/-/jassub-1.5.12.tgz", + "integrity": "sha512-CJiuNCXMMGqfmVVlaDyxqaKfOy3RIHW4HBwVWvbq8pl/d1/y1fgTarfR31whUUupHZCe7Tfq8XB7WDgdu6IHaA==", + "requires": { + "rvfc-polyfill": "^1.0.4" + } + }, "jellyfin-apiclient": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/jellyfin-apiclient/-/jellyfin-apiclient-1.10.0.tgz", @@ -26320,7 +27393,7 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, + "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -26331,13 +27404,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true + "dev": true }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, + "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -26392,7 +27465,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true + "dev": true }, "json-schema": { "version": "0.4.0", @@ -26416,7 +27489,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true + "dev": true }, "jsonfile": { "version": "6.1.0", @@ -26552,9 +27625,9 @@ } }, "lilconfig": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", - "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "dev": true }, "lines-and-columns": { @@ -26567,13 +27640,13 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", - "devOptional": true + "dev": true }, "loader-utils": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "devOptional": true, + "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -26693,7 +27766,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -26711,11 +27784,18 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, + "devOptional": true, "requires": { "semver": "^6.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "peer": true + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -26780,9 +27860,9 @@ } }, "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, "media-typer": { @@ -26868,7 +27948,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true + "dev": true }, "merge2": { "version": "1.4.1", @@ -26917,6 +27997,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -26942,7 +28028,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -26964,6 +28050,33 @@ "kind-of": "^6.0.3" } }, + "minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "optional": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -26998,7 +28111,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true + }, + "nan": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", + "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==", + "optional": true }, "nanoid": { "version": "3.3.4", @@ -27052,7 +28171,7 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true + "dev": true }, "next-tick": { "version": "1.0.0", @@ -27069,6 +28188,39 @@ "tslib": "^2.0.3" } }, + "node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "optional": true, + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, "node-forge": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.0.tgz", @@ -27081,6 +28233,15 @@ "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "requires": { + "abbrev": "1" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -27119,12 +28280,6 @@ "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", "dev": true }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -27134,6 +28289,18 @@ "path-key": "^3.0.0" } }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "nth-check": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz", @@ -27311,7 +28478,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, + "devOptional": true, "requires": { "wrappy": "1" } @@ -27496,7 +28663,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "devOptional": true }, "path-is-inside": { "version": "1.0.2", @@ -27527,12 +28694,18 @@ "resolved": "https://registry.npmjs.org/path-webpack/-/path-webpack-0.0.3.tgz", "integrity": "sha1-/23sdJ7sWpRgXATV9j/FVgegOhY=" }, + "path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==" + }, "pdfjs-dist": { - "version": "2.16.105", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz", - "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==", + "version": "3.5.141", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.5.141.tgz", + "integrity": "sha512-lYIvyi5grtYOIatsfCifIKwxHeAJ8eHyP22DTdvY4pm0yWVSFQnMafpgCPSw8gaNRDDdcHnBVOkqMsyK8SRxZg==", "requires": { - "dommatrix": "^1.0.3", + "canvas": "^2.11.0", + "path2d-polyfill": "^2.0.1", "web-streams-polyfill": "^3.2.1" } }, @@ -27540,7 +28713,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "dev": true }, "picomatch": { "version": "2.3.1", @@ -27605,9 +28778,9 @@ } }, "postcss-attribute-case-insensitive": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.1.tgz", - "integrity": "sha512-XNVoIdu/Pskb5OhkM+iHicEVuASeqAjOTCaW8Wcbrd1UVwRukOJr5+zWzFjYxJj55Z/67ViVm9n/1hwF7MGByQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-6.0.2.tgz", + "integrity": "sha512-IRuCwwAAQbgaLhxQdQcIIK0dCVXg3XDUnzgKD8iwdiYdwU4rMWRWyl/W9/0nA4ihVpq5pyALiHB2veBJ0292pw==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" @@ -27633,36 +28806,36 @@ } }, "postcss-color-functional-notation": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-5.0.1.tgz", - "integrity": "sha512-Q9YDNQddKrl6YBs3229v+ckQINLyAaPfjJqG3jp5NUlP0UMm9+JeuLO1IMpeZy0l+rIE64y4OjUq0o+xhrnnrA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-5.0.2.tgz", + "integrity": "sha512-M6ygxWOyd6eWf3sd1Lv8xi4SeF4iBPfJvkfMU4ITh8ExJc1qhbvh/U8Cv/uOvBgUVOMDdScvCdlg8+hREQzs7w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-color-hex-alpha": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.1.tgz", - "integrity": "sha512-1ZTJvmOZXTCsedKeY+Puqwx6AgoyB1KnzSD/CGDIl1NWvDfxi1jYky4R9konw2SAYw0SOeU33EU27ihE59Fp8Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-9.0.2.tgz", + "integrity": "sha512-SfPjgr//VQ/DOCf80STIAsdAs7sbIbxATvVmd+Ec7JvR8onz9pjawhq3BJM3Pie40EE3TyB0P6hft16D33Nlyg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-color-rebeccapurple": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-8.0.1.tgz", - "integrity": "sha512-bzZYxBDx/uUGW9HeldOA7J69GdymOZJNz3pG8av27YSgJt9dobl4l+hI/3KAosoRJml/iWceT97pJQj3O/dQDw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-8.0.2.tgz", + "integrity": "sha512-xWf/JmAxVoB5bltHpXk+uGRoGFwu4WDAR7210el+iyvTdqiKpDhtcT8N3edXMoVJY0WHFMrKMUieql/wRNiXkw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.0.0.tgz", + "integrity": "sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==", "dev": true, "requires": { "browserslist": "^4.21.4", @@ -27672,9 +28845,9 @@ } }, "postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.0.0.tgz", + "integrity": "sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==", "dev": true, "requires": { "browserslist": "^4.21.4", @@ -27682,82 +28855,82 @@ } }, "postcss-custom-media": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.1.tgz", - "integrity": "sha512-veQwzQkHgBkizxYCV/EBsiK8sFIJA0oQMQL9mpQ3gqFGc2dWlNWURHk4J44i9Q0dFeFCK81vV/Xpj7fyfNQKSA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-9.1.3.tgz", + "integrity": "sha512-W1C4Fu6KAZ7sKYQCuGMr8gyaE4BtjTQGPLVS4m0WCaWM6l7PgVbvmDeb4ClBc5R/7kdwESYf0hdxGtEPhi9CLA==", "dev": true, "requires": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", - "@csstools/media-query-list-parser": "^2.0.0" + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/media-query-list-parser": "^2.0.4" } }, "postcss-custom-properties": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.1.1.tgz", - "integrity": "sha512-FK4dBiHdzWOosLu3kEAHaYpfcrnMfVV4nP6PT6EFIfWXrtHH9LY8idfTYnEDpq/vgE33mr8ykhs7BjlgcT9agg==", + "version": "13.1.5", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-13.1.5.tgz", + "integrity": "sha512-98DXk81zTGqMVkGANysMHbGIg3voH383DYo3/+c+Abzay3nao+vM/f4Jgzsakk9S7BDsEw5DiW7sFy5G4W2wLA==", "dev": true, "requires": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", "postcss-value-parser": "^4.2.0" } }, "postcss-custom-selectors": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.1.tgz", - "integrity": "sha512-CPs3BSdQfKqdrJ3d+3In9ppBPA8GpRy4Bd50jU+BDD6WEZOx8TTIB9i67BfRc2AVEAbRZwDMesreF95598dwhw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-7.1.3.tgz", + "integrity": "sha512-GTVscax6O/8s7agFF0HsOoIyjrnAbLjgCUle8tn+0oDGJuVx7p56U7ClSRoC49poxFuMfu2B4Q8GnxSCOeuFKw==", "dev": true, "requires": { - "@csstools/cascade-layer-name-parser": "^1.0.0", - "@csstools/css-parser-algorithms": "^2.0.0", - "@csstools/css-tokenizer": "^2.0.0", + "@csstools/cascade-layer-name-parser": "^1.0.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", "postcss-selector-parser": "^6.0.4" } }, "postcss-dir-pseudo-class": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.1.tgz", - "integrity": "sha512-VjiqVOTz1op7bsiw7qd5CjZ0txA5yJY/oo1wb3f37qdleRTZQ9hzhAtLDqXimn0ZKh9XbtYawc4pmVBnV+LyMA==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-7.0.2.tgz", + "integrity": "sha512-cMnslilYxBf9k3qejnovrUONZx1rXeUZJw06fgIUBzABJe3D2LiLL5WAER7Imt3nrkaIgG05XZBztueLEf5P8w==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" } }, "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.0.tgz", + "integrity": "sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==", "dev": true, "requires": {} }, "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.0.tgz", + "integrity": "sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==", "dev": true, "requires": {} }, "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.0.tgz", + "integrity": "sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==", "dev": true, "requires": {} }, "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.0.tgz", + "integrity": "sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==", "dev": true, "requires": {} }, "postcss-double-position-gradients": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.1.tgz", - "integrity": "sha512-XE+eKvX96E9cmldwKeRmK8AMxfQfuuHN9Yjerymau5i+fgC/vEY+B+Ke2vnEv4E8EXu8MKdLxi4DzmodusW19Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-4.0.2.tgz", + "integrity": "sha512-GXL1RmFREDK4Q9aYvI2RhVrA6a6qqSMQQ5ke8gSH1xgV6exsqbcJpIumC7AOgooH6/WIG3/K/T8xxAiVHy/tJg==", "dev": true, "requires": { "@csstools/postcss-progressive-custom-properties": "^2.0.0", @@ -27765,18 +28938,18 @@ } }, "postcss-focus-visible": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-8.0.1.tgz", - "integrity": "sha512-azd1NMrLBe5bfKyomui9AMcgIR2zzlqXCTnKjshNDSClmmSO5MauTyflJUqmIwjIhD16+FbPyGV8Nxsly87BjA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-8.0.2.tgz", + "integrity": "sha512-f/Vd+EC/GaKElknU59esVcRYr/Y3t1ZAQyL4u2xSOgkDy4bMCmG7VP5cGvj3+BTLNE9ETfEuz2nnt4qkZwTTeA==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" } }, "postcss-focus-within": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-7.0.1.tgz", - "integrity": "sha512-iSpk018Yqn0xwltFR7NHjagyt+e/6u8w50uEnGOcFOddLay5zQFjpJBg6euEZu7wY5WDq83DPpdO99eL+8Er8g==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-7.0.2.tgz", + "integrity": "sha512-AHAJ89UQBcqBvFgQJE9XasGuwMNkKsGj4D/f9Uk60jFmEBHpAL14DrnSk3Rj+SwZTr/WUG+mh+Rvf8fid/346w==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" @@ -27848,9 +29021,9 @@ } }, "postcss-image-set-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-5.0.1.tgz", - "integrity": "sha512-JnmN9Wo7WjlvM7fg00wzC4d/1kOqau+6v6hteLLqEyBjCuzoFZUU0Te3JphDyxc65RtPNsCujDwYbbs6+vYxCQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-5.0.2.tgz", + "integrity": "sha512-Sszjwo0ubETX0Fi5MvpYzsONwrsjeabjMoc5YqHvURFItXgIu3HdCjcVuVKGMPGzKRhgaknmdM5uVWInWPJmeg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -27873,13 +29046,15 @@ } }, "postcss-lab-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-5.0.1.tgz", - "integrity": "sha512-TuvrxsRIA3oWjjjI9T1ZEAolrtrLzYwYDw14GFivy0BkRqUTi4IithbM1aZkZGbAxV4lLwD6rL7MHhfDslUEzg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-5.2.1.tgz", + "integrity": "sha512-u71Adr4nWi+4EmSZq5EV/fg9d1dYO6W26RNtT9LISEyjhH1q23vJIUkSqRwHgD6v7xxsxLOY5cSdVyaNE6rqzw==", "dev": true, "requires": { - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "@csstools/css-color-parser": "^1.1.2", + "@csstools/css-parser-algorithms": "^2.1.1", + "@csstools/css-tokenizer": "^2.1.1", + "@csstools/postcss-progressive-custom-properties": "^2.0.0" } }, "postcss-less": { @@ -27920,12 +29095,13 @@ } }, "postcss-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.1.0.tgz", - "integrity": "sha512-vTD2DJ8vJD0Vr1WzMQkRZWRjcynGh3t7NeoLg+Sb1TeuK7etiZfL/ZwHbaVa3M+Qni7Lj/29voV9IggnIUjlIw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.2.4.tgz", + "integrity": "sha512-F88rpxxNspo5hatIc+orYwZDtHFaVFOSIVAx+fBfJC1GmhWbVmPWtmg2gXKE1OxJbneOSGn8PWdIwsZFcruS+w==", "dev": true, "requires": { - "cosmiconfig": "^8.0.0", + "cosmiconfig": "^8.1.3", + "cosmiconfig-typescript-loader": "^4.3.0", "klona": "^2.0.6", "semver": "^7.3.8" }, @@ -27948,6 +29124,13 @@ "path-type": "^4.0.0" } }, + "cosmiconfig-typescript-loader": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz", + "integrity": "sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==", + "dev": true, + "requires": {} + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -27986,9 +29169,9 @@ "dev": true }, "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -27997,9 +29180,9 @@ } }, "postcss-logical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-6.0.1.tgz", - "integrity": "sha512-0LIzRgbT42n0q8txcM9SrLkYLjr1LTbRTy80bnKiYXY8tnYGdjkBymwb5XE87o4csW1z8dhKD1VRI6cHBQBQtw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-6.1.0.tgz", + "integrity": "sha512-qb1+LpClhYjxac8SfOcWotnY3unKZesDqIOm+jnGt8rTl7xaIWpE2bPGZHxflOip1E/4ETo79qlJyRL3yrHn1g==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -28145,13 +29328,6 @@ } } }, - "postcss-media-minmax": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", - "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true, - "requires": {} - }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -28159,62 +29335,62 @@ "dev": true }, "postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.0.tgz", + "integrity": "sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" + "stylehacks": "^6.0.0" } }, "postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.0.0.tgz", + "integrity": "sha512-rCXkklftzEkniyv3f4mRCQzxD6oE4Quyh61uyWTUbCJ26Pv2hoz+fivJSsSBWxDBeScR4fKCfF3HHTcD7Ybqnw==", "dev": true, "requires": { "browserslist": "^4.21.4", "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-selector-parser": "^6.0.5" } }, "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.0.0.tgz", + "integrity": "sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.0.tgz", + "integrity": "sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==", "dev": true, "requires": { "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.0.0.tgz", + "integrity": "sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==", "dev": true, "requires": { "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" } }, "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.0.tgz", + "integrity": "sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.5" @@ -28257,9 +29433,9 @@ } }, "postcss-nesting": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-11.1.0.tgz", - "integrity": "sha512-TVBCeKlUmMyX3sNeSg10yATb2XmAoosp0E1zdlpjrD+L2FrQPmrRTxlRFQh/R0Y4WlQ0butfDwRhzlYuj7y/TA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-11.2.2.tgz", + "integrity": "sha512-aOTiUniAB1bcPE6GGiynWRa6PZFPhOTAm5q3q5cem6QeSijIHHkWr6gs65ukCZMXeak8yXeZVbBJET3VM+HlhA==", "dev": true, "requires": { "@csstools/selector-specificity": "^2.0.0", @@ -28267,61 +29443,61 @@ } }, "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.0.tgz", + "integrity": "sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==", "dev": true, "requires": {} }, "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.0.tgz", + "integrity": "sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.0.tgz", + "integrity": "sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.0.tgz", + "integrity": "sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.0.tgz", + "integrity": "sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.0.tgz", + "integrity": "sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.0.0.tgz", + "integrity": "sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==", "dev": true, "requires": { "browserslist": "^4.21.4", @@ -28329,38 +29505,37 @@ } }, "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.0.tgz", + "integrity": "sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==", "dev": true, "requires": { - "normalize-url": "^6.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.0.tgz", + "integrity": "sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" } }, "postcss-opacity-percentage": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", - "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-2.0.0.tgz", + "integrity": "sha512-lyDrCOtntq5Y1JZpBFzIWm2wG9kbEdujpNt4NLannF+J9c8CgFIzPa80YQfdza+Y+yFfzbYj/rfoOsYsooUWTQ==", "dev": true, "requires": {} }, "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.0.tgz", + "integrity": "sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==", "dev": true, "requires": { - "cssnano-utils": "^3.1.0", + "cssnano-utils": "^4.0.0", "postcss-value-parser": "^4.2.0" } }, @@ -28390,79 +29565,81 @@ } }, "postcss-preset-env": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-8.0.1.tgz", - "integrity": "sha512-IUbymw0JlUbyVG+I85963PNWgPp3KhnFa1sxU7M/2dGthxV8e297P0VV5W9XcyypoH4hirH2fp1c6fmqh6YnSg==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-8.3.1.tgz", + "integrity": "sha512-k3Y8BXbVLBAufrla3CNmQJhMS1iRuT9LFlysYvzs1rU5E78+ShX2u0EUL6KpMi0pDJO3wZcuVYSR8cgukfoRtg==", "dev": true, "requires": { - "@csstools/postcss-cascade-layers": "^3.0.0", - "@csstools/postcss-color-function": "^2.0.0", - "@csstools/postcss-font-format-keywords": "^2.0.0", - "@csstools/postcss-hwb-function": "^2.0.0", - "@csstools/postcss-ic-unit": "^2.0.0", - "@csstools/postcss-is-pseudo-class": "^3.0.0", - "@csstools/postcss-logical-float-and-clear": "^1.0.0", - "@csstools/postcss-logical-resize": "^1.0.0", - "@csstools/postcss-logical-viewport-units": "^1.0.0", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.0", - "@csstools/postcss-nested-calc": "^2.0.0", - "@csstools/postcss-normalize-display-values": "^2.0.0", - "@csstools/postcss-oklab-function": "^2.0.0", - "@csstools/postcss-progressive-custom-properties": "^2.0.0", - "@csstools/postcss-scope-pseudo-class": "^2.0.0", - "@csstools/postcss-stepped-value-functions": "^2.0.0", - "@csstools/postcss-text-decoration-shorthand": "^2.0.0", - "@csstools/postcss-trigonometric-functions": "^2.0.0", - "@csstools/postcss-unset-value": "^2.0.0", - "autoprefixer": "^10.4.13", - "browserslist": "^4.21.4", - "css-blank-pseudo": "^5.0.0", - "css-has-pseudo": "^5.0.0", - "css-prefers-color-scheme": "^8.0.0", - "cssdb": "^7.4.0", - "postcss-attribute-case-insensitive": "^6.0.0", + "@csstools/postcss-cascade-layers": "^3.0.1", + "@csstools/postcss-color-function": "^2.2.1", + "@csstools/postcss-color-mix-function": "^1.0.1", + "@csstools/postcss-font-format-keywords": "^2.0.2", + "@csstools/postcss-gradients-interpolation-method": "^3.0.3", + "@csstools/postcss-hwb-function": "^2.2.1", + "@csstools/postcss-ic-unit": "^2.0.2", + "@csstools/postcss-is-pseudo-class": "^3.2.0", + "@csstools/postcss-logical-float-and-clear": "^1.0.1", + "@csstools/postcss-logical-resize": "^1.0.1", + "@csstools/postcss-logical-viewport-units": "^1.0.3", + "@csstools/postcss-media-minmax": "^1.0.2", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.2", + "@csstools/postcss-nested-calc": "^2.0.2", + "@csstools/postcss-normalize-display-values": "^2.0.1", + "@csstools/postcss-oklab-function": "^2.2.1", + "@csstools/postcss-progressive-custom-properties": "^2.1.0", + "@csstools/postcss-scope-pseudo-class": "^2.0.2", + "@csstools/postcss-stepped-value-functions": "^2.1.1", + "@csstools/postcss-text-decoration-shorthand": "^2.2.3", + "@csstools/postcss-trigonometric-functions": "^2.1.1", + "@csstools/postcss-unset-value": "^2.0.1", + "autoprefixer": "^10.4.14", + "browserslist": "^4.21.5", + "css-blank-pseudo": "^5.0.2", + "css-has-pseudo": "^5.0.2", + "css-prefers-color-scheme": "^8.0.2", + "cssdb": "^7.5.3", + "postcss-attribute-case-insensitive": "^6.0.2", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^5.0.0", - "postcss-color-hex-alpha": "^9.0.0", - "postcss-color-rebeccapurple": "^8.0.0", - "postcss-custom-media": "^9.1.0", - "postcss-custom-properties": "^13.1.0", - "postcss-custom-selectors": "^7.1.0", - "postcss-dir-pseudo-class": "^7.0.0", - "postcss-double-position-gradients": "^4.0.0", - "postcss-focus-visible": "^8.0.0", - "postcss-focus-within": "^7.0.0", + "postcss-color-functional-notation": "^5.0.2", + "postcss-color-hex-alpha": "^9.0.2", + "postcss-color-rebeccapurple": "^8.0.2", + "postcss-custom-media": "^9.1.3", + "postcss-custom-properties": "^13.1.5", + "postcss-custom-selectors": "^7.1.3", + "postcss-dir-pseudo-class": "^7.0.2", + "postcss-double-position-gradients": "^4.0.2", + "postcss-focus-visible": "^8.0.2", + "postcss-focus-within": "^7.0.2", "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^4.0.0", - "postcss-image-set-function": "^5.0.0", + "postcss-gap-properties": "^4.0.1", + "postcss-image-set-function": "^5.0.2", "postcss-initial": "^4.0.1", - "postcss-lab-function": "^5.0.0", - "postcss-logical": "^6.0.0", - "postcss-media-minmax": "^5.0.0", - "postcss-nesting": "^11.0.0", - "postcss-opacity-percentage": "^1.1.3", - "postcss-overflow-shorthand": "^4.0.0", + "postcss-lab-function": "^5.2.1", + "postcss-logical": "^6.1.0", + "postcss-nesting": "^11.2.1", + "postcss-opacity-percentage": "^2.0.0", + "postcss-overflow-shorthand": "^4.0.1", "postcss-page-break": "^3.0.4", - "postcss-place": "^8.0.0", - "postcss-pseudo-class-any-link": "^8.0.0", + "postcss-place": "^8.0.1", + "postcss-pseudo-class-any-link": "^8.0.2", "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^7.0.0", + "postcss-selector-not": "^7.0.1", "postcss-value-parser": "^4.2.0" } }, "postcss-pseudo-class-any-link": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-8.0.1.tgz", - "integrity": "sha512-CYcLGofbGDhx6BmNFQGFH0cqW+qlXVk9PR4LZ8Y7g24m6TopYKt6FSwhMGAIyme6lQxgB32XMhpYRwZAcPnMXA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-8.0.2.tgz", + "integrity": "sha512-FYTIuRE07jZ2CW8POvctRgArQJ43yxhr5vLmImdKUvjFCkR09kh8pIdlCwdx/jbFm7MiW4QP58L4oOUv3grQYA==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.10" } }, "postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.0.0.tgz", + "integrity": "sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==", "dev": true, "requires": { "browserslist": "^4.21.4", @@ -28470,9 +29647,9 @@ } }, "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.0.tgz", + "integrity": "sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0" @@ -28642,13 +29819,13 @@ } }, "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.0.tgz", + "integrity": "sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==", "dev": true, "requires": { "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" + "svgo": "^3.0.2" } }, "postcss-syntax": { @@ -28659,9 +29836,9 @@ "requires": {} }, "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.0.tgz", + "integrity": "sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==", "dev": true, "requires": { "postcss-selector-parser": "^6.0.5" @@ -28732,13 +29909,14 @@ "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "devOptional": true + "dev": true }, "qs": { "version": "6.10.3", @@ -28765,7 +29943,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "devOptional": true, + "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -28830,20 +30008,20 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-router": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.9.0.tgz", - "integrity": "sha512-51lKevGNUHrt6kLuX3e/ihrXoXCa9ixY/nVWRLlob4r/l0f45x3SzBvYJe3ctleLUQQ5fVa4RGgJOTH7D9Umhw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", "requires": { - "@remix-run/router": "1.4.0" + "@remix-run/router": "1.5.0" } }, "react-router-dom": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.9.0.tgz", - "integrity": "sha512-/seUAPY01VAuwkGyVBPCn1OXfVbaWGGu4QN9uj0kCPcTyNYgL1ldZpxZUpRU7BLheKQI4Twtl/OW2nHRF1u26Q==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", "requires": { - "@remix-run/router": "1.4.0", - "react-router": "6.9.0" + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" } }, "read-file-stdin": { @@ -28969,9 +30147,9 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.1.tgz", + "integrity": "sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==", "dev": true, "requires": { "@babel/runtime": "^7.8.4" @@ -28999,25 +30177,19 @@ } }, "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dev": true, "requires": { + "@babel/regjsgen": "^0.8.0", "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", "regjsparser": "^0.9.1", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" + "unicode-match-property-value-ecmascript": "^2.1.0" } }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "dev": true - }, "regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -29222,6 +30394,11 @@ "queue-microtask": "^1.2.2" } }, + "rvfc-polyfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/rvfc-polyfill/-/rvfc-polyfill-1.0.4.tgz", + "integrity": "sha512-BemRbBDZiLB8pxoPT+2q6R30ykY1e75XBE/L1A0Ubd/3KdUoCQLqI/z4v4oNFNlN3/Rs93d3b6WoybnXhdebkw==" + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -29260,9 +30437,9 @@ "dev": true }, "sass": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.60.0.tgz", - "integrity": "sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==", + "version": "1.62.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz", + "integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -29271,9 +30448,9 @@ } }, "sass-loader": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.1.tgz", - "integrity": "sha512-VQUrgUa5/waIzMrzyuko3sj5WD9NMsYph91cNICx+OaODbRtLl6To2fswLx8MH2qNxXFqRtpvdPQIa7mE93YOA==", + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", + "integrity": "sha512-nrIdVAAte3B9icfBiGWvmMhT/D+eCDwnk+yA7VE/76dp/WkHX+i44Q/pfo71NYbwj0Ap+PGsn0ekOuU1WFJ2AA==", "dev": true, "requires": { "klona": "^2.0.6", @@ -29325,7 +30502,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "devOptional": true }, "send": { "version": "0.18.0", @@ -29386,10 +30563,10 @@ } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "devOptional": true, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -29462,6 +30639,12 @@ "send": "0.18.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "optional": true + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -29541,7 +30724,24 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "devOptional": true + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "optional": true + }, + "simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } }, "simple-swizzle": { "version": "0.2.2", @@ -29793,7 +30993,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -29803,7 +31003,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true + "dev": true } } }, @@ -29917,12 +31117,6 @@ "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==" }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -29983,7 +31177,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -30069,7 +31263,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "requires": { "ansi-regex": "^5.0.1" } @@ -30121,9 +31315,9 @@ "dev": true }, "stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.0.0.tgz", + "integrity": "sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==", "dev": true, "requires": { "browserslist": "^4.21.4", @@ -30131,18 +31325,18 @@ } }, "stylelint": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.3.0.tgz", - "integrity": "sha512-9UYBYk7K9rtlKcTUDZrtntE840sZM00qyYBQHHe7tjwMNUsPsGvR6Fd43IxHEAhRrDLzpy3TVaHb6CReBB3eFg==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.4.0.tgz", + "integrity": "sha512-TlOvpG3MbcFwHmK0q2ykhmpKo7Dq892beJit0NPdpyY9b1tFah/hGhqnAz/bRm2PDhDbJLKvjzkEYYBEz7Dxcg==", "dev": true, "requires": { - "@csstools/css-parser-algorithms": "^2.0.1", + "@csstools/css-parser-algorithms": "^2.1.0", "@csstools/css-tokenizer": "^2.1.0", "@csstools/media-query-list-parser": "^2.0.1", - "@csstools/selector-specificity": "^2.1.1", + "@csstools/selector-specificity": "^2.2.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^8.1.0", + "cosmiconfig": "^8.1.3", "css-functions-list": "^3.1.0", "css-tree": "^2.3.1", "debug": "^4.3.4", @@ -30210,16 +31404,6 @@ "path-type": "^4.0.0" } }, - "css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "dev": true, - "requires": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - } - }, "globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -30267,12 +31451,6 @@ "argparse": "^2.0.1" } }, - "mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true - }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -32081,16 +33259,16 @@ } }, "stylelint-scss": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.5.0.tgz", - "integrity": "sha512-/+rQ8FePOiwT5xblOHkujYzRYfSjmE6HYhLpqJShL+9wH6/HaAVj4mWpXlpEsM3ZgIpOblG9Y+/BycSJzWgjNw==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.6.0.tgz", + "integrity": "sha512-M+E0BQim6G4XEkaceEhfVjP/41C9Klg5/tTPTCQVlgw/jm2tvB+OXJGaU0TDP5rnTCB62aX6w+rT+gqJW/uwjA==", "dev": true, "requires": { - "lodash": "^4.17.21", + "dlv": "^1.1.3", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.6", - "postcss-value-parser": "^4.1.0" + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" } }, "sugarss": { @@ -32179,18 +33357,81 @@ "dev": true }, "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", + "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", "dev": true, "requires": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" + "css-select": "^5.1.0", + "css-tree": "^2.2.1", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "dependencies": { + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true + }, + "dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + } + }, + "domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true + }, + "domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0" + } + }, + "domutils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", + "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "dev": true, + "requires": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.1" + } + }, + "entities": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", + "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "dev": true + } } }, "swiper": { @@ -32219,7 +33460,29 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", - "devOptional": true + "dev": true + }, + "tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "optional": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true + } + } }, "temp-dir": { "version": "2.0.0", @@ -32248,10 +33511,10 @@ } }, "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "devOptional": true, + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz", + "integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==", + "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", @@ -32263,28 +33526,28 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true + "dev": true } } }, "terser-webpack-plugin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz", - "integrity": "sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g==", - "devOptional": true, + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz", + "integrity": "sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw==", + "dev": true, "requires": { + "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1", - "terser": "^5.7.2" + "serialize-javascript": "^6.0.1", + "terser": "^5.16.5" }, "dependencies": { "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -32296,31 +33559,25 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "requires": {} }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true } } }, @@ -32512,6 +33769,28 @@ } } }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "peer": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, "tsconfig-paths": { "version": "3.14.1", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", @@ -32589,9 +33868,9 @@ } }, "typescript": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz", - "integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", + "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", "dev": true }, "unbox-primitive": { @@ -32633,9 +33912,9 @@ } }, "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", "dev": true }, "unicode-property-aliases-ecmascript": { @@ -32777,7 +34056,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", - "devOptional": true, + "dev": true, "requires": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -32787,7 +34066,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -32833,6 +34112,13 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "peer": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -32869,7 +34155,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "devOptional": true, + "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -32879,7 +34165,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true + "dev": true } } }, @@ -32909,13 +34195,13 @@ "dev": true }, "webpack": { - "version": "5.76.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.3.tgz", - "integrity": "sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA==", - "devOptional": true, + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", + "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", + "@types/estree": "^1.0.0", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -32924,7 +34210,7 @@ "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -32935,7 +34221,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -32944,7 +34230,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -32956,26 +34242,26 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "requires": {} }, "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -33041,9 +34327,9 @@ } }, "webpack-dev-server": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.1.tgz", - "integrity": "sha512-5tWg00bnWbYgkN+pd5yISQKDejRBYGEw15RaEEslH+zdbNDxxaZvEAO2WulaSaFKb5n3YG8JXsGaDsut1D0xdA==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", "dev": true, "requires": { "@types/bonjour": "^3.5.9", @@ -33079,9 +34365,9 @@ }, "dependencies": { "colorette": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", - "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, "rimraf": { @@ -33109,7 +34395,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "devOptional": true + "dev": true }, "websocket-driver": { "version": "0.7.4", @@ -33197,6 +34483,15 @@ "is-typed-array": "^1.1.10" } }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -33495,7 +34790,7 @@ "version": "3.0.8", "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz", "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==", - "devOptional": true, + "dev": true, "requires": { "loader-utils": "^2.0.0", "schema-utils": "^3.0.0" @@ -33505,7 +34800,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -33517,20 +34812,20 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, + "dev": true, "requires": {} }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "devOptional": true, + "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -33543,7 +34838,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "devOptional": true }, "write": { "version": "1.0.3", @@ -33601,13 +34896,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "devOptional": true }, "yargs-parser": { "version": "20.2.9", @@ -33615,6 +34904,13 @@ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "peer": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 4b23b79ae7..f1c8104f36 100644 --- a/package.json +++ b/package.json @@ -5,22 +5,21 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "7.21.3", + "@babel/core": "7.21.4", "@babel/eslint-parser": "7.21.3", "@babel/eslint-plugin": "7.19.1", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-transform-modules-umd": "7.18.6", - "@babel/preset-env": "7.20.2", + "@babel/preset-env": "7.21.4", "@babel/preset-react": "7.18.6", - "@babel/preset-typescript": "7.21.0", "@types/escape-html": "1.0.2", "@types/loadable__component": "5.13.4", "@types/lodash-es": "4.17.7", - "@types/react": "17.0.53", + "@types/react": "17.0.58", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.56.0", - "@typescript-eslint/parser": "5.56.0", + "@typescript-eslint/eslint-plugin": "5.58.0", + "@typescript-eslint/parser": "5.58.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.14", "babel-loader": "9.1.2", @@ -30,10 +29,10 @@ "copy-webpack-plugin": "11.0.0", "cross-env": "7.0.3", "css-loader": "6.7.3", - "cssnano": "5.1.15", - "es-check": "7.1.0", - "eslint": "8.36.0", - "eslint-plugin-compat": "4.1.2", + "cssnano": "6.0.0", + "es-check": "7.1.1", + "eslint": "8.38.0", + "eslint-plugin-compat": "4.1.4", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-jsx-a11y": "6.7.1", @@ -46,23 +45,23 @@ "html-webpack-plugin": "5.5.0", "mini-css-extract-plugin": "2.7.5", "postcss": "8.4.21", - "postcss-loader": "7.1.0", - "postcss-preset-env": "8.0.1", + "postcss-loader": "7.2.4", + "postcss-preset-env": "8.3.1", "postcss-scss": "4.0.6", - "sass": "1.60.0", - "sass-loader": "13.2.1", + "sass": "1.62.0", + "sass-loader": "13.2.2", "source-map-loader": "4.0.1", "style-loader": "3.3.2", - "stylelint": "15.3.0", + "stylelint": "15.4.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "6.0.3", - "stylelint-scss": "4.5.0", + "stylelint-scss": "4.6.0", "ts-loader": "9.4.2", - "typescript": "5.0.2", - "webpack": "5.76.3", + "typescript": "5.0.4", + "webpack": "5.79.0", "webpack-cli": "5.0.1", - "webpack-dev-server": "4.13.1", + "webpack-dev-server": "4.13.2", "webpack-merge": "5.8.0", "workbox-webpack-plugin": "6.5.4", "worker-loader": "3.0.8" @@ -74,23 +73,24 @@ "@fontsource/noto-sans-kr": "4.5.12", "@fontsource/noto-sans-sc": "4.5.12", "@fontsource/noto-sans-tc": "4.5.12", - "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "unstable", "@loadable/component": "5.15.3", "blurhash": "2.0.5", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.29.1", + "core-js": "3.30.0", "date-fns": "2.29.3", "dompurify": "3.0.1", "epubjs": "0.3.93", "escape-html": "1.0.3", + "event-target-polyfill": "github:ThaUnknown/event-target-polyfill", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", "headroom.js": "0.12.0", "history": "5.3.0", - "hls.js": "1.2.4", + "hls.js": "1.4.0", "intersection-observer": "0.12.2", + "jassub": "1.5.12", "jellyfin-apiclient": "1.10.0", "jquery": "3.6.4", "jstree": "3.3.15", @@ -99,10 +99,10 @@ "marked": "4.3.0", "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", - "pdfjs-dist": "2.16.105", + "pdfjs-dist": "3.5.141", "react": "17.0.2", "react-dom": "17.0.2", - "react-router-dom": "6.9.0", + "react-router-dom": "6.10.0", "resize-observer-polyfill": "1.5.1", "screenfull": "6.0.2", "sortablejs": "1.15.0", @@ -131,8 +131,8 @@ "scripts": { "start": "npm run serve", "serve": "webpack serve --config webpack.dev.js", - "build:development": "webpack --config webpack.dev.js", - "build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js", + "build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js", + "build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js", "build:check": "tsc --noEmit", "escheck": "es-check", "lint": "eslint \"./\"", diff --git a/src/App.tsx b/src/App.tsx index 79828b6bd0..1a544723a1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,38 @@ import { History } from '@remix-run/router'; -import React from 'react'; +import React, { useEffect } from 'react'; import { HistoryRouter } from './components/HistoryRouter'; import { ApiProvider } from './hooks/useApi'; -import AppRoutes from './routes/index'; +import { AppRoutes, ExperimentalAppRoutes } from './routes'; const App = ({ history }: { history: History }) => { + const layoutMode = localStorage.getItem('layout'); + + useEffect(() => { + Promise.all([ + // Initialize the UI components after first render + import('./scripts/libraryMenu'), + import('./scripts/autoBackdrops') + ]); + }, []); + return ( - +
+
+ +
+
+
+
+ +
+
+ {layoutMode === 'experimental' ? : } +
+ +
); diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js index bbfb35ebca..91b994c2ef 100644 --- a/src/components/accessSchedule/accessSchedule.js +++ b/src/components/accessSchedule/accessSchedule.js @@ -1,6 +1,3 @@ - -/* eslint-disable indent */ - /** * Module for controlling user parental control from. * @module components/accessSchedule/accessSchedule @@ -14,84 +11,82 @@ import '../../elements/emby-button/paper-icon-button-light'; import '../formdialog.scss'; import template from './accessSchedule.template.html'; - function getDisplayTime(hours) { - let minutes = 0; - const pct = hours % 1; +function getDisplayTime(hours) { + let minutes = 0; + const pct = hours % 1; - if (pct) { - minutes = parseInt(60 * pct, 10); - } - - return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); + if (pct) { + minutes = parseInt(60 * pct, 10); } - function populateHours(context) { - let html = ''; + return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); +} - for (let i = 0; i < 24; i += 0.5) { - html += ``; - } +function populateHours(context) { + let html = ''; - html += ``; - context.querySelector('#selectStart').innerHTML = html; - context.querySelector('#selectEnd').innerHTML = html; + for (let i = 0; i < 24; i += 0.5) { + html += ``; } - function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) { - context.querySelector('#selectDay').value = DayOfWeek || 'Sunday'; - context.querySelector('#selectStart').value = StartHour || 0; - context.querySelector('#selectEnd').value = EndHour || 0; + html += ``; + context.querySelector('#selectStart').innerHTML = html; + context.querySelector('#selectEnd').innerHTML = html; +} + +function loadSchedule(context, { DayOfWeek, StartHour, EndHour }) { + context.querySelector('#selectDay').value = DayOfWeek || 'Sunday'; + context.querySelector('#selectStart').value = StartHour || 0; + context.querySelector('#selectEnd').value = EndHour || 0; +} + +function submitSchedule(context, options) { + const updatedSchedule = { + DayOfWeek: context.querySelector('#selectDay').value, + StartHour: context.querySelector('#selectStart').value, + EndHour: context.querySelector('#selectEnd').value + }; + + if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { + alert(globalize.translate('ErrorStartHourGreaterThanEnd')); + return; } - function submitSchedule(context, options) { - const updatedSchedule = { - DayOfWeek: context.querySelector('#selectDay').value, - StartHour: context.querySelector('#selectStart').value, - EndHour: context.querySelector('#selectEnd').value - }; + context.submitted = true; + options.schedule = Object.assign(options.schedule, updatedSchedule); + dialogHelper.close(context); +} - if (parseFloat(updatedSchedule.StartHour) >= parseFloat(updatedSchedule.EndHour)) { - alert(globalize.translate('ErrorStartHourGreaterThanEnd')); - return; - } - - context.submitted = true; - options.schedule = Object.assign(options.schedule, updatedSchedule); - dialogHelper.close(context); - } - - export function show(options) { - return new Promise((resolve, reject) => { - const dlg = dialogHelper.createDialog({ - removeOnClose: true, - size: 'small' - }); - dlg.classList.add('formDialog'); - let html = ''; - html += globalize.translateHtml(template); - dlg.innerHTML = html; - populateHours(dlg); - loadSchedule(dlg, options.schedule); - dialogHelper.open(dlg); - dlg.addEventListener('close', () => { - if (dlg.submitted) { - resolve(options.schedule); - } else { - reject(); - } - }); - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - dlg.querySelector('form').addEventListener('submit', event => { - submitSchedule(dlg, options); - event.preventDefault(); - return false; - }); +export function show(options) { + return new Promise((resolve, reject) => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + size: 'small' }); - } - -/* eslint-enable indent */ + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateHtml(template); + dlg.innerHTML = html; + populateHours(dlg); + loadSchedule(dlg, options.schedule); + dialogHelper.open(dlg); + dlg.addEventListener('close', () => { + if (dlg.submitted) { + resolve(options.schedule); + } else { + reject(); + } + }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + dlg.querySelector('form').addEventListener('submit', event => { + submitSchedule(dlg, options); + event.preventDefault(); + return false; + }); + }); +} export default { show: show diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 54de02b97c..fc9a5a9fe6 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -11,130 +11,128 @@ import alert from './alert'; import { getLocale } from '../utils/dateFnsLocale.ts'; import { toBoolean } from '../utils/string.ts'; -/* eslint-disable indent */ +function getEntryHtml(entry, apiClient) { + let html = ''; + html += '
'; + let color = '#00a4dc'; + let icon = 'notifications'; - function getEntryHtml(entry, apiClient) { - let html = ''; - html += '
'; - let color = '#00a4dc'; - let icon = 'notifications'; + if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') { + color = '#cc0000'; + icon = 'notification_important'; + } - if (entry.Severity == 'Error' || entry.Severity == 'Fatal' || entry.Severity == 'Warn') { - color = '#cc0000'; - icon = 'notification_important'; - } + if (entry.UserId && entry.UserPrimaryImageTag) { + html += '"; + } else { + html += ''; + } - if (entry.UserId && entry.UserPrimaryImageTag) { - html += '"; - } else { - html += ''; - } + html += '
'; + html += '
'; + html += escapeHtml(entry.Name); + html += '
'; + html += '
'; + html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() }); + html += '
'; + html += '
'; + html += escapeHtml(entry.ShortOverview || ''); + html += '
'; + html += '
'; - html += '
'; - html += '
'; - html += escapeHtml(entry.Name); - html += '
'; - html += '
'; - html += formatRelative(Date.parse(entry.Date), Date.now(), { locale: getLocale() }); - html += '
'; - html += '
'; - html += escapeHtml(entry.ShortOverview || ''); - html += '
'; - html += '
'; - - if (entry.Overview) { - html += ``; - } - - html += '
'; - - return html; } - function renderList(elem, apiClient, result) { - elem.innerHTML = result.Items.map(function (i) { - return getEntryHtml(i, apiClient); - }).join(''); + html += '
'; + + return html; +} + +function renderList(elem, apiClient, result) { + elem.innerHTML = result.Items.map(function (i) { + return getEntryHtml(i, apiClient); + }).join(''); +} + +function reloadData(instance, elem, apiClient, startIndex, limit) { + if (startIndex == null) { + startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); } - function reloadData(instance, elem, apiClient, startIndex, limit) { - if (startIndex == null) { - startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); - } + limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); + const minDate = new Date(); + const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); - limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); - const minDate = new Date(); - const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); - - // TODO: Use date-fns - if (hasUserId) { - minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back - } else { - minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back - } - - ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', { - startIndex: startIndex, - limit: limit, - minDate: minDate.toISOString(), - hasUserId: hasUserId - })).then(function (result) { - elem.setAttribute('data-activitystartindex', startIndex); - elem.setAttribute('data-activitylimit', limit); - if (!startIndex) { - const activityContainer = dom.parentWithClass(elem, 'activityContainer'); - - if (activityContainer) { - if (result.Items.length) { - activityContainer.classList.remove('hide'); - } else { - activityContainer.classList.add('hide'); - } - } - } - - instance.items = result.Items; - renderList(elem, apiClient, result); - }); + // TODO: Use date-fns + if (hasUserId) { + minDate.setTime(minDate.getTime() - 24 * 60 * 60 * 1000); // one day back + } else { + minDate.setTime(minDate.getTime() - 7 * 24 * 60 * 60 * 1000); // one week back } - function onActivityLogUpdate(e, apiClient) { - const options = this.options; + ApiClient.getJSON(ApiClient.getUrl('System/ActivityLog/Entries', { + startIndex: startIndex, + limit: limit, + minDate: minDate.toISOString(), + hasUserId: hasUserId + })).then(function (result) { + elem.setAttribute('data-activitystartindex', startIndex); + elem.setAttribute('data-activitylimit', limit); + if (!startIndex) { + const activityContainer = dom.parentWithClass(elem, 'activityContainer'); - if (options && options.serverId === apiClient.serverId()) { - reloadData(this, options.element, apiClient); - } - } - - function onListClick(e) { - const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); - - if (btnEntryInfo) { - const id = btnEntryInfo.getAttribute('data-id'); - const items = this.items; - - if (items) { - const item = items.filter(function (i) { - return i.Id.toString() === id; - })[0]; - - if (item) { - showItemOverview(item); + if (activityContainer) { + if (result.Items.length) { + activityContainer.classList.remove('hide'); + } else { + activityContainer.classList.add('hide'); } } } - } - function showItemOverview(item) { - alert({ - text: item.Overview - }); + instance.items = result.Items; + renderList(elem, apiClient, result); + }); +} + +function onActivityLogUpdate(e, apiClient) { + const options = this.options; + + if (options && options.serverId === apiClient.serverId()) { + reloadData(this, options.element, apiClient); } +} + +function onListClick(e) { + const btnEntryInfo = dom.parentWithClass(e.target, 'btnEntryInfo'); + + if (btnEntryInfo) { + const id = btnEntryInfo.getAttribute('data-id'); + const items = this.items; + + if (items) { + const item = items.filter(function (i) { + return i.Id.toString() === id; + })[0]; + + if (item) { + showItemOverview(item); + } + } + } +} + +function showItemOverview(item) { + alert({ + text: item.Overview + }); +} class ActivityLog { constructor(options) { @@ -169,5 +167,3 @@ class ActivityLog { } export default ActivityLog; - -/* eslint-enable indent */ diff --git a/src/components/alert.js b/src/components/alert.js index f8c4209f6d..bd6ff7187c 100644 --- a/src/components/alert.js +++ b/src/components/alert.js @@ -3,45 +3,41 @@ import browser from '../scripts/browser'; import dialog from './dialog/dialog'; import globalize from '../scripts/globalize'; -/* eslint-disable indent */ - - function useNativeAlert() { - // webOS seems to block modals - // Tizen 2.x seems to block modals - return !browser.web0s +function useNativeAlert() { + // webOS seems to block modals + // Tizen 2.x seems to block modals + return !browser.web0s && !(browser.tizenVersion && browser.tizenVersion < 3) && browser.tv && window.alert; +} + +export default async function (text, title) { + let options; + if (typeof text === 'string') { + options = { + title: title, + text: text + }; + } else { + options = text; } - export default async function (text, title) { - let options; - if (typeof text === 'string') { - options = { - title: title, - text: text - }; - } else { - options = text; - } + await appRouter.ready(); - await appRouter.ready(); + if (useNativeAlert()) { + alert((options.text || '').replaceAll('
', '\n')); + return Promise.resolve(); + } else { + const items = []; - if (useNativeAlert()) { - alert((options.text || '').replaceAll('
', '\n')); - return Promise.resolve(); - } else { - const items = []; + items.push({ + name: globalize.translate('ButtonGotIt'), + id: 'ok', + type: 'submit' + }); - items.push({ - name: globalize.translate('ButtonGotIt'), - id: 'ok', - type: 'submit' - }); - - options.buttons = items; - return dialog.show(options); - } + options.buttons = items; + return dialog.show(options); } - -/* eslint-enable indent */ +} diff --git a/src/components/alphaPicker/alphaPicker.js b/src/components/alphaPicker/alphaPicker.js index ac484bbf1c..dd97c59074 100644 --- a/src/components/alphaPicker/alphaPicker.js +++ b/src/components/alphaPicker/alphaPicker.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module alphaPicker. * @module components/alphaPicker/alphaPicker @@ -13,312 +11,311 @@ import './style.scss'; import '../../elements/emby-button/paper-icon-button-light'; import 'material-design-icons-iconfont'; - const selectedButtonClass = 'alphaPickerButton-selected'; +const selectedButtonClass = 'alphaPickerButton-selected'; - function focus() { - const scope = this; - const selected = scope.querySelector(`.${selectedButtonClass}`); +function focus() { + const scope = this; + const selected = scope.querySelector(`.${selectedButtonClass}`); - if (selected) { - focusManager.focus(selected); - } else { - focusManager.autoFocus(scope, true); - } + if (selected) { + focusManager.focus(selected); + } else { + focusManager.autoFocus(scope, true); + } +} + +function getAlphaPickerButtonClassName(vertical) { + let alphaPickerButtonClassName = 'alphaPickerButton'; + + if (layoutManager.tv) { + alphaPickerButtonClassName += ' alphaPickerButton-tv'; } - function getAlphaPickerButtonClassName(vertical) { - let alphaPickerButtonClassName = 'alphaPickerButton'; - - if (layoutManager.tv) { - alphaPickerButtonClassName += ' alphaPickerButton-tv'; - } - - if (vertical) { - alphaPickerButtonClassName += ' alphaPickerButton-vertical'; - } - - return alphaPickerButtonClassName; + if (vertical) { + alphaPickerButtonClassName += ' alphaPickerButton-vertical'; } - function getLetterButton(l, vertical) { - return ``; + return alphaPickerButtonClassName; +} + +function getLetterButton(l, vertical) { + return ``; +} + +function mapLetters(letters, vertical) { + return letters.map(l => { + return getLetterButton(l, vertical); + }); +} + +function render(element, options) { + element.classList.add('alphaPicker'); + + if (layoutManager.tv) { + element.classList.add('alphaPicker-tv'); } - function mapLetters(letters, vertical) { - return letters.map(l => { - return getLetterButton(l, vertical); - }); + const vertical = element.classList.contains('alphaPicker-vertical'); + + if (!vertical) { + element.classList.add('focuscontainer-x'); } - function render(element, options) { - element.classList.add('alphaPicker'); + let html = ''; + let letters; - if (layoutManager.tv) { - element.classList.add('alphaPicker-tv'); - } + const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical); - const vertical = element.classList.contains('alphaPicker-vertical'); + let rowClassName = 'alphaPickerRow'; - if (!vertical) { - element.classList.add('focuscontainer-x'); - } + if (vertical) { + rowClassName += ' alphaPickerRow-vertical'; + } - let html = ''; - let letters; - - const alphaPickerButtonClassName = getAlphaPickerButtonClassName(vertical); - - let rowClassName = 'alphaPickerRow'; - - if (vertical) { - rowClassName += ' alphaPickerRow-vertical'; - } - - html += `
`; - if (options.mode === 'keyboard') { - html += ``; - } else { - letters = ['#']; - html += mapLetters(letters, vertical).join(''); - } - - letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; + html += `
`; + if (options.mode === 'keyboard') { + html += ``; + } else { + letters = ['#']; html += mapLetters(letters, vertical).join(''); - - if (options.mode === 'keyboard') { - html += ``; - html += '
'; - - letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; - html += `
`; - html += '
'; - html += mapLetters(letters, vertical).join(''); - html += '
'; - } else { - html += '
'; - } - - element.innerHTML = html; - - element.classList.add('focusable'); - element.focus = focus; } - export class AlphaPicker { - constructor(options) { - const self = this; + letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; + html += mapLetters(letters, vertical).join(''); - this.options = options; + if (options.mode === 'keyboard') { + html += ``; + html += '
'; - const element = options.element; - const itemsContainer = options.itemsContainer; - const itemClass = options.itemClass; + letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; + html += `
`; + html += '
'; + html += mapLetters(letters, vertical).join(''); + html += '
'; + } else { + html += '
'; + } - let itemFocusValue; - let itemFocusTimeout; + element.innerHTML = html; - function onItemFocusTimeout() { - itemFocusTimeout = null; - self.value(itemFocusValue); - } + element.classList.add('focusable'); + element.focus = focus; +} - let alphaFocusedElement; - let alphaFocusTimeout; +export class AlphaPicker { + constructor(options) { + const self = this; - function onAlphaFocusTimeout() { - alphaFocusTimeout = null; + this.options = options; - if (document.activeElement === alphaFocusedElement) { - const value = alphaFocusedElement.getAttribute('data-value'); - self.value(value, true); - } - } + const element = options.element; + const itemsContainer = options.itemsContainer; + const itemClass = options.itemClass; - function onAlphaPickerInKeyboardModeClick(e) { - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + let itemFocusValue; + let itemFocusTimeout; - if (alphaPickerButton) { - const value = alphaPickerButton.getAttribute('data-value'); - - element.dispatchEvent(new CustomEvent('alphavalueclicked', { - cancelable: false, - detail: { - value - } - })); - } - } - - function onAlphaPickerClick(e) { - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); - - if (alphaPickerButton) { - const value = alphaPickerButton.getAttribute('data-value'); - if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) { - this.value(null, true); - } else { - this.value(value, true); - } - } - } - - function onAlphaPickerFocusIn(e) { - if (alphaFocusTimeout) { - clearTimeout(alphaFocusTimeout); - alphaFocusTimeout = null; - } - - const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); - - if (alphaPickerButton) { - alphaFocusedElement = alphaPickerButton; - alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600); - } - } - - function onItemsFocusIn(e) { - const item = dom.parentWithClass(e.target, itemClass); - - if (item) { - const prefix = item.getAttribute('data-prefix'); - if (prefix && prefix.length) { - itemFocusValue = prefix[0]; - if (itemFocusTimeout) { - clearTimeout(itemFocusTimeout); - } - itemFocusTimeout = setTimeout(onItemFocusTimeout, 100); - } - } - } - - this.enabled = function (enabled) { - if (enabled) { - if (itemsContainer) { - itemsContainer.addEventListener('focus', onItemsFocusIn, true); - } - - if (options.mode === 'keyboard') { - element.addEventListener('click', onAlphaPickerInKeyboardModeClick); - } - - if (options.valueChangeEvent !== 'click') { - element.addEventListener('focus', onAlphaPickerFocusIn, true); - } else { - element.addEventListener('click', onAlphaPickerClick.bind(this)); - } - } else { - if (itemsContainer) { - itemsContainer.removeEventListener('focus', onItemsFocusIn, true); - } - - element.removeEventListener('click', onAlphaPickerInKeyboardModeClick); - element.removeEventListener('focus', onAlphaPickerFocusIn, true); - element.removeEventListener('click', onAlphaPickerClick.bind(this)); - } - }; - - render(element, options); - - this.enabled(true); - this.visible(true); + function onItemFocusTimeout() { + itemFocusTimeout = null; + self.value(itemFocusValue); } - value(value, applyValue) { - const element = this.options.element; - let btn; - let selected; + let alphaFocusedElement; + let alphaFocusTimeout; - if (value !== undefined) { - if (value != null) { - value = value.toUpperCase(); - this._currentValue = value; + function onAlphaFocusTimeout() { + alphaFocusTimeout = null; - if (this.options.mode !== 'keyboard') { - selected = element.querySelector(`.${selectedButtonClass}`); - - try { - btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`); - } catch (err) { - console.error('error in querySelector:', err); - } - - if (btn && btn !== selected) { - btn.classList.add(selectedButtonClass); - } - if (selected && selected !== btn) { - selected.classList.remove(selectedButtonClass); - } - } - } else { - this._currentValue = value; - - selected = element.querySelector(`.${selectedButtonClass}`); - if (selected) { - selected.classList.remove(selectedButtonClass); - } - } + if (document.activeElement === alphaFocusedElement) { + const value = alphaFocusedElement.getAttribute('data-value'); + self.value(value, true); } + } - if (applyValue) { - element.dispatchEvent(new CustomEvent('alphavaluechanged', { + function onAlphaPickerInKeyboardModeClick(e) { + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + const value = alphaPickerButton.getAttribute('data-value'); + + element.dispatchEvent(new CustomEvent('alphavalueclicked', { cancelable: false, detail: { value } })); } - - return this._currentValue; } - on(name, fn) { - const element = this.options.element; - element.addEventListener(name, fn); + function onAlphaPickerClick(e) { + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + const value = alphaPickerButton.getAttribute('data-value'); + if ((this._currentValue || '').toUpperCase() === value.toUpperCase()) { + this.value(null, true); + } else { + this.value(value, true); + } + } } - off(name, fn) { - const element = this.options.element; - element.removeEventListener(name, fn); + function onAlphaPickerFocusIn(e) { + if (alphaFocusTimeout) { + clearTimeout(alphaFocusTimeout); + alphaFocusTimeout = null; + } + + const alphaPickerButton = dom.parentWithClass(e.target, 'alphaPickerButton'); + + if (alphaPickerButton) { + alphaFocusedElement = alphaPickerButton; + alphaFocusTimeout = setTimeout(onAlphaFocusTimeout, 600); + } } - updateControls(query) { - if (query.NameLessThan) { - this.value('#'); + function onItemsFocusIn(e) { + const item = dom.parentWithClass(e.target, itemClass); + + if (item) { + const prefix = item.getAttribute('data-prefix'); + if (prefix && prefix.length) { + itemFocusValue = prefix[0]; + if (itemFocusTimeout) { + clearTimeout(itemFocusTimeout); + } + itemFocusTimeout = setTimeout(onItemFocusTimeout, 100); + } + } + } + + this.enabled = function (enabled) { + if (enabled) { + if (itemsContainer) { + itemsContainer.addEventListener('focus', onItemsFocusIn, true); + } + + if (options.mode === 'keyboard') { + element.addEventListener('click', onAlphaPickerInKeyboardModeClick); + } + + if (options.valueChangeEvent !== 'click') { + element.addEventListener('focus', onAlphaPickerFocusIn, true); + } else { + element.addEventListener('click', onAlphaPickerClick.bind(this)); + } } else { - this.value(query.NameStartsWith); + if (itemsContainer) { + itemsContainer.removeEventListener('focus', onItemsFocusIn, true); + } + + element.removeEventListener('click', onAlphaPickerInKeyboardModeClick); + element.removeEventListener('focus', onAlphaPickerFocusIn, true); + element.removeEventListener('click', onAlphaPickerClick.bind(this)); } + }; - this.visible(query.SortBy.indexOf('SortName') !== -1); - } + render(element, options); - visible(visible) { - const element = this.options.element; - element.style.visibility = visible ? 'visible' : 'hidden'; - } - - values() { - const element = this.options.element; - const elems = element.querySelectorAll('.alphaPickerButton'); - const values = []; - for (let i = 0, length = elems.length; i < length; i++) { - values.push(elems[i].getAttribute('data-value')); - } - - return values; - } - - focus() { - const element = this.options.element; - focusManager.autoFocus(element, true); - } - - destroy() { - const element = this.options.element; - this.enabled(false); - element.classList.remove('focuscontainer-x'); - this.options = null; - } + this.enabled(true); + this.visible(true); } -/* eslint-enable indent */ + value(value, applyValue) { + const element = this.options.element; + let btn; + let selected; + + if (value !== undefined) { + if (value != null) { + value = value.toUpperCase(); + this._currentValue = value; + + if (this.options.mode !== 'keyboard') { + selected = element.querySelector(`.${selectedButtonClass}`); + + try { + btn = element.querySelector(`.alphaPickerButton[data-value='${value}']`); + } catch (err) { + console.error('error in querySelector:', err); + } + + if (btn && btn !== selected) { + btn.classList.add(selectedButtonClass); + } + if (selected && selected !== btn) { + selected.classList.remove(selectedButtonClass); + } + } + } else { + this._currentValue = value; + + selected = element.querySelector(`.${selectedButtonClass}`); + if (selected) { + selected.classList.remove(selectedButtonClass); + } + } + } + + if (applyValue) { + element.dispatchEvent(new CustomEvent('alphavaluechanged', { + cancelable: false, + detail: { + value + } + })); + } + + return this._currentValue; + } + + on(name, fn) { + const element = this.options.element; + element.addEventListener(name, fn); + } + + off(name, fn) { + const element = this.options.element; + element.removeEventListener(name, fn); + } + + updateControls(query) { + if (query.NameLessThan) { + this.value('#'); + } else { + this.value(query.NameStartsWith); + } + + this.visible(query.SortBy.indexOf('SortName') !== -1); + } + + visible(visible) { + const element = this.options.element; + element.style.visibility = visible ? 'visible' : 'hidden'; + } + + values() { + const element = this.options.element; + const elems = element.querySelectorAll('.alphaPickerButton'); + const values = []; + for (let i = 0, length = elems.length; i < length; i++) { + values.push(elems[i].getAttribute('data-value')); + } + + return values; + } + + focus() { + const element = this.options.element; + focusManager.autoFocus(element, true); + } + + destroy() { + const element = this.options.element; + this.enabled(false); + element.classList.remove('focuscontainer-x'); + this.options = null; + } +} + export default AlphaPicker; diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js index 59b6bca809..347b6dd4f2 100644 --- a/src/components/autoFocuser.js +++ b/src/components/autoFocuser.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module for performing auto-focus. * @module components/autoFocuser @@ -8,93 +6,91 @@ import focusManager from './focusManager'; import layoutManager from './layoutManager'; - /** +/** * Previously selected element. */ - let activeElement; +let activeElement; - /** +/** * Returns _true_ if AutoFocuser is enabled. */ - export function isEnabled() { - return layoutManager.tv; - } +export function isEnabled() { + return layoutManager.tv; +} - /** +/** * Start AutoFocuser. */ - export function enable() { - if (!isEnabled()) { - return; - } - - window.addEventListener('focusin', function (e) { - activeElement = e.target; - }); - - console.debug('AutoFocuser enabled'); +export function enable() { + if (!isEnabled()) { + return; } - /** + window.addEventListener('focusin', function (e) { + activeElement = e.target; + }); + + console.debug('AutoFocuser enabled'); +} + +/** * Set focus on a suitable element, taking into account the previously selected. * @param {HTMLElement} [container] - Element to limit scope. * @returns {HTMLElement} Focused element. */ - export function autoFocus(container) { - if (!isEnabled()) { - return null; - } - - container = container || document.body; - - let candidates = []; - - if (activeElement) { - // These elements are recreated - if (activeElement.classList.contains('btnPreviousPage')) { - candidates.push(container.querySelector('.btnPreviousPage')); - candidates.push(container.querySelector('.btnNextPage')); - } else if (activeElement.classList.contains('btnNextPage')) { - candidates.push(container.querySelector('.btnNextPage')); - candidates.push(container.querySelector('.btnPreviousPage')); - } else if (activeElement.classList.contains('btnSelectView')) { - candidates.push(container.querySelector('.btnSelectView')); - } - - candidates.push(activeElement); - } - - candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay'))); - - let focusedElement; - - candidates.every(function (element) { - if (focusManager.isCurrentlyFocusable(element)) { - focusManager.focus(element); - focusedElement = element; - return false; - } - - return true; - }); - - if (!focusedElement) { - // FIXME: Multiple itemsContainers - const itemsContainer = container.querySelector('.itemsContainer'); - - if (itemsContainer) { - focusedElement = focusManager.autoFocus(itemsContainer); - } - } - - if (!focusedElement) { - focusedElement = focusManager.autoFocus(container); - } - - return focusedElement; +export function autoFocus(container) { + if (!isEnabled()) { + return null; } -/* eslint-enable indent */ + container = container || document.body; + + let candidates = []; + + if (activeElement) { + // These elements are recreated + if (activeElement.classList.contains('btnPreviousPage')) { + candidates.push(container.querySelector('.btnPreviousPage')); + candidates.push(container.querySelector('.btnNextPage')); + } else if (activeElement.classList.contains('btnNextPage')) { + candidates.push(container.querySelector('.btnNextPage')); + candidates.push(container.querySelector('.btnPreviousPage')); + } else if (activeElement.classList.contains('btnSelectView')) { + candidates.push(container.querySelector('.btnSelectView')); + } + + candidates.push(activeElement); + } + + candidates = candidates.concat(Array.from(container.querySelectorAll('.btnPlay'))); + + let focusedElement; + + candidates.every(function (element) { + if (focusManager.isCurrentlyFocusable(element)) { + focusManager.focus(element); + focusedElement = element; + return false; + } + + return true; + }); + + if (!focusedElement) { + // FIXME: Multiple itemsContainers + const itemsContainer = container.querySelector('.itemsContainer'); + + if (itemsContainer) { + focusedElement = focusManager.autoFocus(itemsContainer); + } + } + + if (!focusedElement) { + focusedElement = focusManager.autoFocus(container); + } + + return focusedElement; +} export default { isEnabled: isEnabled, diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index 027f727812..4cd481827f 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -7,282 +7,278 @@ import ServerConnections from '../ServerConnections'; import './backdrop.scss'; -/* eslint-disable indent */ +function enableAnimation() { + return !browser.slow; +} - function enableAnimation() { - return !browser.slow; - } - - function enableRotation() { - return !browser.tv +function enableRotation() { + return !browser.tv // Causes high cpu usage && !browser.firefox; - } +} - class Backdrop { - load(url, parent, existingBackdropImage) { - const img = new Image(); - const self = this; +class Backdrop { + load(url, parent, existingBackdropImage) { + const img = new Image(); + const self = this; - img.onload = () => { - if (self.isDestroyed) { - return; - } - - const backdropImage = document.createElement('div'); - backdropImage.classList.add('backdropImage'); - backdropImage.classList.add('displayingBackdropImage'); - backdropImage.style.backgroundImage = `url('${url}')`; - backdropImage.setAttribute('data-url', url); - - backdropImage.classList.add('backdropImageFadeIn'); - parent.appendChild(backdropImage); - - if (!enableAnimation()) { - if (existingBackdropImage && existingBackdropImage.parentNode) { - existingBackdropImage.parentNode.removeChild(existingBackdropImage); - } - internalBackdrop(true); - return; - } - - const onAnimationComplete = () => { - dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { - once: true - }); - if (backdropImage === self.currentAnimatingElement) { - self.currentAnimatingElement = null; - } - if (existingBackdropImage && existingBackdropImage.parentNode) { - existingBackdropImage.parentNode.removeChild(existingBackdropImage); - } - }; - - dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { - once: true - }); - - internalBackdrop(true); - }; - - img.src = url; - } - - cancelAnimation() { - const elem = this.currentAnimatingElement; - if (elem) { - elem.classList.remove('backdropImageFadeIn'); - this.currentAnimatingElement = null; - } - } - - destroy() { - this.isDestroyed = true; - this.cancelAnimation(); - } - } - - let backdropContainer; - function getBackdropContainer() { - if (!backdropContainer) { - backdropContainer = document.querySelector('.backdropContainer'); - } - - if (!backdropContainer) { - backdropContainer = document.createElement('div'); - backdropContainer.classList.add('backdropContainer'); - document.body.insertBefore(backdropContainer, document.body.firstChild); - } - - return backdropContainer; - } - - export function clearBackdrop(clearAll) { - clearRotation(); - - if (currentLoadingBackdrop) { - currentLoadingBackdrop.destroy(); - currentLoadingBackdrop = null; - } - - const elem = getBackdropContainer(); - elem.innerHTML = ''; - - if (clearAll) { - hasExternalBackdrop = false; - } - - internalBackdrop(false); - } - - let backgroundContainer; - function getBackgroundContainer() { - if (!backgroundContainer) { - backgroundContainer = document.querySelector('.backgroundContainer'); - } - return backgroundContainer; - } - - function setBackgroundContainerBackgroundEnabled() { - if (hasInternalBackdrop || hasExternalBackdrop) { - getBackgroundContainer().classList.add('withBackdrop'); - } else { - getBackgroundContainer().classList.remove('withBackdrop'); - } - } - - let hasInternalBackdrop; - function internalBackdrop(isEnabled) { - hasInternalBackdrop = isEnabled; - setBackgroundContainerBackgroundEnabled(); - } - - let hasExternalBackdrop; - export function externalBackdrop(isEnabled) { - hasExternalBackdrop = isEnabled; - setBackgroundContainerBackgroundEnabled(); - } - - let currentLoadingBackdrop; - function setBackdropImage(url) { - if (currentLoadingBackdrop) { - currentLoadingBackdrop.destroy(); - currentLoadingBackdrop = null; - } - - const elem = getBackdropContainer(); - const existingBackdropImage = elem.querySelector('.displayingBackdropImage'); - - if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) { - if (existingBackdropImage.getAttribute('data-url') === url) { + img.onload = () => { + if (self.isDestroyed) { return; } - existingBackdropImage.classList.remove('displayingBackdropImage'); - } - const instance = new Backdrop(); - instance.load(url, elem, existingBackdropImage); - currentLoadingBackdrop = instance; - } + const backdropImage = document.createElement('div'); + backdropImage.classList.add('backdropImage'); + backdropImage.classList.add('displayingBackdropImage'); + backdropImage.style.backgroundImage = `url('${url}')`; + backdropImage.setAttribute('data-url', url); - function getItemImageUrls(item, imageOptions) { - imageOptions = imageOptions || {}; + backdropImage.classList.add('backdropImageFadeIn'); + parent.appendChild(backdropImage); - const apiClient = ServerConnections.getApiClient(item.ServerId); - if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { - return item.BackdropImageTags.map((imgTag, index) => { - return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { - type: 'Backdrop', - tag: imgTag, - maxWidth: dom.getScreenWidth(), - index: index - })); + if (!enableAnimation()) { + if (existingBackdropImage && existingBackdropImage.parentNode) { + existingBackdropImage.parentNode.removeChild(existingBackdropImage); + } + internalBackdrop(true); + return; + } + + const onAnimationComplete = () => { + dom.removeEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { + once: true + }); + if (backdropImage === self.currentAnimatingElement) { + self.currentAnimatingElement = null; + } + if (existingBackdropImage && existingBackdropImage.parentNode) { + existingBackdropImage.parentNode.removeChild(existingBackdropImage); + } + }; + + dom.addEventListener(backdropImage, dom.whichAnimationEvent(), onAnimationComplete, { + once: true }); - } - if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - return item.ParentBackdropImageTags.map((imgTag, index) => { - return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { - type: 'Backdrop', - tag: imgTag, - maxWidth: dom.getScreenWidth(), - index: index - })); - }); - } - - return []; - } - - function getImageUrls(items, imageOptions) { - const list = []; - const onImg = img => { - list.push(img); + internalBackdrop(true); }; - for (let i = 0, length = items.length; i < length; i++) { - const itemImages = getItemImageUrls(items[i], imageOptions); - itemImages.forEach(onImg); - } - - return list; + img.src = url; } - function enabled() { - return userSettings.enableBackdrops(); - } - - let rotationInterval; - let currentRotatingImages = []; - let currentRotationIndex = -1; - export function setBackdrops(items, imageOptions, enableImageRotation) { - if (enabled()) { - const images = getImageUrls(items, imageOptions); - - if (images.length) { - startRotation(images, enableImageRotation); - } else { - clearBackdrop(); - } + cancelAnimation() { + const elem = this.currentAnimatingElement; + if (elem) { + elem.classList.remove('backdropImageFadeIn'); + this.currentAnimatingElement = null; } } - function startRotation(images, enableImageRotation) { - if (isEqual(images, currentRotatingImages)) { + destroy() { + this.isDestroyed = true; + this.cancelAnimation(); + } +} + +let backdropContainer; +function getBackdropContainer() { + if (!backdropContainer) { + backdropContainer = document.querySelector('.backdropContainer'); + } + + if (!backdropContainer) { + backdropContainer = document.createElement('div'); + backdropContainer.classList.add('backdropContainer'); + document.body.insertBefore(backdropContainer, document.body.firstChild); + } + + return backdropContainer; +} + +export function clearBackdrop(clearAll) { + clearRotation(); + + if (currentLoadingBackdrop) { + currentLoadingBackdrop.destroy(); + currentLoadingBackdrop = null; + } + + const elem = getBackdropContainer(); + elem.innerHTML = ''; + + if (clearAll) { + hasExternalBackdrop = false; + } + + internalBackdrop(false); +} + +let backgroundContainer; +function getBackgroundContainer() { + if (!backgroundContainer) { + backgroundContainer = document.querySelector('.backgroundContainer'); + } + return backgroundContainer; +} + +function setBackgroundContainerBackgroundEnabled() { + if (hasInternalBackdrop || hasExternalBackdrop) { + getBackgroundContainer().classList.add('withBackdrop'); + } else { + getBackgroundContainer().classList.remove('withBackdrop'); + } +} + +let hasInternalBackdrop; +function internalBackdrop(isEnabled) { + hasInternalBackdrop = isEnabled; + setBackgroundContainerBackgroundEnabled(); +} + +let hasExternalBackdrop; +export function externalBackdrop(isEnabled) { + hasExternalBackdrop = isEnabled; + setBackgroundContainerBackgroundEnabled(); +} + +let currentLoadingBackdrop; +function setBackdropImage(url) { + if (currentLoadingBackdrop) { + currentLoadingBackdrop.destroy(); + currentLoadingBackdrop = null; + } + + const elem = getBackdropContainer(); + const existingBackdropImage = elem.querySelector('.displayingBackdropImage'); + + if (existingBackdropImage && existingBackdropImage.getAttribute('data-url') === url) { + if (existingBackdropImage.getAttribute('data-url') === url) { return; } - - clearRotation(); - - currentRotatingImages = images; - currentRotationIndex = -1; - - if (images.length > 1 && enableImageRotation !== false && enableRotation()) { - rotationInterval = setInterval(onRotationInterval, 24000); - } - - onRotationInterval(); + existingBackdropImage.classList.remove('displayingBackdropImage'); } - function onRotationInterval() { - if (playbackManager.isPlayingLocally(['Video'])) { - return; - } + const instance = new Backdrop(); + instance.load(url, elem, existingBackdropImage); + currentLoadingBackdrop = instance; +} - let newIndex = currentRotationIndex + 1; - if (newIndex >= currentRotatingImages.length) { - newIndex = 0; - } +function getItemImageUrls(item, imageOptions) { + imageOptions = imageOptions || {}; - currentRotationIndex = newIndex; - setBackdropImage(currentRotatingImages[newIndex]); + const apiClient = ServerConnections.getApiClient(item.ServerId); + if (item.BackdropImageTags && item.BackdropImageTags.length > 0) { + return item.BackdropImageTags.map((imgTag, index) => { + return apiClient.getScaledImageUrl(item.BackdropItemId || item.Id, Object.assign(imageOptions, { + type: 'Backdrop', + tag: imgTag, + maxWidth: dom.getScreenWidth(), + index: index + })); + }); } - function clearRotation() { - const interval = rotationInterval; - if (interval) { - clearInterval(interval); - } - - rotationInterval = null; - currentRotatingImages = []; - currentRotationIndex = -1; + if (item.ParentBackdropItemId && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + return item.ParentBackdropImageTags.map((imgTag, index) => { + return apiClient.getScaledImageUrl(item.ParentBackdropItemId, Object.assign(imageOptions, { + type: 'Backdrop', + tag: imgTag, + maxWidth: dom.getScreenWidth(), + index: index + })); + }); } - export function setBackdrop(url, imageOptions) { - if (url && typeof url !== 'string') { - url = getImageUrls([url], imageOptions)[0]; - } + return []; +} - if (url) { - clearRotation(); - setBackdropImage(url); +function getImageUrls(items, imageOptions) { + const list = []; + const onImg = img => { + list.push(img); + }; + + for (let i = 0, length = items.length; i < length; i++) { + const itemImages = getItemImageUrls(items[i], imageOptions); + itemImages.forEach(onImg); + } + + return list; +} + +function enabled() { + return userSettings.enableBackdrops(); +} + +let rotationInterval; +let currentRotatingImages = []; +let currentRotationIndex = -1; +export function setBackdrops(items, imageOptions, enableImageRotation) { + if (enabled()) { + const images = getImageUrls(items, imageOptions); + + if (images.length) { + startRotation(images, enableImageRotation); } else { clearBackdrop(); } } +} -/* eslint-enable indent */ +function startRotation(images, enableImageRotation) { + if (isEqual(images, currentRotatingImages)) { + return; + } + + clearRotation(); + + currentRotatingImages = images; + currentRotationIndex = -1; + + if (images.length > 1 && enableImageRotation !== false && enableRotation()) { + rotationInterval = setInterval(onRotationInterval, 24000); + } + + onRotationInterval(); +} + +function onRotationInterval() { + if (playbackManager.isPlayingLocally(['Video'])) { + return; + } + + let newIndex = currentRotationIndex + 1; + if (newIndex >= currentRotatingImages.length) { + newIndex = 0; + } + + currentRotationIndex = newIndex; + setBackdropImage(currentRotatingImages[newIndex]); +} + +function clearRotation() { + const interval = rotationInterval; + if (interval) { + clearInterval(interval); + } + + rotationInterval = null; + currentRotatingImages = []; + currentRotationIndex = -1; +} + +export function setBackdrop(url, imageOptions) { + if (url && typeof url !== 'string') { + url = getImageUrls([url], imageOptions)[0]; + } + + if (url) { + clearRotation(); + setBackdropImage(url); + } else { + clearBackdrop(); + } +} /** * @enum TransparencyLevel diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 04c03c6605..d4f3aafa49 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -25,467 +24,467 @@ import '../guide/programs.scss'; import ServerConnections from '../ServerConnections'; import { appRouter } from '../appRouter'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - /** +/** * Generate the HTML markup for cards for a set of items. * @param items - The items used to generate cards. * @param options - The options of the cards. * @returns {string} The HTML markup for the cards. */ - export function getCardsHtml(items, options) { - if (arguments.length === 1) { - options = arguments[0]; - items = options.items; - } +export function getCardsHtml(items, options) { + if (arguments.length === 1) { + options = arguments[0]; + items = options.items; + } - return buildCardsHtmlInternal(items, options); - } + return buildCardsHtmlInternal(items, options); +} - /** +/** * Computes the number of posters per row. * @param {string} shape - Shape of the cards. * @param {number} screenWidth - Width of the screen. * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. * @returns {number} Number of cards per row for an itemsContainer. */ - function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { - switch (shape) { - case 'portrait': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 100 / 33.33333333; - case 'square': - if (layoutManager.tv) { - return 100 / 16.66666667; - } - if (screenWidth >= 2200) { - return 100 / 10; - } - if (screenWidth >= 1920) { - return 100 / 11.1111111111; - } - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.28571428571; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 800) { - return 5; - } - if (screenWidth >= 700) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'banner': - if (screenWidth >= 2200) { - return 100 / 25; - } - if (screenWidth >= 1200) { - return 100 / 33.33333333; - } - if (screenWidth >= 800) { - return 2; - } - return 1; - case 'backdrop': - if (layoutManager.tv) { - return 100 / 25; - } - if (screenWidth >= 2500) { - return 6; - } - if (screenWidth >= 1600) { - return 5; - } - if (screenWidth >= 1200) { - return 4; - } - if (screenWidth >= 770) { - return 3; - } - if (screenWidth >= 420) { - return 2; - } - return 1; - case 'smallBackdrop': - if (screenWidth >= 1600) { - return 100 / 12.5; - } - if (screenWidth >= 1400) { - return 100 / 14.2857142857; - } - if (screenWidth >= 1200) { - return 100 / 16.66666667; - } - if (screenWidth >= 1000) { - return 5; - } - if (screenWidth >= 800) { - return 4; - } - if (screenWidth >= 500) { - return 100 / 33.33333333; - } - return 2; - case 'overflowSmallBackdrop': - if (layoutManager.tv) { - return 100 / 18.9; - } - if (isOrientationLandscape) { - if (screenWidth >= 800) { - return 100 / 15.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 540) { - return 100 / 30; - } - return 100 / 72; - } - case 'overflowPortrait': - - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 400) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowSquare': - if (layoutManager.tv) { - return 100 / 15.5; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 11.6; - } - return 100 / 15.5; - } else { - if (screenWidth >= 1400) { - return 100 / 15; - } - if (screenWidth >= 1200) { - return 100 / 18; - } - if (screenWidth >= 760) { - return 100 / 23; - } - if (screenWidth >= 540) { - return 100 / 31.5; - } - return 100 / 42; - } - case 'overflowBackdrop': - if (layoutManager.tv) { - return 100 / 23.3; - } - if (isOrientationLandscape) { - if (screenWidth >= 1700) { - return 100 / 18.5; - } - return 100 / 23.3; - } else { - if (screenWidth >= 1800) { - return 100 / 23.5; - } - if (screenWidth >= 1400) { - return 100 / 30; - } - if (screenWidth >= 760) { - return 100 / 40; - } - if (screenWidth >= 640) { - return 100 / 56; - } - return 100 / 72; - } - default: - return 4; +function getPostersPerRow(shape, screenWidth, isOrientationLandscape) { + switch (shape) { + case 'portrait': + if (layoutManager.tv) { + return 100 / 16.66666667; } - } + if (screenWidth >= 2200) { + return 100 / 10; + } + if (screenWidth >= 1920) { + return 100 / 11.1111111111; + } + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.28571428571; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 800) { + return 5; + } + if (screenWidth >= 700) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 100 / 33.33333333; + case 'square': + if (layoutManager.tv) { + return 100 / 16.66666667; + } + if (screenWidth >= 2200) { + return 100 / 10; + } + if (screenWidth >= 1920) { + return 100 / 11.1111111111; + } + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.28571428571; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 800) { + return 5; + } + if (screenWidth >= 700) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 2; + case 'banner': + if (screenWidth >= 2200) { + return 100 / 25; + } + if (screenWidth >= 1200) { + return 100 / 33.33333333; + } + if (screenWidth >= 800) { + return 2; + } + return 1; + case 'backdrop': + if (layoutManager.tv) { + return 100 / 25; + } + if (screenWidth >= 2500) { + return 6; + } + if (screenWidth >= 1600) { + return 5; + } + if (screenWidth >= 1200) { + return 4; + } + if (screenWidth >= 770) { + return 3; + } + if (screenWidth >= 420) { + return 2; + } + return 1; + case 'smallBackdrop': + if (screenWidth >= 1600) { + return 100 / 12.5; + } + if (screenWidth >= 1400) { + return 100 / 14.2857142857; + } + if (screenWidth >= 1200) { + return 100 / 16.66666667; + } + if (screenWidth >= 1000) { + return 5; + } + if (screenWidth >= 800) { + return 4; + } + if (screenWidth >= 500) { + return 100 / 33.33333333; + } + return 2; + case 'overflowSmallBackdrop': + if (layoutManager.tv) { + return 100 / 18.9; + } + if (isOrientationLandscape) { + if (screenWidth >= 800) { + return 100 / 15.5; + } + return 100 / 23.3; + } else { + if (screenWidth >= 540) { + return 100 / 30; + } + return 100 / 72; + } + case 'overflowPortrait': - /** + if (layoutManager.tv) { + return 100 / 15.5; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 11.6; + } + return 100 / 15.5; + } else { + if (screenWidth >= 1400) { + return 100 / 15; + } + if (screenWidth >= 1200) { + return 100 / 18; + } + if (screenWidth >= 760) { + return 100 / 23; + } + if (screenWidth >= 400) { + return 100 / 31.5; + } + return 100 / 42; + } + case 'overflowSquare': + if (layoutManager.tv) { + return 100 / 15.5; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 11.6; + } + return 100 / 15.5; + } else { + if (screenWidth >= 1400) { + return 100 / 15; + } + if (screenWidth >= 1200) { + return 100 / 18; + } + if (screenWidth >= 760) { + return 100 / 23; + } + if (screenWidth >= 540) { + return 100 / 31.5; + } + return 100 / 42; + } + case 'overflowBackdrop': + if (layoutManager.tv) { + return 100 / 23.3; + } + if (isOrientationLandscape) { + if (screenWidth >= 1700) { + return 100 / 18.5; + } + return 100 / 23.3; + } else { + if (screenWidth >= 1800) { + return 100 / 23.5; + } + if (screenWidth >= 1400) { + return 100 / 30; + } + if (screenWidth >= 760) { + return 100 / 40; + } + if (screenWidth >= 640) { + return 100 / 56; + } + return 100 / 72; + } + default: + return 4; + } +} + +/** * Checks if the window is resizable. * @param {number} windowWidth - Width of the device's screen. * @returns {boolean} - Result of the check. */ - function isResizable(windowWidth) { - const screen = window.screen; - if (screen) { - const screenWidth = screen.availWidth; +function isResizable(windowWidth) { + const screen = window.screen; + if (screen) { + const screenWidth = screen.availWidth; - if ((screenWidth - windowWidth) > 20) { - return true; - } - } - - return false; + if ((screenWidth - windowWidth) > 20) { + return true; } + } - /** + return false; +} + +/** * Gets the width of a card's image according to the shape and amount of cards per row. * @param {string} shape - Shape of the card. * @param {number} screenWidth - Width of the screen. * @param {boolean} isOrientationLandscape - Flag for the orientation of the screen. * @returns {number} Width of the image for a card. */ - function getImageWidth(shape, screenWidth, isOrientationLandscape) { - const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); - return Math.round(screenWidth / imagesPerRow); - } +function getImageWidth(shape, screenWidth, isOrientationLandscape) { + const imagesPerRow = getPostersPerRow(shape, screenWidth, isOrientationLandscape); + return Math.round(screenWidth / imagesPerRow); +} - /** +/** * Normalizes the options for a card. * @param {Object} items - A set of items. * @param {Object} options - Options for handling the items. */ - function setCardData(items, options) { - options.shape = options.shape || 'auto'; +function setCardData(items, options) { + options.shape = options.shape || 'auto'; - const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); + const primaryImageAspectRatio = imageLoader.getPrimaryImageAspectRatio(items); - if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { - const requestedShape = options.shape; - options.shape = null; + if (['auto', 'autohome', 'autooverflow', 'autoVertical'].includes(options.shape)) { + const requestedShape = options.shape; + options.shape = null; - if (primaryImageAspectRatio) { - if (primaryImageAspectRatio >= 3) { - options.shape = 'banner'; - options.coverImage = true; - } else if (primaryImageAspectRatio >= 1.33) { - options.shape = requestedShape === 'autooverflow' ? 'overflowBackdrop' : 'backdrop'; - } else if (primaryImageAspectRatio > 0.71) { - options.shape = requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'; - } else { - options.shape = requestedShape === 'autooverflow' ? 'overflowPortrait' : 'portrait'; - } - } - - if (!options.shape) { - options.shape = options.defaultShape || (requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'); - } - } - - if (options.preferThumb === 'auto') { - options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop'; - } - - options.uiAspect = getDesiredAspect(options.shape); - options.primaryImageAspectRatio = primaryImageAspectRatio; - - if (!options.width && options.widths) { - options.width = options.widths[options.shape]; - } - - if (options.rows && typeof (options.rows) !== 'number') { - options.rows = options.rows[options.shape]; - } - - if (!options.width) { - let screenWidth = dom.getWindowSize().innerWidth; - const screenHeight = dom.getWindowSize().innerHeight; - - if (isResizable(screenWidth)) { - const roundScreenTo = 100; - screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; - } - - options.width = getImageWidth(options.shape, screenWidth, screenWidth > (screenHeight * 1.3)); + if (primaryImageAspectRatio) { + if (primaryImageAspectRatio >= 3) { + options.shape = 'banner'; + options.coverImage = true; + } else if (primaryImageAspectRatio >= 1.33) { + options.shape = requestedShape === 'autooverflow' ? 'overflowBackdrop' : 'backdrop'; + } else if (primaryImageAspectRatio > 0.71) { + options.shape = requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'; + } else { + options.shape = requestedShape === 'autooverflow' ? 'overflowPortrait' : 'portrait'; } } - /** + if (!options.shape) { + options.shape = options.defaultShape || (requestedShape === 'autooverflow' ? 'overflowSquare' : 'square'); + } + } + + if (options.preferThumb === 'auto') { + options.preferThumb = options.shape === 'backdrop' || options.shape === 'overflowBackdrop'; + } + + options.uiAspect = getDesiredAspect(options.shape); + options.primaryImageAspectRatio = primaryImageAspectRatio; + + if (!options.width && options.widths) { + options.width = options.widths[options.shape]; + } + + if (options.rows && typeof (options.rows) !== 'number') { + options.rows = options.rows[options.shape]; + } + + if (!options.width) { + let screenWidth = dom.getWindowSize().innerWidth; + const screenHeight = dom.getWindowSize().innerHeight; + + if (isResizable(screenWidth)) { + const roundScreenTo = 100; + screenWidth = Math.floor(screenWidth / roundScreenTo) * roundScreenTo; + } + + options.width = getImageWidth(options.shape, screenWidth, screenWidth > (screenHeight * 1.3)); + } +} + +/** * Generates the internal HTML markup for cards. * @param {Object} items - Items for which to generate the markup. * @param {Object} options - Options for generating the markup. * @returns {string} The internal HTML markup of the cards. */ - function buildCardsHtmlInternal(items, options) { - let isVertical = false; +function buildCardsHtmlInternal(items, options) { + let isVertical = false; - if (options.shape === 'autoVertical') { - isVertical = true; + if (options.shape === 'autoVertical') { + isVertical = true; + } + + setCardData(items, options); + + let html = ''; + let itemsInRow = 0; + + let currentIndexValue; + let hasOpenRow; + let hasOpenSection; + + const sectionTitleTagName = options.sectionTitleTagName || 'div'; + let apiClient; + let lastServerId; + + for (const [i, item] of items.entries()) { + const serverId = item.ServerId || options.serverId; + + if (serverId !== lastServerId) { + lastServerId = serverId; + apiClient = ServerConnections.getApiClient(lastServerId); + } + + if (options.indexBy) { + let newIndexValue = ''; + + if (options.indexBy === 'PremiereDate') { + if (item.PremiereDate) { + try { + newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), { weekday: 'long', month: 'long', day: 'numeric' }); + } catch (error) { + console.error('error parsing timestamp for premiere date', error); + } + } + } else if (options.indexBy === 'ProductionYear') { + newIndexValue = item.ProductionYear; + } else if (options.indexBy === 'CommunityRating') { + const roundedRatingDecimal = item.CommunityRating % 1 >= 0.5 ? 0.5 : 0; + newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + roundedRatingDecimal) + '+' : null; } - setCardData(items, options); - - let html = ''; - let itemsInRow = 0; - - let currentIndexValue; - let hasOpenRow; - let hasOpenSection; - - const sectionTitleTagName = options.sectionTitleTagName || 'div'; - let apiClient; - let lastServerId; - - for (const [i, item] of items.entries()) { - const serverId = item.ServerId || options.serverId; - - if (serverId !== lastServerId) { - lastServerId = serverId; - apiClient = ServerConnections.getApiClient(lastServerId); - } - - if (options.indexBy) { - let newIndexValue = ''; - - if (options.indexBy === 'PremiereDate') { - if (item.PremiereDate) { - try { - newIndexValue = datetime.toLocaleDateString(datetime.parseISO8601Date(item.PremiereDate), { weekday: 'long', month: 'long', day: 'numeric' }); - } catch (error) { - console.error('error parsing timestamp for premiere date', error); - } - } - } else if (options.indexBy === 'ProductionYear') { - newIndexValue = item.ProductionYear; - } else if (options.indexBy === 'CommunityRating') { - const roundedRatingDecimal = item.CommunityRating % 1 >= 0.5 ? 0.5 : 0; - newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + roundedRatingDecimal) + '+' : null; - } - - if (newIndexValue !== currentIndexValue) { - if (hasOpenRow) { - html += '
'; - hasOpenRow = false; - itemsInRow = 0; - } - - if (hasOpenSection) { - html += '
'; - - if (isVertical) { - html += '
'; - } - hasOpenSection = false; - } - - if (isVertical) { - html += '
'; - } else { - html += '
'; - } - html += '<' + sectionTitleTagName + ' class="sectionTitle">' + newIndexValue + ''; - if (isVertical) { - html += '
'; - } - currentIndexValue = newIndexValue; - hasOpenSection = true; - } - } - - if (options.rows && itemsInRow === 0) { - if (hasOpenRow) { - html += '
'; - hasOpenRow = false; - } - - html += '
'; - hasOpenRow = true; - } - - html += buildCard(i, item, apiClient, options); - - itemsInRow++; - - if (options.rows && itemsInRow >= options.rows) { + if (newIndexValue !== currentIndexValue) { + if (hasOpenRow) { html += '
'; hasOpenRow = false; itemsInRow = 0; } - } - if (hasOpenRow) { - html += '
'; - } + if (hasOpenSection) { + html += '
'; - if (hasOpenSection) { - html += '
'; + if (isVertical) { + html += ''; + } + hasOpenSection = false; + } if (isVertical) { - html += ''; + html += '
'; + } else { + html += '
'; } + html += '<' + sectionTitleTagName + ' class="sectionTitle">' + newIndexValue + ''; + if (isVertical) { + html += '
'; + } + currentIndexValue = newIndexValue; + hasOpenSection = true; } - - return html; } - /** + if (options.rows && itemsInRow === 0) { + if (hasOpenRow) { + html += '
'; + hasOpenRow = false; + } + + html += '
'; + hasOpenRow = true; + } + + html += buildCard(i, item, apiClient, options); + + itemsInRow++; + + if (options.rows && itemsInRow >= options.rows) { + html += '
'; + hasOpenRow = false; + itemsInRow = 0; + } + } + + if (hasOpenRow) { + html += '
'; + } + + if (hasOpenSection) { + html += '
'; + + if (isVertical) { + html += ''; + } + } + + return html; +} + +/** * Computes the aspect ratio for a card given its shape. * @param {string} shape - Shape for which to get the aspect ratio. * @returns {null|number} Ratio of the shape. */ - function getDesiredAspect(shape) { - if (shape) { - shape = shape.toLowerCase(); - if (shape.indexOf('portrait') !== -1) { - return (2 / 3); - } - if (shape.indexOf('backdrop') !== -1) { - return (16 / 9); - } - if (shape.indexOf('square') !== -1) { - return 1; - } - if (shape.indexOf('banner') !== -1) { - return (1000 / 185); - } - } - return null; +function getDesiredAspect(shape) { + if (shape) { + shape = shape.toLowerCase(); + if (shape.indexOf('portrait') !== -1) { + return (2 / 3); } + if (shape.indexOf('backdrop') !== -1) { + return (16 / 9); + } + if (shape.indexOf('square') !== -1) { + return 1; + } + if (shape.indexOf('banner') !== -1) { + return (1000 / 185); + } + } + return null; +} - /** +/** * @typedef {Object} CardImageUrl * @property {string} imgUrl - Image URL. * @property {string} blurhash - Image blurhash. @@ -493,178 +492,178 @@ import { appRouter } from '../appRouter'; * @property {boolean} coverImage - Use cover style. */ - /** Get the URL of the card's image. +/** Get the URL of the card's image. * @param {Object} item - Item for which to generate a card. * @param {Object} apiClient - API client object. * @param {Object} options - Options of the card. * @param {string} shape - Shape of the desired image. * @returns {CardImageUrl} Object representing the URL of the card's image. */ - function getCardImageUrl(item, apiClient, options, shape) { - item = item.ProgramInfo || item; +function getCardImageUrl(item, apiClient, options, shape) { + item = item.ProgramInfo || item; - const width = options.width; - let height = null; - const primaryImageAspectRatio = item.PrimaryImageAspectRatio; - let forceName = false; - let imgUrl = null; - let imgTag = null; - let coverImage = false; - const uiAspect = getDesiredAspect(shape); - let imgType = null; - let itemId = null; + const width = options.width; + let height = null; + const primaryImageAspectRatio = item.PrimaryImageAspectRatio; + let forceName = false; + let imgUrl = null; + let imgTag = null; + let coverImage = false; + const uiAspect = getDesiredAspect(shape); + let imgType = null; + let itemId = null; - /* eslint-disable sonarjs/no-duplicated-branches */ - if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { - imgType = 'Banner'; - imgTag = item.ImageTags.Banner; - } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { - imgType = 'Disc'; - imgTag = item.ImageTags.Disc; - } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { - imgType = 'Logo'; - imgTag = item.ImageTags.Logo; - } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { - imgType = 'Logo'; - imgTag = item.ParentLogoImageTag; - itemId = item.ParentLogoItemId; - } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.SeriesThumbImageTag; - itemId = item.SeriesId; - } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { - imgType = 'Thumb'; - imgTag = item.ParentThumbImageTag; - itemId = item.ParentThumbItemId; - } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { - imgType = 'Backdrop'; - imgTag = item.BackdropImageTags[0]; - forceName = true; - } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { - imgType = 'Backdrop'; - imgTag = item.ParentBackdropImageTags[0]; - itemId = item.ParentBackdropItemId; - } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { - imgType = 'Primary'; - imgTag = item.ImageTags.Primary; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + /* eslint-disable sonarjs/no-duplicated-branches */ + if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if ((options.preferBanner || shape === 'banner') && item.ImageTags && item.ImageTags.Banner) { + imgType = 'Banner'; + imgTag = item.ImageTags.Banner; + } else if (options.preferDisc && item.ImageTags && item.ImageTags.Disc) { + imgType = 'Disc'; + imgTag = item.ImageTags.Disc; + } else if (options.preferLogo && item.ImageTags && item.ImageTags.Logo) { + imgType = 'Logo'; + imgTag = item.ImageTags.Logo; + } else if (options.preferLogo && item.ParentLogoImageTag && item.ParentLogoItemId) { + imgType = 'Logo'; + imgTag = item.ParentLogoImageTag; + itemId = item.ParentLogoItemId; + } else if (options.preferThumb && item.SeriesThumbImageTag && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if (options.preferThumb && item.ParentThumbItemId && options.inheritThumb !== false && item.MediaType !== 'Photo') { + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if (options.preferThumb && item.BackdropImageTags && item.BackdropImageTags.length) { + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; + forceName = true; + } else if (options.preferThumb && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false && item.Type === 'Episode') { + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } else if (item.ImageTags && item.ImageTags.Primary && (item.Type !== 'Episode' || item.ChildCount !== 0)) { + imgType = 'Primary'; + imgTag = item.ImageTags.Primary; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - if (options.preferThumb && options.showTitle !== false) { - forceName = true; - } - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.SeriesPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.SeriesPrimaryImageTag; - itemId = item.SeriesId; - } else if (item.PrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.PrimaryImageTag; - itemId = item.PrimaryImageItemId; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - - if (options.preferThumb && options.showTitle !== false) { - forceName = true; - } - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.ParentPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.ParentPrimaryImageTag; - itemId = item.ParentPrimaryImageItemId; - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - imgType = 'Primary'; - imgTag = item.AlbumPrimaryImageTag; - itemId = item.AlbumId; - height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; - - if (primaryImageAspectRatio && uiAspect) { - coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; - } - } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if (item.BackdropImageTags && item.BackdropImageTags.length) { - imgType = 'Backdrop'; - imgTag = item.BackdropImageTags[0]; - } else if (item.ImageTags && item.ImageTags.Thumb) { - imgType = 'Thumb'; - imgTag = item.ImageTags.Thumb; - } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.SeriesThumbImageTag; - itemId = item.SeriesId; - } else if (item.ParentThumbItemId && options.inheritThumb !== false) { - imgType = 'Thumb'; - imgTag = item.ParentThumbImageTag; - itemId = item.ParentThumbItemId; - } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { - imgType = 'Backdrop'; - imgTag = item.ParentBackdropImageTags[0]; - itemId = item.ParentBackdropItemId; - } - /* eslint-enable sonarjs/no-duplicated-branches */ - - if (!itemId) { - itemId = item.Id; - } - - if (imgTag && imgType) { - // TODO: This place is a mess. Could do with a good spring cleaning. - if (!height && width && uiAspect) { - height = width / uiAspect; - } - imgUrl = apiClient.getScaledImageUrl(itemId, { - type: imgType, - fillHeight: height, - fillWidth: width, - quality: 96, - tag: imgTag - }); - } - - const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; - - return { - imgUrl: imgUrl, - blurhash: (blurHashes[imgType] || {})[imgTag], - forceName: forceName, - coverImage: coverImage - }; + if (options.preferThumb && options.showTitle !== false) { + forceName = true; } - /** + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.SeriesPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.PrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.PrimaryImageTag; + itemId = item.PrimaryImageItemId; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + + if (options.preferThumb && options.showTitle !== false) { + forceName = true; + } + + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.ParentPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + imgType = 'Primary'; + imgTag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + height = width && primaryImageAspectRatio ? Math.round(width / primaryImageAspectRatio) : null; + + if (primaryImageAspectRatio && uiAspect) { + coverImage = (Math.abs(primaryImageAspectRatio - uiAspect) / uiAspect) <= 0.2; + } + } else if (item.Type === 'Season' && item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if (item.BackdropImageTags && item.BackdropImageTags.length) { + imgType = 'Backdrop'; + imgTag = item.BackdropImageTags[0]; + } else if (item.ImageTags && item.ImageTags.Thumb) { + imgType = 'Thumb'; + imgTag = item.ImageTags.Thumb; + } else if (item.SeriesThumbImageTag && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.SeriesThumbImageTag; + itemId = item.SeriesId; + } else if (item.ParentThumbItemId && options.inheritThumb !== false) { + imgType = 'Thumb'; + imgTag = item.ParentThumbImageTag; + itemId = item.ParentThumbItemId; + } else if (item.ParentBackdropImageTags && item.ParentBackdropImageTags.length && options.inheritThumb !== false) { + imgType = 'Backdrop'; + imgTag = item.ParentBackdropImageTags[0]; + itemId = item.ParentBackdropItemId; + } + /* eslint-enable sonarjs/no-duplicated-branches */ + + if (!itemId) { + itemId = item.Id; + } + + if (imgTag && imgType) { + // TODO: This place is a mess. Could do with a good spring cleaning. + if (!height && width && uiAspect) { + height = width / uiAspect; + } + imgUrl = apiClient.getScaledImageUrl(itemId, { + type: imgType, + fillHeight: height, + fillWidth: width, + quality: 96, + tag: imgTag + }); + } + + const blurHashes = options.imageBlurhashes || item.ImageBlurHashes || {}; + + return { + imgUrl: imgUrl, + blurhash: (blurHashes[imgType] || {})[imgTag], + forceName: forceName, + coverImage: coverImage + }; +} + +/** * Generates an index used to select the default color of a card based on a string. * @param {?string} [str] - String to use for generating the index. * @returns {number} Index of the color. */ - function getDefaultColorIndex(str) { - const numRandomColors = 5; +function getDefaultColorIndex(str) { + const numRandomColors = 5; - if (str) { - const charIndex = Math.floor(str.length / 2); - const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); - let sum = 0; - for (let i = 0; i < character.length; i++) { - sum += parseInt(character.charAt(i), 10); - } - const index = String(sum).slice(-1); - - return (index % numRandomColors) + 1; - } else { - return randomInt(1, numRandomColors); - } + if (str) { + const charIndex = Math.floor(str.length / 2); + const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); + let sum = 0; + for (let i = 0; i < character.length; i++) { + sum += parseInt(character.charAt(i), 10); } + const index = String(sum).slice(-1); - /** + return (index % numRandomColors) + 1; + } else { + return randomInt(1, numRandomColors); + } +} + +/** * Generates the HTML markup for a card's text. * @param {Array} lines - Array containing the text lines. * @param {string} cssClass - Base CSS class to use for the lines. @@ -675,91 +674,91 @@ import { appRouter } from '../appRouter'; * @param {number} maxLines - Maximum number of lines to render. * @returns {string} HTML markup for the card's text. */ - function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout, addRightMargin, maxLines) { - let html = ''; +function getCardTextLines(lines, cssClass, forceLines, isOuterFooter, cardLayout, addRightMargin, maxLines) { + let html = ''; - let valid = 0; + let valid = 0; - for (let i = 0; i < lines.length; i++) { - let currentCssClass = cssClass; - const text = lines[i]; + for (let i = 0; i < lines.length; i++) { + let currentCssClass = cssClass; + const text = lines[i]; - if (valid > 0 && isOuterFooter) { - currentCssClass += ' cardText-secondary'; - } else if (valid === 0 && isOuterFooter) { - currentCssClass += ' cardText-first'; - } - - if (addRightMargin) { - currentCssClass += ' cardText-rightmargin'; - } - - if (text) { - html += "
"; - html += '' + text + ''; - html += '
'; - valid++; - - if (maxLines && valid >= maxLines) { - break; - } - } - } - - if (forceLines) { - const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); - - while (valid < linesLength) { - html += "
 
"; - valid++; - } - } - - return html; + if (valid > 0 && isOuterFooter) { + currentCssClass += ' cardText-secondary'; + } else if (valid === 0 && isOuterFooter) { + currentCssClass += ' cardText-first'; } - /** + if (addRightMargin) { + currentCssClass += ' cardText-rightmargin'; + } + + if (text) { + html += "
"; + html += '' + text + ''; + html += '
'; + valid++; + + if (maxLines && valid >= maxLines) { + break; + } + } + } + + if (forceLines) { + const linesLength = maxLines || Math.min(lines.length, maxLines || lines.length); + + while (valid < linesLength) { + html += "
 
"; + valid++; + } + } + + return html; +} + +/** * Determines if the item is live TV. * @param {Object} item - Item to use for the check. * @returns {boolean} Flag showing if the item is live TV. */ - function isUsingLiveTvNaming(item) { - return item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording'; - } +function isUsingLiveTvNaming(item) { + return item.Type === 'Program' || item.Type === 'Timer' || item.Type === 'Recording'; +} - /** +/** * Returns the air time text for the item based on the given times. * @param {object} item - Item used to generate the air time text. * @param {boolean} showAirDateTime - ISO8601 date for the start of the show. * @param {boolean} showAirEndTime - ISO8601 date for the end of the show. * @returns {string} The air time text for the item based on the given dates. */ - function getAirTimeText(item, showAirDateTime, showAirEndTime) { - let airTimeText = ''; +function getAirTimeText(item, showAirDateTime, showAirEndTime) { + let airTimeText = ''; - if (item.StartDate) { - try { - let date = datetime.parseISO8601Date(item.StartDate); + if (item.StartDate) { + try { + let date = datetime.parseISO8601Date(item.StartDate); - if (showAirDateTime) { - airTimeText += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }) + ' '; - } - - airTimeText += datetime.getDisplayTime(date); - - if (item.EndDate && showAirEndTime) { - date = datetime.parseISO8601Date(item.EndDate); - airTimeText += ' - ' + datetime.getDisplayTime(date); - } - } catch (e) { - console.error('error parsing date: ' + item.StartDate); - } + if (showAirDateTime) { + airTimeText += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }) + ' '; } - return airTimeText; - } + airTimeText += datetime.getDisplayTime(date); - /** + if (item.EndDate && showAirEndTime) { + date = datetime.parseISO8601Date(item.EndDate); + airTimeText += ' - ' + datetime.getDisplayTime(date); + } + } catch (e) { + console.error('error parsing date: ' + item.StartDate); + } + } + + return airTimeText; +} + +/** * Generates the HTML markup for the card's footer text. * @param {Object} item - Item used to generate the footer text. * @param {Object} apiClient - API client instance. @@ -770,370 +769,370 @@ import { appRouter } from '../appRouter'; * @param {Object} urls - Various urls for the footer * @returns {string} HTML markup of the card's footer text element. */ - function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) { - item = item.ProgramInfo || item; - let html = ''; +function getCardFooterText(item, apiClient, options, footerClass, progressHtml, flags, urls) { + item = item.ProgramInfo || item; + let html = ''; - if (urls.logoUrl) { - html += ''; - } + if (urls.logoUrl) { + html += ''; + } - const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder'); - const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText; + const showTitle = options.showTitle === 'auto' ? true : (options.showTitle || item.Type === 'PhotoAlbum' || item.Type === 'Folder'); + const showOtherText = flags.isOuterFooter ? !flags.overlayText : flags.overlayText; - if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { - html += ``; - } + if (flags.isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { + html += ``; + } - const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; - const serverId = item.ServerId || options.serverId; + const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; + const serverId = item.ServerId || options.serverId; - let lines = []; - const parentTitleUnderneath = item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'MusicVideo'; - let titleAdded; - - if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { - if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) { - if (item.SeriesId) { - lines.push(getTextActionButton({ - Id: item.SeriesId, - ServerId: serverId, - Name: item.SeriesName, - Type: 'Series', - IsFolder: true - })); - } else { - lines.push(escapeHtml(item.SeriesName)); - } - } else { - if (isUsingLiveTvNaming(item)) { - lines.push(escapeHtml(item.Name)); - - if (!item.EpisodeTitle && !item.IndexNumber) { - titleAdded = true; - } - } else { - const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - - if (parentTitle || showTitle) { - lines.push(escapeHtml(parentTitle)); - } - } - } - } - - let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length); - if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) { - showMediaTitle = true; - } - - if (showMediaTitle) { - const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, { - includeParentInfo: options.includeParentInfoInTitle - }); + let lines = []; + const parentTitleUnderneath = item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'MusicVideo'; + let titleAdded; + if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { + if (flags.isOuterFooter && item.Type === 'Episode' && item.SeriesName) { + if (item.SeriesId) { lines.push(getTextActionButton({ - Id: item.Id, + Id: item.SeriesId, ServerId: serverId, - Name: name, - Type: item.Type, - CollectionType: item.CollectionType, - IsFolder: item.IsFolder + Name: item.SeriesName, + Type: 'Series', + IsFolder: true })); + } else { + lines.push(escapeHtml(item.SeriesName)); + } + } else { + if (isUsingLiveTvNaming(item)) { + lines.push(escapeHtml(item.Name)); + + if (!item.EpisodeTitle && !item.IndexNumber) { + titleAdded = true; + } + } else { + const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; + + if (parentTitle || showTitle) { + lines.push(escapeHtml(parentTitle)); + } + } + } + } + + let showMediaTitle = (showTitle && !titleAdded) || (options.showParentTitleOrTitle && !lines.length); + if (!showMediaTitle && !titleAdded && (showTitle || flags.forceName)) { + showMediaTitle = true; + } + + if (showMediaTitle) { + const name = options.showTitle === 'auto' && !item.IsFolder && item.MediaType === 'Photo' ? '' : itemHelper.getDisplayName(item, { + includeParentInfo: options.includeParentInfoInTitle + }); + + lines.push(getTextActionButton({ + Id: item.Id, + ServerId: serverId, + Name: name, + Type: item.Type, + CollectionType: item.CollectionType, + IsFolder: item.IsFolder + })); + } + + if (showOtherText) { + if (options.showParentTitle && parentTitleUnderneath) { + if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { + item.AlbumArtists[0].Type = 'MusicArtist'; + item.AlbumArtists[0].IsFolder = true; + lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); + } else { + lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''))); + } + } + + if (item.ExtraType && item.ExtraType !== 'Unknown') { + lines.push(globalize.translate(item.ExtraType)); + } + + if (options.showItemCounts) { + lines.push(getItemCountsHtml(options, item)); + } + + if (options.textLines) { + const additionalLines = options.textLines(item); + for (let i = 0; i < additionalLines.length; i++) { + lines.push(additionalLines[i]); + } + } + + if (options.showSongCount) { + let songLine = ''; + + if (item.SongCount) { + songLine = item.SongCount === 1 ? + globalize.translate('ValueOneSong') : + globalize.translate('ValueSongCount', item.SongCount); } - if (showOtherText) { - if (options.showParentTitle && parentTitleUnderneath) { - if (flags.isOuterFooter && item.AlbumArtists && item.AlbumArtists.length) { - item.AlbumArtists[0].Type = 'MusicArtist'; - item.AlbumArtists[0].IsFolder = true; - lines.push(getTextActionButton(item.AlbumArtists[0], null, serverId)); - } else { - lines.push(escapeHtml(isUsingLiveTvNaming(item) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''))); - } + lines.push(songLine); + } + + if (options.showPremiereDate) { + if (item.PremiereDate) { + try { + lines.push(datetime.toLocaleDateString( + datetime.parseISO8601Date(item.PremiereDate), + { weekday: 'long', month: 'long', day: 'numeric' } + )); + } catch (err) { + lines.push(''); } + } else { + lines.push(''); + } + } - if (item.ExtraType && item.ExtraType !== 'Unknown') { - lines.push(globalize.translate(item.ExtraType)); - } - - if (options.showItemCounts) { - lines.push(getItemCountsHtml(options, item)); - } - - if (options.textLines) { - const additionalLines = options.textLines(item); - for (let i = 0; i < additionalLines.length; i++) { - lines.push(additionalLines[i]); - } - } - - if (options.showSongCount) { - let songLine = ''; - - if (item.SongCount) { - songLine = item.SongCount === 1 ? - globalize.translate('ValueOneSong') : - globalize.translate('ValueSongCount', item.SongCount); - } - - lines.push(songLine); - } - - if (options.showPremiereDate) { - if (item.PremiereDate) { - try { - lines.push(datetime.toLocaleDateString( - datetime.parseISO8601Date(item.PremiereDate), - { weekday: 'long', month: 'long', day: 'numeric' } - )); - } catch (err) { - lines.push(''); - } - } else { - lines.push(''); - } - } - - if (options.showYear || options.showSeriesYear) { - const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); - if (item.Type === 'Series') { - if (item.Status === 'Continuing') { - lines.push(globalize.translate('SeriesYearToPresent', productionYear || '')); - } else { - if (item.EndDate && item.ProductionYear) { - const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); - lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); - } else { - lines.push(productionYear || ''); - } - } + if (options.showYear || options.showSeriesYear) { + const productionYear = item.ProductionYear && datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); + if (item.Type === 'Series') { + if (item.Status === 'Continuing') { + lines.push(globalize.translate('SeriesYearToPresent', productionYear || '')); + } else { + if (item.EndDate && item.ProductionYear) { + const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); + lines.push(productionYear + ((endYear === item.ProductionYear) ? '' : (' - ' + endYear))); } else { lines.push(productionYear || ''); } } - - if (options.showRuntime) { - if (item.RunTimeTicks) { - lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); - } else { - lines.push(''); - } - } - - if (options.showAirTime) { - lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ''); - } - - if (options.showChannelName) { - if (item.ChannelId) { - lines.push(getTextActionButton({ - - Id: item.ChannelId, - ServerId: serverId, - Name: item.ChannelName, - Type: 'TvChannel', - MediaType: item.MediaType, - IsFolder: false - - }, item.ChannelName)); - } else { - lines.push(escapeHtml(item.ChannelName || '') || ' '); - } - } - - if (options.showCurrentProgram && item.Type === 'TvChannel') { - if (item.CurrentProgram) { - lines.push(escapeHtml(item.CurrentProgram.Name)); - } else { - lines.push(''); - } - } - - if (options.showCurrentProgramTime && item.Type === 'TvChannel') { - if (item.CurrentProgram) { - lines.push(getAirTimeText(item.CurrentProgram, false, true) || ''); - } else { - lines.push(''); - } - } - - if (options.showSeriesTimerTime) { - if (item.RecordAnyTime) { - lines.push(globalize.translate('Anytime')); - } else { - lines.push(datetime.getDisplayTime(item.StartDate)); - } - } - - if (options.showSeriesTimerChannel) { - if (item.RecordAnyChannel) { - lines.push(globalize.translate('AllChannels')); - } else { - lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel')); - } - } - - if (options.showPersonRoleOrType && item.Role) { - lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); - } + } else { + lines.push(productionYear || ''); } - - if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) { - lines = []; - } - - if (flags.overlayText && showTitle) { - lines = [escapeHtml(item.Name)]; - } - - const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; - - html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); - - if (progressHtml) { - html += progressHtml; - } - - if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) { - html = '
' + html; - - //cardFooter - html += '
'; - } - - return html; } - /** + if (options.showRuntime) { + if (item.RunTimeTicks) { + lines.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); + } else { + lines.push(''); + } + } + + if (options.showAirTime) { + lines.push(getAirTimeText(item, options.showAirDateTime, options.showAirEndTime) || ''); + } + + if (options.showChannelName) { + if (item.ChannelId) { + lines.push(getTextActionButton({ + + Id: item.ChannelId, + ServerId: serverId, + Name: item.ChannelName, + Type: 'TvChannel', + MediaType: item.MediaType, + IsFolder: false + + }, item.ChannelName)); + } else { + lines.push(escapeHtml(item.ChannelName || '') || ' '); + } + } + + if (options.showCurrentProgram && item.Type === 'TvChannel') { + if (item.CurrentProgram) { + lines.push(escapeHtml(item.CurrentProgram.Name)); + } else { + lines.push(''); + } + } + + if (options.showCurrentProgramTime && item.Type === 'TvChannel') { + if (item.CurrentProgram) { + lines.push(getAirTimeText(item.CurrentProgram, false, true) || ''); + } else { + lines.push(''); + } + } + + if (options.showSeriesTimerTime) { + if (item.RecordAnyTime) { + lines.push(globalize.translate('Anytime')); + } else { + lines.push(datetime.getDisplayTime(item.StartDate)); + } + } + + if (options.showSeriesTimerChannel) { + if (item.RecordAnyChannel) { + lines.push(globalize.translate('AllChannels')); + } else { + lines.push(escapeHtml(item.ChannelName || '') || globalize.translate('OneChannel')); + } + } + + if (options.showPersonRoleOrType && item.Role) { + lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); + } + } + + if ((showTitle || !urls.imgUrl) && flags.forceName && flags.overlayText && lines.length === 1) { + lines = []; + } + + if (flags.overlayText && showTitle) { + lines = [escapeHtml(item.Name)]; + } + + const addRightTextMargin = flags.isOuterFooter && options.cardLayout && !options.centerText && options.cardFooterAside !== 'none' && layoutManager.mobile; + + html += getCardTextLines(lines, cssClass, !options.overlayText, flags.isOuterFooter, options.cardLayout, addRightTextMargin, options.lines); + + if (progressHtml) { + html += progressHtml; + } + + if (html && (!flags.isOuterFooter || urls.logoUrl || options.cardLayout)) { + html = '
' + html; + + //cardFooter + html += '
'; + } + + return html; +} + +/** * Generates the HTML markup for the action button. * @param {Object} item - Item used to generate the action button. * @param {string} text - Text of the action button. * @param {string} serverId - ID of the server. * @returns {string} HTML markup of the action button. */ - function getTextActionButton(item, text, serverId) { - if (!text) { - text = itemHelper.getDisplayName(item); - } +function getTextActionButton(item, text, serverId) { + if (!text) { + text = itemHelper.getDisplayName(item); + } - text = escapeHtml(text); + text = escapeHtml(text); - if (layoutManager.tv) { - return text; - } + if (layoutManager.tv) { + return text; + } - const url = appRouter.getRouteUrl(item); - let html = ''; - html += text; - html += ''; + const url = appRouter.getRouteUrl(item); + let html = ''; + html += text; + html += ''; - return html; - } + return html; +} - /** +/** * Generates HTML markup for the item count indicator. * @param {Object} options - Options used to generate the item count. * @param {Object} item - Item used to generate the item count. * @returns {string} HTML markup for the item count indicator. */ - function getItemCountsHtml(options, item) { - const counts = []; - let childText; +function getItemCountsHtml(options, item) { + const counts = []; + let childText; - if (item.Type === 'Playlist') { - childText = ''; + if (item.Type === 'Playlist') { + childText = ''; - if (item.RunTimeTicks) { - let minutes = item.RunTimeTicks / 600000000; + if (item.RunTimeTicks) { + let minutes = item.RunTimeTicks / 600000000; - minutes = minutes || 1; + minutes = minutes || 1; - childText += globalize.translate('ValueMinutes', Math.round(minutes)); - } else { - childText += globalize.translate('ValueMinutes', 0); - } - - counts.push(childText); - } else if (item.Type === 'Genre' || item.Type === 'Studio') { - if (item.MovieCount) { - childText = item.MovieCount === 1 ? - globalize.translate('ValueOneMovie') : - globalize.translate('ValueMovieCount', item.MovieCount); - - counts.push(childText); - } - - if (item.SeriesCount) { - childText = item.SeriesCount === 1 ? - globalize.translate('ValueOneSeries') : - globalize.translate('ValueSeriesCount', item.SeriesCount); - - counts.push(childText); - } - if (item.EpisodeCount) { - childText = item.EpisodeCount === 1 ? - globalize.translate('ValueOneEpisode') : - globalize.translate('ValueEpisodeCount', item.EpisodeCount); - - counts.push(childText); - } - } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { - if (item.AlbumCount) { - childText = item.AlbumCount === 1 ? - globalize.translate('ValueOneAlbum') : - globalize.translate('ValueAlbumCount', item.AlbumCount); - - counts.push(childText); - } - if (item.SongCount) { - childText = item.SongCount === 1 ? - globalize.translate('ValueOneSong') : - globalize.translate('ValueSongCount', item.SongCount); - - counts.push(childText); - } - if (item.MusicVideoCount) { - childText = item.MusicVideoCount === 1 ? - globalize.translate('ValueOneMusicVideo') : - globalize.translate('ValueMusicVideoCount', item.MusicVideoCount); - - counts.push(childText); - } - } else if (item.Type === 'Series') { - childText = item.RecursiveItemCount === 1 ? - globalize.translate('ValueOneEpisode') : - globalize.translate('ValueEpisodeCount', item.RecursiveItemCount); - - counts.push(childText); - } - - return counts.join(', '); + childText += globalize.translate('ValueMinutes', Math.round(minutes)); + } else { + childText += globalize.translate('ValueMinutes', 0); } - let refreshIndicatorLoaded; + counts.push(childText); + } else if (item.Type === 'Genre' || item.Type === 'Studio') { + if (item.MovieCount) { + childText = item.MovieCount === 1 ? + globalize.translate('ValueOneMovie') : + globalize.translate('ValueMovieCount', item.MovieCount); - /** + counts.push(childText); + } + + if (item.SeriesCount) { + childText = item.SeriesCount === 1 ? + globalize.translate('ValueOneSeries') : + globalize.translate('ValueSeriesCount', item.SeriesCount); + + counts.push(childText); + } + if (item.EpisodeCount) { + childText = item.EpisodeCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', item.EpisodeCount); + + counts.push(childText); + } + } else if (item.Type === 'MusicGenre' || options.context === 'MusicArtist') { + if (item.AlbumCount) { + childText = item.AlbumCount === 1 ? + globalize.translate('ValueOneAlbum') : + globalize.translate('ValueAlbumCount', item.AlbumCount); + + counts.push(childText); + } + if (item.SongCount) { + childText = item.SongCount === 1 ? + globalize.translate('ValueOneSong') : + globalize.translate('ValueSongCount', item.SongCount); + + counts.push(childText); + } + if (item.MusicVideoCount) { + childText = item.MusicVideoCount === 1 ? + globalize.translate('ValueOneMusicVideo') : + globalize.translate('ValueMusicVideoCount', item.MusicVideoCount); + + counts.push(childText); + } + } else if (item.Type === 'Series') { + childText = item.RecursiveItemCount === 1 ? + globalize.translate('ValueOneEpisode') : + globalize.translate('ValueEpisodeCount', item.RecursiveItemCount); + + counts.push(childText); + } + + return counts.join(', '); +} + +let refreshIndicatorLoaded; + +/** * Imports the refresh indicator element. */ - function importRefreshIndicator() { - if (!refreshIndicatorLoaded) { - refreshIndicatorLoaded = true; - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator'); - } - } +function importRefreshIndicator() { + if (!refreshIndicatorLoaded) { + refreshIndicatorLoaded = true; + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-itemrefreshindicator/emby-itemrefreshindicator'); + } +} - /** +/** * Returns the default background class for a card based on a string. * @param {?string} [str] - Text used to generate the background class. * @returns {string} CSS classes for default card backgrounds. */ - export function getDefaultBackgroundClass(str) { - return 'defaultCardBackground defaultCardBackground' + getDefaultColorIndex(str); - } +export function getDefaultBackgroundClass(str) { + return 'defaultCardBackground defaultCardBackground' + getDefaultColorIndex(str); +} - /** +/** * Builds the HTML markup for an individual card. * @param {number} index - Index of the card * @param {object} item - Item used to generate the card. @@ -1141,619 +1140,617 @@ import { appRouter } from '../appRouter'; * @param {object} options - Options used to generate the card. * @returns {string} HTML markup for the generated card. */ - function buildCard(index, item, apiClient, options) { - let action = options.action || 'link'; +function buildCard(index, item, apiClient, options) { + let action = options.action || 'link'; - if (action === 'play' && item.IsFolder) { - // If this hard-coding is ever removed make sure to test nested photo albums - action = 'link'; - } else if (item.MediaType === 'Photo') { - action = 'play'; - } + if (action === 'play' && item.IsFolder) { + // If this hard-coding is ever removed make sure to test nested photo albums + action = 'link'; + } else if (item.MediaType === 'Photo') { + action = 'play'; + } - let shape = options.shape; + let shape = options.shape; - if (shape === 'mixed') { - shape = null; + if (shape === 'mixed') { + shape = null; - const primaryImageAspectRatio = item.PrimaryImageAspectRatio; + const primaryImageAspectRatio = item.PrimaryImageAspectRatio; - if (primaryImageAspectRatio) { - if (primaryImageAspectRatio >= 1.33) { - shape = 'mixedBackdrop'; - } else if (primaryImageAspectRatio > 0.71) { - shape = 'mixedSquare'; - } else { - shape = 'mixedPortrait'; - } - } - - shape = shape || 'mixedSquare'; - } - - // TODO move card creation code to Card component - - let className = 'card'; - - if (shape) { - className += ' ' + shape + 'Card'; - } - - if (options.cardCssClass) { - className += ' ' + options.cardCssClass; - } - - if (options.cardClass) { - className += ' ' + options.cardClass; - } - - if (layoutManager.desktop) { - className += ' card-hoverable'; - } - - if (layoutManager.tv) { - className += ' show-focus'; - - if (enableFocusTransform) { - className += ' show-animation'; - } - } - - const imgInfo = getCardImageUrl(item, apiClient, options, shape); - const imgUrl = imgInfo.imgUrl; - const blurhash = imgInfo.blurhash; - - const forceName = imgInfo.forceName; - - const overlayText = options.overlayText; - - let cardImageContainerClass = 'cardImageContainer'; - const coveredImage = options.coverImage || imgInfo.coverImage; - - if (coveredImage) { - cardImageContainerClass += ' coveredImage'; - - if (item.Type === 'TvChannel') { - cardImageContainerClass += ' coveredImage-contain'; - } - } - - if (!imgUrl) { - cardImageContainerClass += ' ' + getDefaultBackgroundClass(item.Name); - } - - let cardBoxClass = options.cardLayout ? 'cardBox visualCardBox' : 'cardBox'; - - let footerCssClass; - let progressHtml = indicators.getProgressBarHtml(item); - - let innerCardFooter = ''; - - let footerOverlayed = false; - - let logoUrl; - const logoHeight = 40; - - if (options.showChannelLogo && item.ChannelPrimaryImageTag) { - logoUrl = apiClient.getScaledImageUrl(item.ChannelId, { - type: 'Primary', - height: logoHeight, - tag: item.ChannelPrimaryImageTag - }); - } else if (options.showLogo && item.ParentLogoImageTag) { - logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { - type: 'Logo', - height: logoHeight, - tag: item.ParentLogoImageTag - }); - } - - if (overlayText) { - logoUrl = null; - - footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; - innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl }); - footerOverlayed = true; - } else if (progressHtml) { - innerCardFooter += '
'; - innerCardFooter += progressHtml; - innerCardFooter += '
'; - - progressHtml = ''; - } - - const mediaSourceCount = item.MediaSourceCount || 1; - if (mediaSourceCount > 1 && options.disableIndicators !== true) { - innerCardFooter += '
' + mediaSourceCount + '
'; - } - - let outerCardFooter = ''; - if (!overlayText && !footerOverlayed) { - footerCssClass = options.cardLayout ? 'cardFooter' : 'cardFooter cardFooter-transparent'; - - if (logoUrl) { - footerCssClass += ' cardFooter-withlogo'; - } - - if (!options.cardLayout) { - logoUrl = null; - } - - outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl }); - } - - if (outerCardFooter && !options.cardLayout) { - cardBoxClass += ' cardBox-bottompadded'; - } - - let overlayButtons = ''; - if (layoutManager.mobile) { - let overlayPlayButton = options.overlayPlayButton; - - if (overlayPlayButton == null && !options.overlayMoreButton && !options.overlayInfoButton && !options.cardLayout) { - overlayPlayButton = item.MediaType === 'Video'; - } - - const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction'; - - if (options.centerPlayButton) { - overlayButtons += ``; - } - - if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') { - overlayButtons += ``; - } - - if (options.overlayMoreButton) { - overlayButtons += ``; - } - } - - if (options.showChildCountIndicator && item.ChildCount) { - className += ' groupedCard'; - } - - // cardBox can be it's own separate element if an outer footer is ever needed - let cardImageContainerOpen; - let cardImageContainerClose = ''; - let cardBoxClose = ''; - let cardScalableClose = ''; - - const cardContentClass = 'cardContent'; - - let blurhashAttrib = ''; - if (blurhash && blurhash.length > 0) { - blurhashAttrib = 'data-blurhash="' + blurhash + '"'; - } - - if (layoutManager.tv) { - // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? ('
') : ('
'); - - cardImageContainerClose = '
'; + if (primaryImageAspectRatio) { + if (primaryImageAspectRatio >= 1.33) { + shape = 'mixedBackdrop'; + } else if (primaryImageAspectRatio > 0.71) { + shape = 'mixedSquare'; } else { - const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; - - const url = appRouter.getRouteUrl(item); - // Don't use the IMG tag with safari because it puts a white border around it - cardImageContainerOpen = imgUrl ? ('') : (''); - - cardImageContainerClose = ''; + shape = 'mixedPortrait'; } - - const cardScalableClass = 'cardScalable'; - - let cardPadderIcon = ''; - - // TV Channel logos are transparent so skip the placeholder to avoid overlapping - if (imgUrl && item.Type !== 'TvChannel') { - cardPadderIcon = getDefaultText(item, { - // Always use an icon - defaultCardImageIcon: 'folder', - ...options - }); - } - - cardImageContainerOpen = `
${cardPadderIcon}
${cardImageContainerOpen}`; - cardBoxClose = '
'; - cardScalableClose = '
'; - - if (options.disableIndicators !== true) { - let indicatorsHtml = ''; - - if (options.missingIndicator !== false) { - indicatorsHtml += indicators.getMissingIndicator(item); - } - - indicatorsHtml += indicators.getSyncIndicator(item); - indicatorsHtml += indicators.getTimerIndicator(item); - - indicatorsHtml += indicators.getTypeIndicator(item); - - if (options.showGroupCount) { - indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { - minCount: 1 - }); - } else { - indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - } - - if (item.Type === 'CollectionFolder' || item.CollectionType) { - const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; - indicatorsHtml += '
'; - importRefreshIndicator(); - } - - if (indicatorsHtml) { - cardImageContainerOpen += '
' + indicatorsHtml + '
'; - } - } - - if (!imgUrl) { - cardImageContainerOpen += getDefaultText(item, options); - } - - const tagName = (layoutManager.tv) && !overlayButtons ? 'button' : 'div'; - - const nameWithPrefix = (item.SortName || item.Name || ''); - let prefix = nameWithPrefix.substring(0, Math.min(3, nameWithPrefix.length)); - - if (prefix) { - prefix = prefix.toUpperCase(); - } - - let timerAttributes = ''; - if (item.TimerId) { - timerAttributes += ' data-timerid="' + item.TimerId + '"'; - } - if (item.SeriesTimerId) { - timerAttributes += ' data-seriestimerid="' + item.SeriesTimerId + '"'; - } - - let actionAttribute; - let ariaLabelAttribute = ''; - - if (tagName === 'button') { - className += ' itemAction'; - actionAttribute = ' data-action="' + action + '"'; - ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; - } else { - actionAttribute = ''; - } - - if (item.Type !== 'MusicAlbum' && item.Type !== 'MusicArtist' && item.Type !== 'Audio') { - className += ' card-withuserdata'; - } - - 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 + '"') : ''; - const pathData = item.Path ? (' data-path="' + escapeHtml(item.Path) + '"') : ''; - const contextData = options.context ? (' data-context="' + options.context + '"') : ''; - const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : ''; - const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : ''; - const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : ''; - - let additionalCardContent = ''; - - if (layoutManager.desktop && !options.disableHoverMenu) { - additionalCardContent += getHoverMenuHtml(item, action); - } - - return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; } - /** + shape = shape || 'mixedSquare'; + } + + // TODO move card creation code to Card component + + let className = 'card'; + + if (shape) { + className += ' ' + shape + 'Card'; + } + + if (options.cardCssClass) { + className += ' ' + options.cardCssClass; + } + + if (options.cardClass) { + className += ' ' + options.cardClass; + } + + if (layoutManager.desktop) { + className += ' card-hoverable'; + } + + if (layoutManager.tv) { + className += ' show-focus'; + + if (enableFocusTransform) { + className += ' show-animation'; + } + } + + const imgInfo = getCardImageUrl(item, apiClient, options, shape); + const imgUrl = imgInfo.imgUrl; + const blurhash = imgInfo.blurhash; + + const forceName = imgInfo.forceName; + + const overlayText = options.overlayText; + + let cardImageContainerClass = 'cardImageContainer'; + const coveredImage = options.coverImage || imgInfo.coverImage; + + if (coveredImage) { + cardImageContainerClass += ' coveredImage'; + + if (item.Type === 'TvChannel') { + cardImageContainerClass += ' coveredImage-contain'; + } + } + + if (!imgUrl) { + cardImageContainerClass += ' ' + getDefaultBackgroundClass(item.Name); + } + + let cardBoxClass = options.cardLayout ? 'cardBox visualCardBox' : 'cardBox'; + + let footerCssClass; + let progressHtml = indicators.getProgressBarHtml(item); + + let innerCardFooter = ''; + + let footerOverlayed = false; + + let logoUrl; + const logoHeight = 40; + + if (options.showChannelLogo && item.ChannelPrimaryImageTag) { + logoUrl = apiClient.getScaledImageUrl(item.ChannelId, { + type: 'Primary', + height: logoHeight, + tag: item.ChannelPrimaryImageTag + }); + } else if (options.showLogo && item.ParentLogoImageTag) { + logoUrl = apiClient.getScaledImageUrl(item.ParentLogoItemId, { + type: 'Logo', + height: logoHeight, + tag: item.ParentLogoImageTag + }); + } + + if (overlayText) { + logoUrl = null; + + footerCssClass = progressHtml ? 'innerCardFooter fullInnerCardFooter' : 'innerCardFooter'; + innerCardFooter += getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: false }, { imgUrl, logoUrl }); + footerOverlayed = true; + } else if (progressHtml) { + innerCardFooter += '
'; + innerCardFooter += progressHtml; + innerCardFooter += '
'; + + progressHtml = ''; + } + + const mediaSourceCount = item.MediaSourceCount || 1; + if (mediaSourceCount > 1 && options.disableIndicators !== true) { + innerCardFooter += '
' + mediaSourceCount + '
'; + } + + let outerCardFooter = ''; + if (!overlayText && !footerOverlayed) { + footerCssClass = options.cardLayout ? 'cardFooter' : 'cardFooter cardFooter-transparent'; + + if (logoUrl) { + footerCssClass += ' cardFooter-withlogo'; + } + + if (!options.cardLayout) { + logoUrl = null; + } + + outerCardFooter = getCardFooterText(item, apiClient, options, footerCssClass, progressHtml, { forceName, overlayText, isOuterFooter: true }, { imgUrl, logoUrl }); + } + + if (outerCardFooter && !options.cardLayout) { + cardBoxClass += ' cardBox-bottompadded'; + } + + let overlayButtons = ''; + if (layoutManager.mobile) { + let overlayPlayButton = options.overlayPlayButton; + + if (overlayPlayButton == null && !options.overlayMoreButton && !options.overlayInfoButton && !options.cardLayout) { + overlayPlayButton = item.MediaType === 'Video'; + } + + const btnCssClass = 'cardOverlayButton cardOverlayButton-br itemAction'; + + if (options.centerPlayButton) { + overlayButtons += ``; + } + + if (overlayPlayButton && !item.IsPlaceHolder && (item.LocationType !== 'Virtual' || !item.MediaType || item.Type === 'Program') && item.Type !== 'Person') { + overlayButtons += ``; + } + + if (options.overlayMoreButton) { + overlayButtons += ``; + } + } + + if (options.showChildCountIndicator && item.ChildCount) { + className += ' groupedCard'; + } + + // cardBox can be it's own separate element if an outer footer is ever needed + let cardImageContainerOpen; + let cardImageContainerClose = ''; + let cardBoxClose = ''; + let cardScalableClose = ''; + + const cardContentClass = 'cardContent'; + + let blurhashAttrib = ''; + if (blurhash && blurhash.length > 0) { + blurhashAttrib = 'data-blurhash="' + blurhash + '"'; + } + + if (layoutManager.tv) { + // Don't use the IMG tag with safari because it puts a white border around it + cardImageContainerOpen = imgUrl ? ('
') : ('
'); + + cardImageContainerClose = '
'; + } else { + const cardImageContainerAriaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; + + const url = appRouter.getRouteUrl(item); + // Don't use the IMG tag with safari because it puts a white border around it + cardImageContainerOpen = imgUrl ? ('') : (''); + + cardImageContainerClose = ''; + } + + const cardScalableClass = 'cardScalable'; + + let cardPadderIcon = ''; + + // TV Channel logos are transparent so skip the placeholder to avoid overlapping + if (imgUrl && item.Type !== 'TvChannel') { + cardPadderIcon = getDefaultText(item, { + // Always use an icon + defaultCardImageIcon: 'folder', + ...options + }); + } + + cardImageContainerOpen = `
${cardPadderIcon}
${cardImageContainerOpen}`; + cardBoxClose = '
'; + cardScalableClose = '
'; + + if (options.disableIndicators !== true) { + let indicatorsHtml = ''; + + if (options.missingIndicator !== false) { + indicatorsHtml += indicators.getMissingIndicator(item); + } + + indicatorsHtml += indicators.getSyncIndicator(item); + indicatorsHtml += indicators.getTimerIndicator(item); + + indicatorsHtml += indicators.getTypeIndicator(item); + + if (options.showGroupCount) { + indicatorsHtml += indicators.getChildCountIndicatorHtml(item, { + minCount: 1 + }); + } else { + indicatorsHtml += indicators.getPlayedIndicatorHtml(item); + } + + if (item.Type === 'CollectionFolder' || item.CollectionType) { + const refreshClass = item.RefreshProgress ? '' : ' class="hide"'; + indicatorsHtml += '
'; + importRefreshIndicator(); + } + + if (indicatorsHtml) { + cardImageContainerOpen += '
' + indicatorsHtml + '
'; + } + } + + if (!imgUrl) { + cardImageContainerOpen += getDefaultText(item, options); + } + + const tagName = (layoutManager.tv) && !overlayButtons ? 'button' : 'div'; + + const nameWithPrefix = (item.SortName || item.Name || ''); + let prefix = nameWithPrefix.substring(0, Math.min(3, nameWithPrefix.length)); + + if (prefix) { + prefix = prefix.toUpperCase(); + } + + let timerAttributes = ''; + if (item.TimerId) { + timerAttributes += ' data-timerid="' + item.TimerId + '"'; + } + if (item.SeriesTimerId) { + timerAttributes += ' data-seriestimerid="' + item.SeriesTimerId + '"'; + } + + let actionAttribute; + let ariaLabelAttribute = ''; + + if (tagName === 'button') { + className += ' itemAction'; + actionAttribute = ' data-action="' + action + '"'; + ariaLabelAttribute = ` aria-label="${escapeHtml(item.Name)}"`; + } else { + actionAttribute = ''; + } + + if (item.Type !== 'MusicAlbum' && item.Type !== 'MusicArtist' && item.Type !== 'Audio') { + className += ' card-withuserdata'; + } + + 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 + '"') : ''; + const pathData = item.Path ? (' data-path="' + escapeHtml(item.Path) + '"') : ''; + const contextData = options.context ? (' data-context="' + options.context + '"') : ''; + const parentIdData = options.parentId ? (' data-parentid="' + options.parentId + '"') : ''; + const startDate = item.StartDate ? (' data-startdate="' + item.StartDate.toString() + '"') : ''; + const endDate = item.EndDate ? (' data-enddate="' + item.EndDate.toString() + '"') : ''; + + let additionalCardContent = ''; + + if (layoutManager.desktop && !options.disableHoverMenu) { + additionalCardContent += getHoverMenuHtml(item, action); + } + + return '<' + tagName + ' data-index="' + index + '"' + timerAttributes + actionAttribute + ' data-isfolder="' + (item.IsFolder || false) + '" data-serverid="' + (item.ServerId || options.serverId) + '" data-id="' + (item.Id || item.ItemId) + '" data-type="' + item.Type + '"' + mediaTypeData + collectionTypeData + channelIdData + pathData + positionTicksData + collectionIdData + playlistIdData + contextData + parentIdData + startDate + endDate + ' data-prefix="' + escapeHtml(prefix) + '" class="' + className + '"' + ariaLabelAttribute + '>' + cardImageContainerOpen + innerCardFooter + cardImageContainerClose + overlayButtons + additionalCardContent + cardScalableClose + outerCardFooter + cardBoxClose + ''; +} + +/** * Generates HTML markup for the card overlay. * @param {object} item - Item used to generate the card overlay. * @param {string} action - Action assigned to the overlay. * @returns {string} HTML markup of the card overlay. */ - function getHoverMenuHtml(item, action) { - let html = ''; +function getHoverMenuHtml(item, action) { + let html = ''; - html += '
'; - const url = appRouter.getRouteUrl(item); - html += ''; + html += '
'; + const url = appRouter.getRouteUrl(item); + html += ''; - const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light'; + const btnCssClass = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light'; - if (playbackManager.canPlay(item)) { - html += ''; - } + if (playbackManager.canPlay(item)) { + html += ''; + } - html += '
'; + html += '
'; - const userData = item.UserData || {}; + const userData = item.UserData || {}; - if (itemHelper.canMarkPlayed(item)) { - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-playstatebutton/emby-playstatebutton'); - html += ''; - } + if (itemHelper.canMarkPlayed(item)) { + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-playstatebutton/emby-playstatebutton'); + html += ''; + } - if (itemHelper.canRate(item)) { - const likes = userData.Likes == null ? '' : userData.Likes; + if (itemHelper.canRate(item)) { + const likes = userData.Likes == null ? '' : userData.Likes; - /* eslint-disable-next-line @babel/no-unused-expressions */ - import('../../elements/emby-ratingbutton/emby-ratingbutton'); - html += ''; - } + /* eslint-disable-next-line @babel/no-unused-expressions */ + import('../../elements/emby-ratingbutton/emby-ratingbutton'); + html += ''; + } - html += ``; - html += '
'; - html += '
'; + html += ``; + html += '
'; + html += '
'; - return html; - } + return html; +} - /** +/** * Generates the text or icon used for default card backgrounds. * @param {object} item - Item used to generate the card overlay. * @param {object} options - Options used to generate the card overlay. * @returns {string} HTML markup of the card overlay. */ - export function getDefaultText(item, options) { - if (item.CollectionType) { - return ''; - } +export function getDefaultText(item, options) { + if (item.CollectionType) { + return ''; + } - switch (item.Type) { - case 'MusicAlbum': - return ''; - case 'MusicArtist': - case 'Person': - return ''; - case 'Audio': - return ''; - case 'Movie': - return ''; - case 'Episode': - case 'Series': - return ''; - case 'Program': - return ''; - case 'Book': - return ''; - case 'Folder': - return ''; - case 'BoxSet': - return ''; - case 'Playlist': - return ''; - case 'Photo': - return ''; - case 'PhotoAlbum': - return ''; - } + switch (item.Type) { + case 'MusicAlbum': + return ''; + case 'MusicArtist': + case 'Person': + return ''; + case 'Audio': + return ''; + case 'Movie': + return ''; + case 'Episode': + case 'Series': + return ''; + case 'Program': + return ''; + case 'Book': + return ''; + case 'Folder': + return ''; + case 'BoxSet': + return ''; + case 'Playlist': + return ''; + case 'Photo': + return ''; + case 'PhotoAlbum': + return ''; + } - if (options?.defaultCardImageIcon) { - return ''; - } + if (options?.defaultCardImageIcon) { + return ''; + } - const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item); - return '
' + escapeHtml(defaultName) + '
'; - } + const defaultName = isUsingLiveTvNaming(item) ? item.Name : itemHelper.getDisplayName(item); + return '
' + escapeHtml(defaultName) + '
'; +} - /** +/** * Builds a set of cards and inserts them into the page. * @param {Array} items - Array of items used to build the cards. * @param {options} options - Options of the cards to build. */ - export function buildCards(items, options) { - // Abort if the container has been disposed - if (!document.body.contains(options.itemsContainer)) { - return; - } +export function buildCards(items, options) { + // Abort if the container has been disposed + if (!document.body.contains(options.itemsContainer)) { + return; + } - if (options.parentContainer) { - if (items.length) { - options.parentContainer.classList.remove('hide'); - } else { - options.parentContainer.classList.add('hide'); - return; - } - } + if (options.parentContainer) { + if (items.length) { + options.parentContainer.classList.remove('hide'); + } else { + options.parentContainer.classList.add('hide'); + return; + } + } - const html = buildCardsHtmlInternal(items, options); + const html = buildCardsHtmlInternal(items, options); - if (html) { - if (options.itemsContainer.cardBuilderHtml !== html) { - options.itemsContainer.innerHTML = html; + if (html) { + if (options.itemsContainer.cardBuilderHtml !== html) { + options.itemsContainer.innerHTML = html; - if (items.length < 50) { - options.itemsContainer.cardBuilderHtml = html; - } else { - options.itemsContainer.cardBuilderHtml = null; - } - } - - imageLoader.lazyChildren(options.itemsContainer); + if (items.length < 50) { + options.itemsContainer.cardBuilderHtml = html; } else { - options.itemsContainer.innerHTML = html; options.itemsContainer.cardBuilderHtml = null; } - - if (options.autoFocus) { - focusManager.autoFocus(options.itemsContainer, true); - } } - /** + imageLoader.lazyChildren(options.itemsContainer); + } else { + options.itemsContainer.innerHTML = html; + options.itemsContainer.cardBuilderHtml = null; + } + + if (options.autoFocus) { + focusManager.autoFocus(options.itemsContainer, true); + } +} + +/** * Ensures the indicators for a card exist and creates them if they don't exist. * @param {HTMLDivElement} card - DOM element of the card. * @param {HTMLDivElement} indicatorsElem - DOM element of the indicators. * @returns {HTMLDivElement} - DOM element of the indicators. */ - function ensureIndicators(card, indicatorsElem) { - if (indicatorsElem) { - return indicatorsElem; - } +function ensureIndicators(card, indicatorsElem) { + if (indicatorsElem) { + return indicatorsElem; + } - indicatorsElem = card.querySelector('.cardIndicators'); + indicatorsElem = card.querySelector('.cardIndicators'); - if (!indicatorsElem) { - const cardImageContainer = card.querySelector('.cardImageContainer'); - indicatorsElem = document.createElement('div'); - indicatorsElem.classList.add('cardIndicators'); - cardImageContainer.appendChild(indicatorsElem); - } + if (!indicatorsElem) { + const cardImageContainer = card.querySelector('.cardImageContainer'); + indicatorsElem = document.createElement('div'); + indicatorsElem.classList.add('cardIndicators'); + cardImageContainer.appendChild(indicatorsElem); + } - return indicatorsElem; - } + return indicatorsElem; +} - /** +/** * Adds user data to the card such as progress indicators and played status. * @param {HTMLDivElement} card - DOM element of the card. * @param {Object} userData - User data to apply to the card. */ - function updateUserData(card, userData) { - const type = card.getAttribute('data-type'); - const enableCountIndicator = type === 'Series' || type === 'BoxSet' || type === 'Season'; - let indicatorsElem = null; - let playedIndicator = null; - let countIndicator = null; - let itemProgressBar = null; +function updateUserData(card, userData) { + const type = card.getAttribute('data-type'); + const enableCountIndicator = type === 'Series' || type === 'BoxSet' || type === 'Season'; + let indicatorsElem = null; + let playedIndicator = null; + let countIndicator = null; + let itemProgressBar = null; - if (userData.Played) { - playedIndicator = card.querySelector('.playedIndicator'); + if (userData.Played) { + playedIndicator = card.querySelector('.playedIndicator'); - if (!playedIndicator) { - playedIndicator = document.createElement('div'); - playedIndicator.classList.add('playedIndicator'); - playedIndicator.classList.add('indicator'); - indicatorsElem = ensureIndicators(card, indicatorsElem); - indicatorsElem.appendChild(playedIndicator); - } - playedIndicator.innerHTML = ''; - } else { - playedIndicator = card.querySelector('.playedIndicator'); - if (playedIndicator) { - playedIndicator.parentNode.removeChild(playedIndicator); - } - } - if (userData.UnplayedItemCount) { - countIndicator = card.querySelector('.countIndicator'); - - if (!countIndicator) { - countIndicator = document.createElement('div'); - countIndicator.classList.add('countIndicator'); - indicatorsElem = ensureIndicators(card, indicatorsElem); - indicatorsElem.appendChild(countIndicator); - } - countIndicator.innerHTML = userData.UnplayedItemCount; - } else if (enableCountIndicator) { - countIndicator = card.querySelector('.countIndicator'); - if (countIndicator) { - countIndicator.parentNode.removeChild(countIndicator); - } - } - - const progressHtml = indicators.getProgressBarHtml({ - Type: type, - UserData: userData, - MediaType: 'Video' - }); - - if (progressHtml) { - itemProgressBar = card.querySelector('.itemProgressBar'); - - if (!itemProgressBar) { - itemProgressBar = document.createElement('div'); - itemProgressBar.classList.add('itemProgressBar'); - - let innerCardFooter = card.querySelector('.innerCardFooter'); - if (!innerCardFooter) { - innerCardFooter = document.createElement('div'); - innerCardFooter.classList.add('innerCardFooter'); - const cardImageContainer = card.querySelector('.cardImageContainer'); - cardImageContainer.appendChild(innerCardFooter); - } - innerCardFooter.appendChild(itemProgressBar); - } - - itemProgressBar.innerHTML = progressHtml; - } else { - itemProgressBar = card.querySelector('.itemProgressBar'); - if (itemProgressBar) { - itemProgressBar.parentNode.removeChild(itemProgressBar); - } + if (!playedIndicator) { + playedIndicator = document.createElement('div'); + playedIndicator.classList.add('playedIndicator'); + playedIndicator.classList.add('indicator'); + indicatorsElem = ensureIndicators(card, indicatorsElem); + indicatorsElem.appendChild(playedIndicator); + } + playedIndicator.innerHTML = ''; + } else { + playedIndicator = card.querySelector('.playedIndicator'); + if (playedIndicator) { + playedIndicator.parentNode.removeChild(playedIndicator); + } + } + if (userData.UnplayedItemCount) { + countIndicator = card.querySelector('.countIndicator'); + + if (!countIndicator) { + countIndicator = document.createElement('div'); + countIndicator.classList.add('countIndicator'); + indicatorsElem = ensureIndicators(card, indicatorsElem); + indicatorsElem.appendChild(countIndicator); + } + countIndicator.innerHTML = userData.UnplayedItemCount; + } else if (enableCountIndicator) { + countIndicator = card.querySelector('.countIndicator'); + if (countIndicator) { + countIndicator.parentNode.removeChild(countIndicator); + } + } + + const progressHtml = indicators.getProgressBarHtml({ + Type: type, + UserData: userData, + MediaType: 'Video' + }); + + if (progressHtml) { + itemProgressBar = card.querySelector('.itemProgressBar'); + + if (!itemProgressBar) { + itemProgressBar = document.createElement('div'); + itemProgressBar.classList.add('itemProgressBar'); + + let innerCardFooter = card.querySelector('.innerCardFooter'); + if (!innerCardFooter) { + innerCardFooter = document.createElement('div'); + innerCardFooter.classList.add('innerCardFooter'); + const cardImageContainer = card.querySelector('.cardImageContainer'); + cardImageContainer.appendChild(innerCardFooter); } + innerCardFooter.appendChild(itemProgressBar); } - /** + itemProgressBar.innerHTML = progressHtml; + } else { + itemProgressBar = card.querySelector('.itemProgressBar'); + if (itemProgressBar) { + itemProgressBar.parentNode.removeChild(itemProgressBar); + } + } +} + +/** * Handles when user data has changed. * @param {Object} userData - User data to apply to the card. * @param {HTMLElement} scope - DOM element to use as a scope when selecting cards. */ - export function onUserDataChanged(userData, scope) { - const cards = (scope || document.body).querySelectorAll('.card-withuserdata[data-id="' + userData.ItemId + '"]'); +export function onUserDataChanged(userData, scope) { + const cards = (scope || document.body).querySelectorAll('.card-withuserdata[data-id="' + userData.ItemId + '"]'); - for (let i = 0, length = cards.length; i < length; i++) { - updateUserData(cards[i], userData); - } - } + for (let i = 0, length = cards.length; i < length; i++) { + updateUserData(cards[i], userData); + } +} - /** +/** * Handles when a timer has been created. * @param {string} programId - ID of the program. * @param {string} newTimerId - ID of the new timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onTimerCreated(programId, newTimerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); +export function onTimerCreated(programId, newTimerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-id="' + programId + '"]'); - for (let i = 0, length = cells.length; i < length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (!icon) { - const indicatorsElem = ensureIndicators(cell); - indicatorsElem.insertAdjacentHTML('beforeend', ''); - } - cell.setAttribute('data-timerid', newTimerId); - } + for (let i = 0, length = cells.length; i < length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (!icon) { + const indicatorsElem = ensureIndicators(cell); + indicatorsElem.insertAdjacentHTML('beforeend', ''); } + cell.setAttribute('data-timerid', newTimerId); + } +} - /** +/** * Handles when a timer has been cancelled. * @param {string} timerId - ID of the cancelled timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onTimerCancelled(timerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); +export function onTimerCancelled(timerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-timerid="' + timerId + '"]'); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-timerid'); - } + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (icon) { + icon.parentNode.removeChild(icon); } + cell.removeAttribute('data-timerid'); + } +} - /** +/** * Handles when a series timer has been cancelled. * @param {string} cancelledTimerId - ID of the cancelled timer. * @param {HTMLElement} itemsContainer - DOM element of the itemsContainer. */ - export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) { - const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); +export function onSeriesTimerCancelled(cancelledTimerId, itemsContainer) { + const cells = itemsContainer.querySelectorAll('.card[data-seriestimerid="' + cancelledTimerId + '"]'); - for (let i = 0; i < cells.length; i++) { - const cell = cells[i]; - const icon = cell.querySelector('.timerIndicator'); - if (icon) { - icon.parentNode.removeChild(icon); - } - cell.removeAttribute('data-seriestimerid'); - } + for (let i = 0; i < cells.length; i++) { + const cell = cells[i]; + const icon = cell.querySelector('.timerIndicator'); + if (icon) { + icon.parentNode.removeChild(icon); } - -/* eslint-enable indent */ + cell.removeAttribute('data-seriestimerid'); + } +} export default { getCardsHtml: getCardsHtml, diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index 794e845f8f..4a359cd9f4 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -12,123 +11,121 @@ import layoutManager from '../layoutManager'; import browser from '../../scripts/browser'; import ServerConnections from '../ServerConnections'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - function buildChapterCardsHtml(item, chapters, options) { - // TODO move card creation code to Card component +function buildChapterCardsHtml(item, chapters, options) { + // TODO move card creation code to Card component - let className = 'card itemAction chapterCard'; + let className = 'card itemAction chapterCard'; - if (layoutManager.tv) { - className += ' show-focus'; + if (layoutManager.tv) { + className += ' show-focus'; - if (enableFocusTransform) { - className += ' show-animation'; - } + if (enableFocusTransform) { + className += ' show-animation'; } - - const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; - const videoStream = mediaStreams.filter(({ Type }) => { - return Type === 'Video'; - })[0] || {}; - - let shape = (options.backdropShape || 'backdrop'); - - if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) { - shape = (options.squareShape || 'square'); - } - - className += ` ${shape}Card`; - - if (options.block || options.rows) { - className += ' block'; - } - - let html = ''; - let itemsInRow = 0; - - const apiClient = ServerConnections.getApiClient(item.ServerId); - - for (let i = 0, length = chapters.length; i < length; i++) { - if (options.rows && itemsInRow === 0) { - html += '
'; - } - - const chapter = chapters[i]; - - html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); - itemsInRow++; - - if (options.rows && itemsInRow >= options.rows) { - itemsInRow = 0; - html += '
'; - } - } - - return html; } - function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) { - if (ImageTag) { - return apiClient.getScaledImageUrl(Id, { + const mediaStreams = ((item.MediaSources || [])[0] || {}).MediaStreams || []; + const videoStream = mediaStreams.filter(({ Type }) => { + return Type === 'Video'; + })[0] || {}; - maxWidth: maxWidth, - tag: ImageTag, - type: 'Chapter', - index - }); - } + let shape = (options.backdropShape || 'backdrop'); - return null; + if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) { + shape = (options.squareShape || 'square'); } - function buildChapterCard(item, apiClient, chapter, index, { width, coverImage }, className, shape) { - const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient); + className += ` ${shape}Card`; - let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; - if (coverImage) { - cardImageContainerClass += ' coveredImage'; - } - 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 += ''; - } - - let nameHtml = ''; - nameHtml += `
${escapeHtml(chapter.Name)}
`; - nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; - - const cardBoxCssClass = 'cardBox'; - const cardScalableClass = 'cardScalable'; - - return `
`; + if (options.block || options.rows) { + className += ' block'; } - export function buildChapterCards(item, chapters, options) { - if (options.parentContainer) { - // Abort if the container has been disposed - if (!document.body.contains(options.parentContainer)) { - return; - } + let html = ''; + let itemsInRow = 0; - if (chapters.length) { - options.parentContainer.classList.remove('hide'); - } else { - options.parentContainer.classList.add('hide'); - return; - } + const apiClient = ServerConnections.getApiClient(item.ServerId); + + for (let i = 0, length = chapters.length; i < length; i++) { + if (options.rows && itemsInRow === 0) { + html += '
'; } - const html = buildChapterCardsHtml(item, chapters, options); + const chapter = chapters[i]; - options.itemsContainer.innerHTML = html; + html += buildChapterCard(item, apiClient, chapter, i, options, className, shape); + itemsInRow++; - imageLoader.lazyChildren(options.itemsContainer); + if (options.rows && itemsInRow >= options.rows) { + itemsInRow = 0; + html += '
'; + } } -/* eslint-enable indent */ + return html; +} + +function getImgUrl({ Id }, { ImageTag }, index, maxWidth, apiClient) { + if (ImageTag) { + return apiClient.getScaledImageUrl(Id, { + + maxWidth: maxWidth, + tag: ImageTag, + type: 'Chapter', + index + }); + } + + return null; +} + +function buildChapterCard(item, apiClient, chapter, index, { width, coverImage }, className, shape) { + const imgUrl = getImgUrl(item, chapter, index, width || 400, apiClient); + + let cardImageContainerClass = 'cardContent cardContent-shadow cardImageContainer chapterCardImageContainer'; + if (coverImage) { + cardImageContainerClass += ' coveredImage'; + } + 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 += ''; + } + + let nameHtml = ''; + nameHtml += `
${escapeHtml(chapter.Name)}
`; + nameHtml += `
${datetime.getDisplayRunningTime(chapter.StartPositionTicks)}
`; + + const cardBoxCssClass = 'cardBox'; + const cardScalableClass = 'cardScalable'; + + return `
`; +} + +export function buildChapterCards(item, chapters, options) { + if (options.parentContainer) { + // Abort if the container has been disposed + if (!document.body.contains(options.parentContainer)) { + return; + } + + if (chapters.length) { + options.parentContainer.classList.remove('hide'); + } else { + options.parentContainer.classList.add('hide'); + return; + } + } + + const html = buildChapterCardsHtml(item, chapters, options); + + options.itemsContainer.innerHTML = html; + + imageLoader.lazyChildren(options.itemsContainer); +} export default { buildChapterCards: buildChapterCards diff --git a/src/components/cardbuilder/peoplecardbuilder.js b/src/components/cardbuilder/peoplecardbuilder.js index 00b8f0fb89..a991953704 100644 --- a/src/components/cardbuilder/peoplecardbuilder.js +++ b/src/components/cardbuilder/peoplecardbuilder.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for building cards from item data. @@ -7,20 +6,18 @@ import cardBuilder from './cardBuilder'; - export function buildPeopleCards(items, options) { - options = Object.assign(options || {}, { - cardLayout: false, - centerText: true, - showTitle: true, - cardFooterAside: 'none', - showPersonRoleOrType: true, - cardCssClass: 'personCard', - defaultCardImageIcon: 'person' - }); - cardBuilder.buildCards(items, options); - } - - /* eslint-enable indent */ +export function buildPeopleCards(items, options) { + options = Object.assign(options || {}, { + cardLayout: false, + centerText: true, + showTitle: true, + cardFooterAside: 'none', + showPersonRoleOrType: true, + cardCssClass: 'personCard', + defaultCardImageIcon: 'person' + }); + cardBuilder.buildCards(items, options); +} export default { buildPeopleCards: buildPeopleCards diff --git a/src/components/collectionEditor/collectionEditor.js b/src/components/collectionEditor/collectionEditor.js index 89862725a5..efcd7f86e7 100644 --- a/src/components/collectionEditor/collectionEditor.js +++ b/src/components/collectionEditor/collectionEditor.js @@ -16,254 +16,251 @@ import '../../styles/flexstyles.scss'; import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; -/* eslint-disable indent */ +let currentServerId; - let currentServerId; +function onSubmit(e) { + loading.show(); - function onSubmit(e) { - loading.show(); + const panel = dom.parentWithClass(this, 'dialog'); - const panel = dom.parentWithClass(this, 'dialog'); + const collectionId = panel.querySelector('#selectCollectionToAddTo').value; - const collectionId = panel.querySelector('#selectCollectionToAddTo').value; + const apiClient = ServerConnections.getApiClient(currentServerId); - const apiClient = ServerConnections.getApiClient(currentServerId); - - if (collectionId) { - addToCollection(apiClient, panel, collectionId); - } else { - createCollection(apiClient, panel); - } - - e.preventDefault(); - return false; + if (collectionId) { + addToCollection(apiClient, panel, collectionId); + } else { + createCollection(apiClient, panel); } - function createCollection(apiClient, dlg) { - const url = apiClient.getUrl('Collections', { + e.preventDefault(); + return false; +} - Name: dlg.querySelector('#txtNewCollectionName').value, - IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, - Ids: dlg.querySelector('.fldSelectedItemIds').value || '' - }); +function createCollection(apiClient, dlg) { + const url = apiClient.getUrl('Collections', { - apiClient.ajax({ - type: 'POST', - url: url, - dataType: 'json' + Name: dlg.querySelector('#txtNewCollectionName').value, + IsLocked: !dlg.querySelector('#chkEnableInternetMetadata').checked, + Ids: dlg.querySelector('.fldSelectedItemIds').value || '' + }); - }).then(result => { - loading.hide(); + apiClient.ajax({ + type: 'POST', + url: url, + dataType: 'json' - const id = result.Id; + }).then(result => { + loading.hide(); - dlg.submitted = true; - dialogHelper.close(dlg); - redirectToCollection(apiClient, id); - }); - } + const id = result.Id; - function redirectToCollection(apiClient, id) { - appRouter.showItem(id, apiClient.serverId()); - } + dlg.submitted = true; + dialogHelper.close(dlg); + redirectToCollection(apiClient, id); + }); +} - function addToCollection(apiClient, dlg, id) { - const url = apiClient.getUrl(`Collections/${id}/Items`, { +function redirectToCollection(apiClient, id) { + appRouter.showItem(id, apiClient.serverId()); +} - Ids: dlg.querySelector('.fldSelectedItemIds').value || '' - }); +function addToCollection(apiClient, dlg, id) { + const url = apiClient.getUrl(`Collections/${id}/Items`, { - apiClient.ajax({ - type: 'POST', - url: url + Ids: dlg.querySelector('.fldSelectedItemIds').value || '' + }); - }).then(() => { - loading.hide(); + apiClient.ajax({ + type: 'POST', + url: url - dlg.submitted = true; - dialogHelper.close(dlg); + }).then(() => { + loading.hide(); - toast(globalize.translate('MessageItemsAdded')); - }); - } + dlg.submitted = true; + dialogHelper.close(dlg); - function triggerChange(select) { - select.dispatchEvent(new CustomEvent('change', {})); - } + toast(globalize.translate('MessageItemsAdded')); + }); +} - function populateCollections(panel) { - loading.show(); +function triggerChange(select) { + select.dispatchEvent(new CustomEvent('change', {})); +} - const select = panel.querySelector('#selectCollectionToAddTo'); +function populateCollections(panel) { + loading.show(); - panel.querySelector('.newCollectionInfo').classList.add('hide'); + const select = panel.querySelector('#selectCollectionToAddTo'); - const options = { + panel.querySelector('.newCollectionInfo').classList.add('hide'); - Recursive: true, - IncludeItemTypes: 'BoxSet', - SortBy: 'SortName', - EnableTotalRecordCount: false - }; + const options = { - const apiClient = ServerConnections.getApiClient(currentServerId); - apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { - let html = ''; + Recursive: true, + IncludeItemTypes: 'BoxSet', + SortBy: 'SortName', + EnableTotalRecordCount: false + }; - html += ``; - - html += result.Items.map(i => { - return ``; - }); - - select.innerHTML = html; - select.value = ''; - triggerChange(select); - - loading.hide(); - }); - } - - function getEditorHtml() { + const apiClient = ServerConnections.getApiClient(currentServerId); + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; - html += '
'; - html += '
'; - html += '
'; + html += ``; - html += '
'; - html += globalize.translate('NewCollectionHelp'); - html += '
'; - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += ``; - html += '
'; - html += '
'; - - html += '
'; - - html += '
'; - html += ``; - html += `
${globalize.translate('NewCollectionNameExample')}
`; - html += '
'; - - html += ''; - - // newCollectionInfo - html += '
'; - - html += '
'; - html += ``; - html += '
'; - - html += ''; - - html += '
'; - html += '
'; - html += '
'; - - return html; - } - - function initEditor(content, items) { - content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () { - if (this.value) { - content.querySelector('.newCollectionInfo').classList.add('hide'); - content.querySelector('#txtNewCollectionName').removeAttribute('required'); - } else { - content.querySelector('.newCollectionInfo').classList.remove('hide'); - content.querySelector('#txtNewCollectionName').setAttribute('required', 'required'); - } + html += result.Items.map(i => { + return ``; }); - content.querySelector('form').addEventListener('submit', onSubmit); + select.innerHTML = html; + select.value = ''; + triggerChange(select); - content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + loading.hide(); + }); +} - if (items.length) { - content.querySelector('.fldSelectCollection').classList.remove('hide'); - populateCollections(content); +function getEditorHtml() { + let html = ''; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + html += globalize.translate('NewCollectionHelp'); + html += '
'; + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ``; + html += '
'; + html += '
'; + + html += '
'; + + html += '
'; + html += ``; + html += `
${globalize.translate('NewCollectionNameExample')}
`; + html += '
'; + + html += ''; + + // newCollectionInfo + html += '
'; + + html += '
'; + html += ``; + html += '
'; + + html += ''; + + html += '
'; + html += '
'; + html += '
'; + + return html; +} + +function initEditor(content, items) { + content.querySelector('#selectCollectionToAddTo').addEventListener('change', function () { + if (this.value) { + content.querySelector('.newCollectionInfo').classList.add('hide'); + content.querySelector('#txtNewCollectionName').removeAttribute('required'); } else { - content.querySelector('.fldSelectCollection').classList.add('hide'); - - const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo'); - selectCollectionToAddTo.innerHTML = ''; - selectCollectionToAddTo.value = ''; - triggerChange(selectCollectionToAddTo); + content.querySelector('.newCollectionInfo').classList.remove('hide'); + content.querySelector('#txtNewCollectionName').setAttribute('required', 'required'); } - } + }); - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); + content.querySelector('form').addEventListener('submit', onSubmit); + + content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + + if (items.length) { + content.querySelector('.fldSelectCollection').classList.remove('hide'); + populateCollections(content); + } else { + content.querySelector('.fldSelectCollection').classList.add('hide'); + + const selectCollectionToAddTo = content.querySelector('#selectCollectionToAddTo'); + selectCollectionToAddTo.innerHTML = ''; + selectCollectionToAddTo.value = ''; + triggerChange(selectCollectionToAddTo); + } +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +class CollectionEditor { + show(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 = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); + + html += '
'; + html += ``; + html += '

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

'; + + html += '
'; + + 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(); }); } +} - class CollectionEditor { - show(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 = items.length ? globalize.translate('HeaderAddToCollection') : globalize.translate('NewCollection'); - - html += '
'; - html += ``; - html += '

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

'; - - html += '
'; - - 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 CollectionEditor; diff --git a/src/components/dialog/dialog.js b/src/components/dialog/dialog.js index 85e81abb02..3e285514e1 100644 --- a/src/components/dialog/dialog.js +++ b/src/components/dialog/dialog.js @@ -13,129 +13,126 @@ import '../formdialog.scss'; import '../../styles/flexstyles.scss'; import template from './dialog.template.html'; -/* eslint-disable indent */ +function showDialog(options = { dialogOptions: {}, buttons: [] }) { + const dialogOptions = { + removeOnClose: true, + scrollY: false, + ...options.dialogOptions + }; - function showDialog(options = { dialogOptions: {}, buttons: [] }) { - const dialogOptions = { - removeOnClose: true, - scrollY: false, - ...options.dialogOptions - }; + const enableTvLayout = layoutManager.tv; - const enableTvLayout = layoutManager.tv; + if (enableTvLayout) { + dialogOptions.size = 'fullscreen'; + } - if (enableTvLayout) { - dialogOptions.size = 'fullscreen'; + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + dlg.classList.add('align-items-center'); + dlg.classList.add('justify-content-center'); + const formDialogContent = dlg.querySelector('.formDialogContent'); + formDialogContent.classList.add('no-grow'); + + if (enableTvLayout) { + formDialogContent.style['max-width'] = '50%'; + 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`; + dlg.classList.add('dialog-fullscreen-lowres'); + } + + if (options.title) { + dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || ''; + } else { + dlg.querySelector('.formDialogHeaderTitle').classList.add('hide'); + } + + const displayText = options.html || options.text || ''; + dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText); + + if (!displayText) { + dlg.querySelector('.dialogContentInner').classList.add('hide'); + } + + let i; + let length; + let html = ''; + let hasDescriptions = false; + + for (i = 0, length = options.buttons.length; i < length; i++) { + const item = options.buttons[i]; + const autoFocus = i === 0 ? ' autofocus' : ''; + + let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize'; + + if (item.type) { + buttonClass += ` button-${item.type}`; } - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - dlg.classList.add('align-items-center'); - dlg.classList.add('justify-content-center'); - const formDialogContent = dlg.querySelector('.formDialogContent'); - formDialogContent.classList.add('no-grow'); - - if (enableTvLayout) { - formDialogContent.style['max-width'] = '50%'; - 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`; - dlg.classList.add('dialog-fullscreen-lowres'); + if (item.description) { + hasDescriptions = true; } - if (options.title) { - dlg.querySelector('.formDialogHeaderTitle').innerText = options.title || ''; - } else { - dlg.querySelector('.formDialogHeaderTitle').classList.add('hide'); - } - - const displayText = options.html || options.text || ''; - dlg.querySelector('.text').innerHTML = DOMPurify.sanitize(displayText); - - if (!displayText) { - dlg.querySelector('.dialogContentInner').classList.add('hide'); - } - - let i; - let length; - let html = ''; - let hasDescriptions = false; - - for (i = 0, length = options.buttons.length; i < length; i++) { - const item = options.buttons[i]; - const autoFocus = i === 0 ? ' autofocus' : ''; - - let buttonClass = 'btnOption raised formDialogFooterItem formDialogFooterItem-autosize'; - - if (item.type) { - buttonClass += ` button-${item.type}`; - } - - if (item.description) { - hasDescriptions = true; - } - - if (hasDescriptions) { - buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom'; - } - - html += ``; - - if (item.description) { - html += `
${item.description}
`; - } - } - - dlg.querySelector('.formDialogFooter').innerHTML = html; - if (hasDescriptions) { - dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); + buttonClass += ' formDialogFooterItem-vertical formDialogFooterItem-nomarginbottom'; } - let dialogResult; - function onButtonClick() { - dialogResult = this.getAttribute('data-id'); - dialogHelper.close(dlg); + html += ``; + + if (item.description) { + html += `
${item.description}
`; } - - const buttons = dlg.querySelectorAll('.btnOption'); - for (i = 0, length = buttons.length; i < length; i++) { - buttons[i].addEventListener('click', onButtonClick); - } - - return dialogHelper.open(dlg).then(() => { - if (enableTvLayout) { - scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); - } - - if (dialogResult) { - return dialogResult; - } else { - return Promise.reject(); - } - }); } - export function show(text, title) { - let options; - if (typeof text === 'string') { - options = { - title: title, - text: text - }; + dlg.querySelector('.formDialogFooter').innerHTML = html; + + if (hasDescriptions) { + dlg.querySelector('.formDialogFooter').classList.add('formDialogFooter-vertical'); + } + + let dialogResult; + function onButtonClick() { + dialogResult = this.getAttribute('data-id'); + dialogHelper.close(dlg); + } + + const buttons = dlg.querySelectorAll('.btnOption'); + for (i = 0, length = buttons.length; i < length; i++) { + buttons[i].addEventListener('click', onButtonClick); + } + + return dialogHelper.open(dlg).then(() => { + if (enableTvLayout) { + scrollHelper.centerFocus.off(dlg.querySelector('.formDialogContent'), false); + } + + if (dialogResult) { + return dialogResult; } else { - options = text; + return Promise.reject(); } + }); +} - return showDialog(options); +export function show(text, title) { + let options; + if (typeof text === 'string') { + options = { + title: title, + text: text + }; + } else { + options = text; } -/* eslint-enable indent */ + return showDialog(options); +} + export default { show: show }; diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index 6811525465..5819f0a755 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -9,506 +9,501 @@ import dom from '../../scripts/dom'; import './dialoghelper.scss'; import '../../styles/scrollstyles.scss'; -/* eslint-disable indent */ +let globalOnOpenCallback; - let globalOnOpenCallback; - - function enableAnimation() { - // too slow - if (browser.tv) { - return false; - } - - return browser.supportsCssAnimation(); +function enableAnimation() { + // too slow + if (browser.tv) { + return false; } - function removeCenterFocus(dlg) { - if (layoutManager.tv) { - if (dlg.classList.contains('scrollX')) { - centerFocus(dlg, true, false); - } else if (dlg.classList.contains('smoothScrollY')) { - centerFocus(dlg, false, false); - } + return browser.supportsCssAnimation(); +} + +function removeCenterFocus(dlg) { + if (layoutManager.tv) { + if (dlg.classList.contains('scrollX')) { + centerFocus(dlg, true, false); + } else if (dlg.classList.contains('smoothScrollY')) { + centerFocus(dlg, false, false); + } + } +} + +function tryRemoveElement(elem) { + const parentNode = elem.parentNode; + if (parentNode) { + // Seeing crashes in edge webview + try { + parentNode.removeChild(elem); + } catch (err) { + console.error('[dialogHelper] error removing dialog element: ' + err); + } + } +} + +function DialogHashHandler(dlg, hash, resolve) { + const self = this; + self.originalUrl = window.location.href; + const activeElement = document.activeElement; + let removeScrollLockOnClose = false; + let unlisten; + + function onHashChange({ location }) { + const dialogs = location.state?.dialogs || []; + const shouldClose = !dialogs.includes(hash); + + if ((shouldClose || !isOpened(dlg)) && unlisten) { + unlisten(); + unlisten = null; + } + + if (shouldClose) { + close(dlg); } } - function tryRemoveElement(elem) { - const parentNode = elem.parentNode; - if (parentNode) { - // Seeing crashes in edge webview - try { - parentNode.removeChild(elem); - } catch (err) { - console.error('[dialogHelper] error removing dialog element: ' + err); - } - } - } - - function DialogHashHandler(dlg, hash, resolve) { - const self = this; - self.originalUrl = window.location.href; - const activeElement = document.activeElement; - let removeScrollLockOnClose = false; - let unlisten; - - function onHashChange({ location }) { - const dialogs = location.state?.dialogs || []; - const shouldClose = !dialogs.includes(hash); - - if ((shouldClose || !isOpened(dlg)) && unlisten) { - unlisten(); - unlisten = null; - } - - if (shouldClose) { - close(dlg); - } + function finishClose() { + if (unlisten) { + unlisten(); + unlisten = null; } - function finishClose() { - if (unlisten) { - unlisten(); - unlisten = null; - } - - dlg.dispatchEvent(new CustomEvent('close', { - bubbles: false, - cancelable: false - })); - - resolve({ - element: dlg - }); - } - - function onBackCommand(e) { - if (e.detail.command === 'back') { - e.preventDefault(); - e.stopPropagation(); - close(dlg); - } - } - - function onDialogClosed() { - if (!isHistoryEnabled(dlg)) { - inputManager.off(dlg, onBackCommand); - } - - if (unlisten) { - unlisten(); - unlisten = null; - } - - removeBackdrop(dlg); - dlg.classList.remove('opened'); - - if (removeScrollLockOnClose) { - document.body.classList.remove('noScroll'); - } - - if (isHistoryEnabled(dlg)) { - const state = history.location.state || {}; - if (state.dialogs?.length > 0) { - if (state.dialogs[state.dialogs.length - 1] === hash) { - unlisten = history.listen(finishClose); - history.back(); - } else if (state.dialogs.includes(hash)) { - console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash); - - unlisten = history.listen(finishClose); - - // Remove the closed dialog hash from the history state - history.replace( - `${history.location.pathname}${history.location.search}`, - { - ...state, - dialogs: state.dialogs.filter(dialog => dialog !== hash) - } - ); - } - } - } - - if (layoutManager.tv) { - focusManager.focus(activeElement); - } - - if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) { - removeCenterFocus(dlg); - - const dialogContainer = dlg.dialogContainer; - if (dialogContainer) { - tryRemoveElement(dialogContainer); - dlg.dialogContainer = null; - } else { - tryRemoveElement(dlg); - } - } - - if (!unlisten) { - finishClose(); - } - } - - dlg.addEventListener('_close', onDialogClosed); - - const center = !dlg.classList.contains('dialog-fixedSize'); - if (center) { - dlg.classList.add('centeredDialog'); - } - - dlg.classList.remove('hide'); - - addBackdropOverlay(dlg); - - dlg.classList.add('opened'); - dlg.dispatchEvent(new CustomEvent('open', { + dlg.dispatchEvent(new CustomEvent('close', { bubbles: false, cancelable: false })); - if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) { - document.body.classList.add('noScroll'); - removeScrollLockOnClose = true; + resolve({ + element: dlg + }); + } + + function onBackCommand(e) { + if (e.detail.command === 'back') { + e.preventDefault(); + e.stopPropagation(); + close(dlg); + } + } + + function onDialogClosed() { + if (!isHistoryEnabled(dlg)) { + inputManager.off(dlg, onBackCommand); } - animateDialogOpen(dlg); + if (unlisten) { + unlisten(); + unlisten = null; + } + + removeBackdrop(dlg); + dlg.classList.remove('opened'); + + if (removeScrollLockOnClose) { + document.body.classList.remove('noScroll'); + } if (isHistoryEnabled(dlg)) { const state = history.location.state || {}; - const dialogs = state.dialogs || []; - // Add new dialog to the list of open dialogs - dialogs.push(hash); + if (state.dialogs?.length > 0) { + if (state.dialogs[state.dialogs.length - 1] === hash) { + unlisten = history.listen(finishClose); + history.back(); + } else if (state.dialogs.includes(hash)) { + console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash); - history.push( - `${history.location.pathname}${history.location.search}`, - { - ...state, - dialogs + unlisten = history.listen(finishClose); + + // Remove the closed dialog hash from the history state + history.replace( + `${history.location.pathname}${history.location.search}`, + { + ...state, + dialogs: state.dialogs.filter(dialog => dialog !== hash) + } + ); } - ); - - unlisten = history.listen(onHashChange); - } else { - inputManager.on(dlg, onBackCommand); - } - } - - function addBackdropOverlay(dlg) { - const backdrop = document.createElement('div'); - backdrop.classList.add('dialogBackdrop'); - - const backdropParent = dlg.dialogContainer || dlg; - backdropParent.parentNode.insertBefore(backdrop, backdropParent); - dlg.backdrop = backdrop; - - // trigger reflow or the backdrop will not animate - void backdrop.offsetWidth; - backdrop.classList.add('dialogBackdropOpened'); - - dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => { - if (e.target === dlg.dialogContainer) { - close(dlg); } - }, { - passive: true - }); + } - dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => { - if (e.target === dlg.dialogContainer) { - // Close the application dialog menu - close(dlg); - // Prevent the default browser context menu from appearing - e.preventDefault(); + if (layoutManager.tv) { + focusManager.focus(activeElement); + } + + if (toBoolean(dlg.getAttribute('data-removeonclose'), true)) { + removeCenterFocus(dlg); + + const dialogContainer = dlg.dialogContainer; + if (dialogContainer) { + tryRemoveElement(dialogContainer); + dlg.dialogContainer = null; + } else { + tryRemoveElement(dlg); } - }); - } - - function isHistoryEnabled(dlg) { - return dlg.getAttribute('data-history') === 'true'; - } - - export function open(dlg) { - if (globalOnOpenCallback) { - globalOnOpenCallback(dlg); } - const parent = dlg.parentNode; - if (parent) { - parent.removeChild(dlg); + if (!unlisten) { + finishClose(); } - - const dialogContainer = document.createElement('div'); - dialogContainer.classList.add('dialogContainer'); - dialogContainer.appendChild(dlg); - dlg.dialogContainer = dialogContainer; - document.body.appendChild(dialogContainer); - - return new Promise((resolve) => { - new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve); - }); } - function isOpened(dlg) { - //return dlg.opened; - return !dlg.classList.contains('hide'); + dlg.addEventListener('_close', onDialogClosed); + + const center = !dlg.classList.contains('dialog-fixedSize'); + if (center) { + dlg.classList.add('centeredDialog'); } - export function close(dlg) { - if (!dlg.classList.contains('hide')) { - dlg.dispatchEvent(new CustomEvent('closing', { + dlg.classList.remove('hide'); + + addBackdropOverlay(dlg); + + dlg.classList.add('opened'); + dlg.dispatchEvent(new CustomEvent('open', { + bubbles: false, + cancelable: false + })); + + if (dlg.getAttribute('data-lockscroll') === 'true' && !document.body.classList.contains('noScroll')) { + document.body.classList.add('noScroll'); + removeScrollLockOnClose = true; + } + + animateDialogOpen(dlg); + + if (isHistoryEnabled(dlg)) { + const state = history.location.state || {}; + const dialogs = state.dialogs || []; + // Add new dialog to the list of open dialogs + dialogs.push(hash); + + history.push( + `${history.location.pathname}${history.location.search}`, + { + ...state, + dialogs + } + ); + + unlisten = history.listen(onHashChange); + } else { + inputManager.on(dlg, onBackCommand); + } +} + +function addBackdropOverlay(dlg) { + const backdrop = document.createElement('div'); + backdrop.classList.add('dialogBackdrop'); + + const backdropParent = dlg.dialogContainer || dlg; + backdropParent.parentNode.insertBefore(backdrop, backdropParent); + dlg.backdrop = backdrop; + + // trigger reflow or the backdrop will not animate + void backdrop.offsetWidth; + backdrop.classList.add('dialogBackdropOpened'); + + dom.addEventListener((dlg.dialogContainer || backdrop), 'click', e => { + if (e.target === dlg.dialogContainer) { + close(dlg); + } + }, { + passive: true + }); + + dom.addEventListener((dlg.dialogContainer || backdrop), 'contextmenu', e => { + if (e.target === dlg.dialogContainer) { + // Close the application dialog menu + close(dlg); + // Prevent the default browser context menu from appearing + e.preventDefault(); + } + }); +} + +function isHistoryEnabled(dlg) { + return dlg.getAttribute('data-history') === 'true'; +} + +export function open(dlg) { + if (globalOnOpenCallback) { + globalOnOpenCallback(dlg); + } + + const parent = dlg.parentNode; + if (parent) { + parent.removeChild(dlg); + } + + const dialogContainer = document.createElement('div'); + dialogContainer.classList.add('dialogContainer'); + dialogContainer.appendChild(dlg); + dlg.dialogContainer = dialogContainer; + document.body.appendChild(dialogContainer); + + return new Promise((resolve) => { + new DialogHashHandler(dlg, `dlg${new Date().getTime()}`, resolve); + }); +} + +function isOpened(dlg) { + return !dlg.classList.contains('hide'); +} + +export function close(dlg) { + if (!dlg.classList.contains('hide')) { + dlg.dispatchEvent(new CustomEvent('closing', { + bubbles: false, + cancelable: false + })); + + const onAnimationFinish = () => { + focusManager.popScope(dlg); + + dlg.classList.add('hide'); + dlg.dispatchEvent(new CustomEvent('_close', { bubbles: false, cancelable: false })); + }; - const onAnimationFinish = () => { - focusManager.popScope(dlg); - - dlg.classList.add('hide'); - dlg.dispatchEvent(new CustomEvent('_close', { - bubbles: false, - cancelable: false - })); - }; - - animateDialogClose(dlg, onAnimationFinish); - } + animateDialogClose(dlg, onAnimationFinish); } +} - const getAnimationEndHandler = (dlg, callback) => function handler() { - dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); - callback(); +const getAnimationEndHandler = (dlg, callback) => function handler() { + dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); + callback(); +}; + +function animateDialogOpen(dlg) { + const onAnimationFinish = () => { + focusManager.pushScope(dlg); + + if (dlg.getAttribute('data-autofocus') === 'true') { + focusManager.autoFocus(dlg); + } + + if (document.activeElement && !dlg.contains(document.activeElement)) { + // Blur foreign element to prevent triggering of an action from the previous scope + document.activeElement.blur(); + } }; - function animateDialogOpen(dlg) { - const onAnimationFinish = () => { - focusManager.pushScope(dlg); + if (enableAnimation()) { + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); - if (dlg.getAttribute('data-autofocus') === 'true') { - focusManager.autoFocus(dlg); - } + return; + } - if (document.activeElement && !dlg.contains(document.activeElement)) { - // Blur foreign element to prevent triggering of an action from the previous scope - document.activeElement.blur(); - } - }; + onAnimationFinish(); +} - if (enableAnimation()) { - dom.addEventListener( - dlg, - dom.whichAnimationEvent(), - getAnimationEndHandler(dlg, onAnimationFinish), - { once: true }); +function animateDialogClose(dlg, onAnimationFinish) { + if (enableAnimation()) { + let animated = true; + switch (dlg.animationConfig.exit.name) { + case 'fadeout': + 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`; + break; + case 'slidedown': + dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`; + break; + default: + animated = false; + break; + } + + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); + + if (animated) { return; } - - onAnimationFinish(); } - function animateDialogClose(dlg, onAnimationFinish) { - if (enableAnimation()) { - let animated = true; + onAnimationFinish(); +} - switch (dlg.animationConfig.exit.name) { - case 'fadeout': - 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`; - break; - case 'slidedown': - dlg.style.animation = `slidedown ${dlg.animationConfig.exit.timing.duration}ms ease-out normal both`; - break; - default: - animated = false; - break; +const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style; + +function shouldLockDocumentScroll(options) { + if (options.lockScroll != null) { + return options.lockScroll; + } + + if (options.size === 'fullscreen') { + return true; + } + + if (supportsOverscrollBehavior && (options.size || !browser.touch)) { + return false; + } + + if (options.size) { + return true; + } + + return browser.touch; +} + +function removeBackdrop(dlg) { + const backdrop = dlg.backdrop; + + if (!backdrop) { + return; + } + + dlg.backdrop = null; + + const onAnimationFinish = () => { + tryRemoveElement(backdrop); + }; + + if (enableAnimation()) { + backdrop.classList.remove('dialogBackdropOpened'); + + // this is not firing animationend + setTimeout(onAnimationFinish, 300); + return; + } + + onAnimationFinish(); +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +export function createDialog(options = {}) { + // If there's no native dialog support, use a plain div + // Also not working well in samsung tizen browser, content inside not clickable + // Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog + const dlg = document.createElement('div'); + + // Add an id so we can access the dialog element + if (options.id) { + dlg.id = options.id; + } + + dlg.classList.add('focuscontainer'); + dlg.classList.add('hide'); + + if (shouldLockDocumentScroll(options)) { + dlg.setAttribute('data-lockscroll', 'true'); + } + + if (options.enableHistory !== false) { + dlg.setAttribute('data-history', 'true'); + } + + // without this safari will scroll the background instead of the dialog contents + // but not needed here since this is already on top of an existing dialog + // but skip it in IE because it's causing the entire browser to hang + // Also have to disable for firefox because it's causing select elements to not be clickable + if (options.modal !== false) { + dlg.setAttribute('modal', 'modal'); + } + + if (options.autoFocus !== false) { + dlg.setAttribute('data-autofocus', 'true'); + } + + const defaultEntryAnimation = 'scaleup'; + const defaultExitAnimation = 'scaledown'; + const entryAnimation = options.entryAnimation || defaultEntryAnimation; + const exitAnimation = options.exitAnimation || defaultExitAnimation; + + // If it's not fullscreen then lower the default animation speed to make it open really fast + const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280); + const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220); + + dlg.animationConfig = { + // scale up + 'entry': { + name: entryAnimation, + timing: { + duration: entryAnimationDuration, + easing: 'ease-out' } - - dom.addEventListener( - dlg, - dom.whichAnimationEvent(), - getAnimationEndHandler(dlg, onAnimationFinish), - { once: true }); - - if (animated) { - return; + }, + // fade out + 'exit': { + name: exitAnimation, + timing: { + duration: exitAnimationDuration, + easing: 'ease-out', + fill: 'both' } } + }; - onAnimationFinish(); + dlg.classList.add('dialog'); + + if (options.scrollX) { + dlg.classList.add('scrollX'); + dlg.classList.add('smoothScrollX'); + + if (layoutManager.tv) { + centerFocus(dlg, true, true); + } + } else if (options.scrollY !== false) { + dlg.classList.add('smoothScrollY'); + + if (layoutManager.tv) { + centerFocus(dlg, false, true); + } } - const supportsOverscrollBehavior = 'overscroll-behavior-y' in document.body.style; - - function shouldLockDocumentScroll(options) { - if (options.lockScroll != null) { - return options.lockScroll; - } - - if (options.size === 'fullscreen') { - return true; - } - - if (supportsOverscrollBehavior && (options.size || !browser.touch)) { - return false; - } - - if (options.size) { - return true; - } - - return browser.touch; + if (options.removeOnClose) { + dlg.setAttribute('data-removeonclose', 'true'); } - function removeBackdrop(dlg) { - const backdrop = dlg.backdrop; - - if (!backdrop) { - return; - } - - dlg.backdrop = null; - - const onAnimationFinish = () => { - tryRemoveElement(backdrop); - }; - - if (enableAnimation()) { - backdrop.classList.remove('dialogBackdropOpened'); - - // this is not firing animationend - setTimeout(onAnimationFinish, 300); - return; - } - - onAnimationFinish(); + if (options.size) { + dlg.classList.add('dialog-fixedSize'); + dlg.classList.add(`dialog-${options.size}`); } - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); + if (enableAnimation()) { + switch (dlg.animationConfig.entry.name) { + case 'fadein': + dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`; + break; + case 'scaleup': + dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`; + break; + case 'slideup': + dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`; + break; + case 'slidedown': + dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`; + break; + default: + break; + } } - export function createDialog(options = {}) { - // If there's no native dialog support, use a plain div - // Also not working well in samsung tizen browser, content inside not clickable - // Just go ahead and always use a plain div because we're seeing issues overlaying absoltutely positioned content over a modal dialog - const dlg = document.createElement('div'); + return dlg; +} - // Add an id so we can access the dialog element - if (options.id) { - dlg.id = options.id; - } - - dlg.classList.add('focuscontainer'); - dlg.classList.add('hide'); - - if (shouldLockDocumentScroll(options)) { - dlg.setAttribute('data-lockscroll', 'true'); - } - - if (options.enableHistory !== false) { - dlg.setAttribute('data-history', 'true'); - } - - // without this safari will scroll the background instead of the dialog contents - // but not needed here since this is already on top of an existing dialog - // but skip it in IE because it's causing the entire browser to hang - // Also have to disable for firefox because it's causing select elements to not be clickable - if (options.modal !== false) { - dlg.setAttribute('modal', 'modal'); - } - - if (options.autoFocus !== false) { - dlg.setAttribute('data-autofocus', 'true'); - } - - const defaultEntryAnimation = 'scaleup'; - const defaultExitAnimation = 'scaledown'; - const entryAnimation = options.entryAnimation || defaultEntryAnimation; - const exitAnimation = options.exitAnimation || defaultExitAnimation; - - // If it's not fullscreen then lower the default animation speed to make it open really fast - const entryAnimationDuration = options.entryAnimationDuration || (options.size !== 'fullscreen' ? 180 : 280); - const exitAnimationDuration = options.exitAnimationDuration || (options.size !== 'fullscreen' ? 120 : 220); - - dlg.animationConfig = { - // scale up - 'entry': { - name: entryAnimation, - timing: { - duration: entryAnimationDuration, - easing: 'ease-out' - } - }, - // fade out - 'exit': { - name: exitAnimation, - timing: { - duration: exitAnimationDuration, - easing: 'ease-out', - fill: 'both' - } - } - }; - - dlg.classList.add('dialog'); - - if (options.scrollX) { - dlg.classList.add('scrollX'); - dlg.classList.add('smoothScrollX'); - - if (layoutManager.tv) { - centerFocus(dlg, true, true); - } - } else if (options.scrollY !== false) { - dlg.classList.add('smoothScrollY'); - - if (layoutManager.tv) { - centerFocus(dlg, false, true); - } - } - - if (options.removeOnClose) { - dlg.setAttribute('data-removeonclose', 'true'); - } - - if (options.size) { - dlg.classList.add('dialog-fixedSize'); - dlg.classList.add(`dialog-${options.size}`); - } - - if (enableAnimation()) { - switch (dlg.animationConfig.entry.name) { - case 'fadein': - dlg.style.animation = `fadein ${entryAnimationDuration}ms ease-out normal`; - break; - case 'scaleup': - dlg.style.animation = `scaleup ${entryAnimationDuration}ms ease-out normal both`; - break; - case 'slideup': - dlg.style.animation = `slideup ${entryAnimationDuration}ms ease-out normal`; - break; - case 'slidedown': - dlg.style.animation = `slidedown ${entryAnimationDuration}ms ease-out normal`; - break; - default: - break; - } - } - - return dlg; - } - - export function setOnOpen(val) { - globalOnOpenCallback = val; - } - -/* eslint-enable indent */ +export function setOnOpen(val) { + globalOnOpenCallback = val; +} export default { open: open, diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index 195c919e9b..c0ede8aec5 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -18,238 +18,235 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './displaySettings.template.html'; -/* eslint-disable indent */ - - function fillThemes(select, selectedTheme) { - skinManager.getThemes().then(themes => { - select.innerHTML = themes.map(t => { - return ``; - }).join(''); - - // get default theme - const defaultTheme = themes.find(theme => theme.default); - - // set the current theme - select.value = selectedTheme || defaultTheme.id; - }); - } - - function loadScreensavers(context, userSettings) { - const selectScreensaver = context.querySelector('.selectScreensaver'); - const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { - return { - name: plugin.name, - value: plugin.id - }; - }); - - options.unshift({ - name: globalize.translate('None'), - value: 'none' - }); - - selectScreensaver.innerHTML = options.map(o => { - return ``; +function fillThemes(select, selectedTheme) { + skinManager.getThemes().then(themes => { + select.innerHTML = themes.map(t => { + return ``; }).join(''); - selectScreensaver.value = userSettings.screensaver(); + // get default theme + const defaultTheme = themes.find(theme => theme.default); - if (!selectScreensaver.value) { - // TODO: set the default instead of none - selectScreensaver.value = 'none'; - } + // set the current theme + select.value = selectedTheme || defaultTheme.id; + }); +} + +function loadScreensavers(context, userSettings) { + const selectScreensaver = context.querySelector('.selectScreensaver'); + const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { + return { + name: plugin.name, + value: plugin.id + }; + }); + + options.unshift({ + name: globalize.translate('None'), + value: 'none' + }); + + selectScreensaver.innerHTML = options.map(o => { + return ``; + }).join(''); + + selectScreensaver.value = userSettings.screensaver(); + + if (!selectScreensaver.value) { + // TODO: set the default instead of none + selectScreensaver.value = 'none'; + } +} + +function showOrHideMissingEpisodesField(context) { + if (browser.tizen || browser.web0s) { + context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide'); + return; } - function showOrHideMissingEpisodesField(context) { - if (browser.tizen || browser.web0s) { - context.querySelector('.fldDisplayMissingEpisodes').classList.add('hide'); - return; - } + context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); +} - context.querySelector('.fldDisplayMissingEpisodes').classList.remove('hide'); +function loadForm(context, user, userSettings) { + if (appHost.supports('displaylanguage')) { + context.querySelector('.languageSection').classList.remove('hide'); + } else { + context.querySelector('.languageSection').classList.add('hide'); } - function loadForm(context, user, userSettings) { - if (appHost.supports('displaylanguage')) { - context.querySelector('.languageSection').classList.remove('hide'); - } else { - context.querySelector('.languageSection').classList.add('hide'); - } - - if (appHost.supports('displaymode')) { - context.querySelector('.fldDisplayMode').classList.remove('hide'); - } else { - context.querySelector('.fldDisplayMode').classList.add('hide'); - } - - if (appHost.supports('externallinks')) { - context.querySelector('.learnHowToContributeContainer').classList.remove('hide'); - } else { - context.querySelector('.learnHowToContributeContainer').classList.add('hide'); - } - - context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator); - - if (appHost.supports('screensaver')) { - context.querySelector('.selectScreensaverContainer').classList.remove('hide'); - } else { - context.querySelector('.selectScreensaverContainer').classList.add('hide'); - } - - if (datetime.supportsLocalization()) { - context.querySelector('.fldDateTimeLocale').classList.remove('hide'); - } else { - context.querySelector('.fldDateTimeLocale').classList.add('hide'); - } - - fillThemes(context.querySelector('#selectTheme'), userSettings.theme()); - fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme()); - - loadScreensavers(context, userSettings); - - context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; - - context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); - context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); - context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); - context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); - context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); - context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); - - context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); - context.querySelector('#txtLocalCustomCss').value = userSettings.customCss(); - - context.querySelector('#selectLanguage').value = userSettings.language() || ''; - context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; - - context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); - - context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp(); - context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp(); - context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume(); - - context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || ''; - - showOrHideMissingEpisodesField(context); - - loading.hide(); + if (appHost.supports('displaymode')) { + context.querySelector('.fldDisplayMode').classList.remove('hide'); + } else { + context.querySelector('.fldDisplayMode').classList.add('hide'); } - function saveUser(context, user, userSettingsInstance, apiClient) { - user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; - - if (appHost.supports('displaylanguage')) { - userSettingsInstance.language(context.querySelector('#selectLanguage').value); - } - - userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value); - - userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked); - userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked); - userSettingsInstance.theme(context.querySelector('#selectTheme').value); - userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); - userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); - - userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); - - userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value); - userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked); - userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked); - - userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); - userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); - userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); - userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); - - userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); - userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value); - - if (user.Id === apiClient.getCurrentUserId()) { - skinManager.setTheme(userSettingsInstance.theme()); - } - - layoutManager.setLayout(context.querySelector('.selectLayout').value); - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + if (appHost.supports('externallinks')) { + context.querySelector('.learnHowToContributeContainer').classList.remove('hide'); + } else { + context.querySelector('.learnHowToContributeContainer').classList.add('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + context.querySelector('.selectDashboardThemeContainer').classList.toggle('hide', !user.Policy.IsAdministrator); + + if (appHost.supports('screensaver')) { + context.querySelector('.selectScreensaverContainer').classList.remove('hide'); + } else { + context.querySelector('.selectScreensaverContainer').classList.add('hide'); + } + + if (datetime.supportsLocalization()) { + context.querySelector('.fldDateTimeLocale').classList.remove('hide'); + } else { + context.querySelector('.fldDateTimeLocale').classList.add('hide'); + } + + fillThemes(context.querySelector('#selectTheme'), userSettings.theme()); + fillThemes(context.querySelector('#selectDashboardTheme'), userSettings.dashboardTheme()); + + loadScreensavers(context, userSettings); + + context.querySelector('.chkDisplayMissingEpisodes').checked = user.Configuration.DisplayMissingEpisodes || false; + + context.querySelector('#chkThemeSong').checked = userSettings.enableThemeSongs(); + context.querySelector('#chkThemeVideo').checked = userSettings.enableThemeVideos(); + context.querySelector('#chkFadein').checked = userSettings.enableFastFadein(); + context.querySelector('#chkBlurhash').checked = userSettings.enableBlurhash(); + context.querySelector('#chkBackdrops').checked = userSettings.enableBackdrops(); + context.querySelector('#chkDetailsBanner').checked = userSettings.detailsBanner(); + + context.querySelector('#chkDisableCustomCss').checked = userSettings.disableCustomCss(); + context.querySelector('#txtLocalCustomCss').value = userSettings.customCss(); + + context.querySelector('#selectLanguage').value = userSettings.language() || ''; + context.querySelector('.selectDateTimeLocale').value = userSettings.dateTimeLocale() || ''; + + context.querySelector('#txtLibraryPageSize').value = userSettings.libraryPageSize(); + + context.querySelector('#txtMaxDaysForNextUp').value = userSettings.maxDaysForNextUp(); + context.querySelector('#chkRewatchingNextUp').checked = userSettings.enableRewatchingInNextUp(); + context.querySelector('#chkUseEpisodeImagesInNextUp').checked = userSettings.useEpisodeImagesInNextUpAndResume(); + + context.querySelector('.selectLayout').value = layoutManager.getSavedLayout() || ''; + + showOrHideMissingEpisodesField(context); + + loading.hide(); +} + +function saveUser(context, user, userSettingsInstance, apiClient) { + user.Configuration.DisplayMissingEpisodes = context.querySelector('.chkDisplayMissingEpisodes').checked; + + if (appHost.supports('displaylanguage')) { + userSettingsInstance.language(context.querySelector('#selectLanguage').value); + } + + userSettingsInstance.dateTimeLocale(context.querySelector('.selectDateTimeLocale').value); + + userSettingsInstance.enableThemeSongs(context.querySelector('#chkThemeSong').checked); + userSettingsInstance.enableThemeVideos(context.querySelector('#chkThemeVideo').checked); + userSettingsInstance.theme(context.querySelector('#selectTheme').value); + userSettingsInstance.dashboardTheme(context.querySelector('#selectDashboardTheme').value); + userSettingsInstance.screensaver(context.querySelector('.selectScreensaver').value); + + userSettingsInstance.libraryPageSize(context.querySelector('#txtLibraryPageSize').value); + + userSettingsInstance.maxDaysForNextUp(context.querySelector('#txtMaxDaysForNextUp').value); + userSettingsInstance.enableRewatchingInNextUp(context.querySelector('#chkRewatchingNextUp').checked); + userSettingsInstance.useEpisodeImagesInNextUpAndResume(context.querySelector('#chkUseEpisodeImagesInNextUp').checked); + + userSettingsInstance.enableFastFadein(context.querySelector('#chkFadein').checked); + userSettingsInstance.enableBlurhash(context.querySelector('#chkBlurhash').checked); + userSettingsInstance.enableBackdrops(context.querySelector('#chkBackdrops').checked); + userSettingsInstance.detailsBanner(context.querySelector('#chkDetailsBanner').checked); + + userSettingsInstance.disableCustomCss(context.querySelector('#chkDisableCustomCss').checked); + userSettingsInstance.customCss(context.querySelector('#txtLocalCustomCss').value); + + if (user.Id === apiClient.getCurrentUserId()) { + skinManager.setTheme(userSettingsInstance.theme()); + } + + layoutManager.setLayout(context.querySelector('.selectLayout').value); + return apiClient.updateUserConfiguration(user.Id, user.Configuration); +} + +function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + loading.show(); + + apiClient.getUser(userId).then(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + Events.trigger(instance, 'saved'); + }, () => { + loading.hide(); + }); + }); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const enableSaveConfirmation = self.options.enableSaveConfirmation; + save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); + }); + + // Disable default form submission + if (e) { + e.preventDefault(); + } + return false; +} + +function embed(options, self) { + options.element.innerHTML = globalize.translateHtml(template, 'core'); + options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); + if (options.enableSaveButton) { + options.element.querySelector('.btnSave').classList.remove('hide'); + } + self.loadData(options.autoFocus); +} + +class DisplaySettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData(autoFocus) { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const enableSaveConfirmation = self.options.enableSaveConfirmation; - save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); - }); - - // Disable default form submission - if (e) { - e.preventDefault(); - } - return false; - } - - function embed(options, self) { - options.element.innerHTML = globalize.translateHtml(template, 'core'); - options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); - if (options.enableSaveButton) { - options.element.querySelector('.btnSave').classList.remove('hide'); - } - self.loadData(options.autoFocus); - } - - class DisplaySettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData(autoFocus) { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - return apiClient.getUser(userId).then(user => { - return userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - loadForm(context, user, userSettings); - if (autoFocus) { - focusManager.autoFocus(context); - } - }); + return apiClient.getUser(userId).then(user => { + return userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; + loadForm(context, user, userSettings); + if (autoFocus) { + focusManager.autoFocus(context); + } }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} + export default DisplaySettings; diff --git a/src/components/favoriteitems.js b/src/components/favoriteitems.js index 6bba5c3bdc..ac0f3c0de0 100644 --- a/src/components/favoriteitems.js +++ b/src/components/favoriteitems.js @@ -9,236 +9,232 @@ import { getParameterByName } from '../utils/url.ts'; import '../styles/scrollstyles.scss'; import '../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +function enableScrollX() { + return !layoutManager.desktop; +} - function enableScrollX() { - return !layoutManager.desktop; +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getPosterShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function getSections() { + return [{ + name: 'Movies', + types: 'Movie', + id: 'favoriteMovies', + shape: getPosterShape(), + showTitle: false, + overlayPlayButton: true + }, { + name: 'Shows', + types: 'Series', + id: 'favoriteShows', + shape: getPosterShape(), + showTitle: false, + overlayPlayButton: true + }, { + name: 'Episodes', + types: 'Episode', + id: 'favoriteEpisode', + shape: getThumbShape(), + preferThumb: false, + showTitle: true, + showParentTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Videos', + types: 'Video,MusicVideo', + id: 'favoriteVideos', + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Artists', + types: 'MusicArtist', + id: 'favoriteArtists', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Albums', + types: 'MusicAlbum', + id: 'favoriteAlbums', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Songs', + types: 'Audio', + id: 'favoriteSongs', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayMoreButton: true, + action: 'instantmix', + coverImage: true + }]; +} + +function loadSection(elem, userId, topParentId, section, isSingleSection) { + const screenWidth = dom.getWindowSize().innerWidth; + const options = { + SortBy: 'SortName', + SortOrder: 'Ascending', + Filters: 'IsFavorite', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + CollapseBoxSetItems: false, + ExcludeLocationTypes: 'Virtual', + EnableTotalRecordCount: false + }; + + if (topParentId) { + options.ParentId = topParentId; } - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } + if (!isSingleSection) { + options.Limit = 6; - function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } - - function getSections() { - return [{ - name: 'Movies', - types: 'Movie', - id: 'favoriteMovies', - shape: getPosterShape(), - showTitle: false, - overlayPlayButton: true - }, { - name: 'Shows', - types: 'Series', - id: 'favoriteShows', - shape: getPosterShape(), - showTitle: false, - overlayPlayButton: true - }, { - name: 'Episodes', - types: 'Episode', - id: 'favoriteEpisode', - shape: getThumbShape(), - preferThumb: false, - showTitle: true, - showParentTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Videos', - types: 'Video,MusicVideo', - id: 'favoriteVideos', - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Artists', - types: 'MusicArtist', - id: 'favoriteArtists', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Albums', - types: 'MusicAlbum', - id: 'favoriteAlbums', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Songs', - types: 'Audio', - id: 'favoriteSongs', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayMoreButton: true, - action: 'instantmix', - coverImage: true - }]; - } - - function loadSection(elem, userId, topParentId, section, isSingleSection) { - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'SortName', - SortOrder: 'Ascending', - Filters: 'IsFavorite', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - CollapseBoxSetItems: false, - ExcludeLocationTypes: 'Virtual', - EnableTotalRecordCount: false - }; - - if (topParentId) { - options.ParentId = topParentId; + if (enableScrollX()) { + options.Limit = 20; + } else if (screenWidth >= 1920) { + options.Limit = 10; + } else if (screenWidth >= 1440) { + options.Limit = 8; } + } - if (!isSingleSection) { - options.Limit = 6; + let promise; + if (section.types === 'MusicArtist') { + promise = ApiClient.getArtists(userId, options); + } else { + options.IncludeItemTypes = section.types; + promise = ApiClient.getItems(userId, options); + } + + return promise.then(function (result) { + let html = ''; + + if (result.Items.length) { + html += '
'; + + if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { + html += ''; + html += '

'; + html += globalize.translate(section.name); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate(section.name) + '

'; + } + + html += '
'; if (enableScrollX()) { - options.Limit = 20; - } else if (screenWidth >= 1920) { - options.Limit = 10; - } else if (screenWidth >= 1440) { - options.Limit = 8; - } - } - - let promise; - - if (section.types === 'MusicArtist') { - promise = ApiClient.getArtists(userId, options); - } else { - options.IncludeItemTypes = section.types; - promise = ApiClient.getItems(userId, options); - } - - return promise.then(function (result) { - let html = ''; - - if (result.Items.length) { - html += '
'; - - if (!layoutManager.tv && options.Limit && result.Items.length >= options.Limit) { - html += ''; - html += '

'; - html += globalize.translate(section.name); - html += '

'; - html += ''; - html += '
'; - } else { - html += '

' + globalize.translate(section.name) + '

'; + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; } - html += '
'; - if (enableScrollX()) { - let scrollXClass = 'scrollX hiddenScrollX'; - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
'; - } else { - html += '
'; - } - - let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; - cardLayout = false; - html += cardBuilder.getCardsHtml(result.Items, { - preferThumb: section.preferThumb, - shape: section.shape, - centerText: section.centerText && !cardLayout, - overlayText: section.overlayText !== false, - showTitle: section.showTitle, - showParentTitle: section.showParentTitle, - scalable: true, - coverImage: section.coverImage, - overlayPlayButton: section.overlayPlayButton, - overlayMoreButton: section.overlayMoreButton && !cardLayout, - action: section.action, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout - }); - html += '
'; + html += '
'; + } else { + html += '
'; } - elem.innerHTML = html; - imageLoader.lazyChildren(elem); + let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; + cardLayout = false; + html += cardBuilder.getCardsHtml(result.Items, { + preferThumb: section.preferThumb, + shape: section.shape, + centerText: section.centerText && !cardLayout, + overlayText: section.overlayText !== false, + showTitle: section.showTitle, + showParentTitle: section.showParentTitle, + scalable: true, + coverImage: section.coverImage, + overlayPlayButton: section.overlayPlayButton, + overlayMoreButton: section.overlayMoreButton && !cardLayout, + action: section.action, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout + }); + html += '
'; + } + + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + }); +} + +export function loadSections(page, userId, topParentId, types) { + loading.show(); + let sections = getSections(); + const sectionid = getParameterByName('sectionid'); + + if (sectionid) { + sections = sections.filter(function (s) { + return s.id === sectionid; }); } - export function loadSections(page, userId, topParentId, types) { - loading.show(); - let sections = getSections(); - const sectionid = getParameterByName('sectionid'); + if (types) { + sections = sections.filter(function (s) { + return types.indexOf(s.id) !== -1; + }); + } - if (sectionid) { - sections = sections.filter(function (s) { - return s.id === sectionid; - }); - } + let elem = page.querySelector('.favoriteSections'); - if (types) { - sections = sections.filter(function (s) { - return types.indexOf(s.id) !== -1; - }); - } - - let elem = page.querySelector('.favoriteSections'); - - if (!elem.innerHTML) { - let html = ''; - - for (let i = 0, length = sections.length; i < length; i++) { - html += '
'; - } - - elem.innerHTML = html; - } - - const promises = []; + if (!elem.innerHTML) { + let html = ''; for (let i = 0, length = sections.length; i < length; i++) { - const section = sections[i]; - elem = page.querySelector('.section' + section.id); - promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1)); + html += '
'; } - Promise.all(promises).then(function () { - loading.hide(); - }); + elem.innerHTML = html; } + const promises = []; + + for (let i = 0, length = sections.length; i < length; i++) { + const section = sections[i]; + elem = page.querySelector('.section' + section.id); + promises.push(loadSection(elem, userId, topParentId, section, sections.length === 1)); + } + + Promise.all(promises).then(function () { + loading.hide(); + }); +} + export default { render: loadSections }; - -/* eslint-enable indent */ diff --git a/src/components/fetchhelper.js b/src/components/fetchhelper.js index efc490fc66..f63fa62ced 100644 --- a/src/components/fetchhelper.js +++ b/src/components/fetchhelper.js @@ -1,110 +1,108 @@ -/* eslint-disable indent */ - export function getFetchPromise(request) { - const headers = request.headers || {}; +export function getFetchPromise(request) { + const headers = request.headers || {}; - if (request.dataType === 'json') { - headers.accept = 'application/json'; - } - - const fetchRequest = { - headers: headers, - method: request.type, - credentials: 'same-origin' - }; - - let contentType = request.contentType; - - if (request.data) { - if (typeof request.data === 'string') { - fetchRequest.body = request.data; - } else { - fetchRequest.body = paramsToString(request.data); - - contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8'; - } - } - - if (contentType) { - headers['Content-Type'] = contentType; - } - - let url = request.url; - - if (request.query) { - const paramString = paramsToString(request.query); - if (paramString) { - url += `?${paramString}`; - } - } - - if (!request.timeout) { - return fetch(url, fetchRequest); - } - - return fetchWithTimeout(url, fetchRequest, request.timeout); + if (request.dataType === 'json') { + headers.accept = 'application/json'; } - function fetchWithTimeout(url, options, timeoutMs) { - console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); + const fetchRequest = { + headers: headers, + method: request.type, + credentials: 'same-origin' + }; - return new Promise(function (resolve, reject) { - const timeout = setTimeout(reject, timeoutMs); + let contentType = request.contentType; - options = options || {}; - options.credentials = 'same-origin'; + if (request.data) { + if (typeof request.data === 'string') { + fetchRequest.body = request.data; + } else { + fetchRequest.body = paramsToString(request.data); - fetch(url, options).then(function (response) { - clearTimeout(timeout); + contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8'; + } + } - console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); + if (contentType) { + headers['Content-Type'] = contentType; + } - resolve(response); - }, function (error) { - clearTimeout(timeout); + let url = request.url; - console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); + if (request.query) { + const paramString = paramsToString(request.query); + if (paramString) { + url += `?${paramString}`; + } + } - reject(error); - }); + if (!request.timeout) { + return fetch(url, fetchRequest); + } + + return fetchWithTimeout(url, fetchRequest, request.timeout); +} + +function fetchWithTimeout(url, options, timeoutMs) { + console.debug(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`); + + return new Promise(function (resolve, reject) { + const timeout = setTimeout(reject, timeoutMs); + + options = options || {}; + options.credentials = 'same-origin'; + + fetch(url, options).then(function (response) { + clearTimeout(timeout); + + console.debug(`fetchWithTimeout: succeeded connecting to url: ${url}`); + + resolve(response); + }, function (error) { + clearTimeout(timeout); + + console.debug(`fetchWithTimeout: timed out connecting to url: ${url}`); + + reject(error); }); - } + }); +} - /** +/** * @param params {Record} * @returns {string} Query string */ - function paramsToString(params) { - return Object.entries(params) - .filter(([, v]) => v !== null && v !== undefined && v !== '') - .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) - .join('&'); +function paramsToString(params) { + return Object.entries(params) + .filter(([, v]) => v !== null && v !== undefined && v !== '') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); +} + +export function ajax(request) { + if (!request) { + throw new Error('Request cannot be null'); } - export function ajax(request) { - if (!request) { - throw new Error('Request cannot be null'); - } + request.headers = request.headers || {}; - 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}`); - 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().startsWith('text/')) { - return response.text(); - } else { - return response; - } + return getFetchPromise(request).then(function (response) { + 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().startsWith('text/')) { + return response.text(); } else { - return Promise.reject(response); + return response; } - }, function (err) { - console.error(`request failed to url: ${request.url}`); - throw err; - }); - } -/* eslint-enable indent */ + } else { + return Promise.reject(response); + } + }, function (err) { + console.error(`request failed to url: ${request.url}`); + throw err; + }); +} diff --git a/src/components/filterdialog/filterdialog.js b/src/components/filterdialog/filterdialog.js index 83d7ccbd2e..2301dcd404 100644 --- a/src/components/filterdialog/filterdialog.js +++ b/src/components/filterdialog/filterdialog.js @@ -8,417 +8,414 @@ import './style.scss'; import ServerConnections from '../ServerConnections'; import template from './filterdialog.template.html'; -/* eslint-disable indent */ - function renderOptions(context, selector, cssClass, items, isCheckedFn) { - const elem = context.querySelector(selector); - if (items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - let html = ''; - html += '
'; - html += items.map(function (filter) { - let itemHtml = ''; - const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; - itemHtml += ''; - return itemHtml; - }).join(''); - html += '
'; - elem.querySelector('.filterOptions').innerHTML = html; +function renderOptions(context, selector, cssClass, items, isCheckedFn) { + const elem = context.querySelector(selector); + if (items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); } + let html = ''; + html += '
'; + html += items.map(function (filter) { + let itemHtml = ''; + const checkedHtml = isCheckedFn(filter) ? 'checked' : ''; + itemHtml += ''; + return itemHtml; + }).join(''); + html += '
'; + elem.querySelector('.filterOptions').innerHTML = html; +} - function renderFilters(context, result, query) { - renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { - const delimeter = '|'; - return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) { - const delimeter = '|'; - return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) { - const delimeter = '|'; - return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter); - }); - renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) { - const delimeter = ','; - return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter); - }); - } +function renderFilters(context, result, query) { + renderOptions(context, '.genreFilters', 'chkGenreFilter', result.Genres, function (i) { + const delimeter = '|'; + return (delimeter + (query.Genres || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.officialRatingFilters', 'chkOfficialRatingFilter', result.OfficialRatings, function (i) { + const delimeter = '|'; + return (delimeter + (query.OfficialRatings || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.tagFilters', 'chkTagFilter', result.Tags, function (i) { + const delimeter = '|'; + return (delimeter + (query.Tags || '') + delimeter).includes(delimeter + i + delimeter); + }); + renderOptions(context, '.yearFilters', 'chkYearFilter', result.Years, function (i) { + const delimeter = ','; + return (delimeter + (query.Years || '') + delimeter).includes(delimeter + i + delimeter); + }); +} - function loadDynamicFilters(context, apiClient, userId, itemQuery) { - return apiClient.getJSON(apiClient.getUrl('Items/Filters', { - UserId: userId, - ParentId: itemQuery.ParentId, - IncludeItemTypes: itemQuery.IncludeItemTypes - })).then(function (result) { - renderFilters(context, result, itemQuery); - }); - } +function loadDynamicFilters(context, apiClient, userId, itemQuery) { + return apiClient.getJSON(apiClient.getUrl('Items/Filters', { + UserId: userId, + ParentId: itemQuery.ParentId, + IncludeItemTypes: itemQuery.IncludeItemTypes + })).then(function (result) { + renderFilters(context, result, itemQuery); + }); +} - /** +/** * @param context {HTMLDivElement} Dialog * @param options {any} Options */ - function updateFilterControls(context, options) { - const query = options.query; +function updateFilterControls(context, options) { + const query = options.query; - if (options.mode === 'livetvchannels') { - context.querySelector('.chkFavorite').checked = query.IsFavorite === true; + if (options.mode === 'livetvchannels') { + context.querySelector('.chkFavorite').checked = query.IsFavorite === true; + } else { + for (const elem of context.querySelectorAll('.chkStandardFilter')) { + const filters = `,${query.Filters || ''}`; + const filterName = elem.getAttribute('data-filter'); + elem.checked = filters.includes(`,${filterName}`); + } + } + + 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 === false; + 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') { + hideByClass(context, 'videoStandard'); + } + + if (enableDynamicFilters(options.mode)) { + context.querySelector('.genreFilters').classList.remove('hide'); + context.querySelector('.officialRatingFilters').classList.remove('hide'); + context.querySelector('.tagFilters').classList.remove('hide'); + context.querySelector('.yearFilters').classList.remove('hide'); + } + + if (options.mode === 'movies' || options.mode === 'episodes') { + context.querySelector('.videoTypeFilters').classList.remove('hide'); + } + + if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') { + context.querySelector('.features').classList.remove('hide'); + } + + if (options.mode === 'series') { + context.querySelector('.seriesStatus').classList.remove('hide'); + } + + if (options.mode === 'episodes') { + showByClass(context, 'episodeFilter'); + } +} + +function showByClass(context, className) { + for (const elem of context.querySelectorAll(`.${className}`)) { + elem.classList.remove('hide'); + } +} + +function hideByClass(context, className) { + 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'; +} + +class FilterDialog { + constructor(options) { + /** + * @private + */ + this.options = options; + } + + /** + * @private + */ + onFavoriteChange(elem) { + const query = this.options.query; + query.StartIndex = 0; + query.IsFavorite = !!elem.checked || null; + triggerChange(this); + } + + /** + * @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(this); + } + + /** + * @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 (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; + } + + query.StartIndex = 0; + query.VideoTypes = filters; + triggerChange(this); + } + + /** + * @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 (elem.checked) { + filters = filters ? `${filters},${filterName}` : filterName; + } + + query.SeriesStatus = filters; + query.StartIndex = 0; + triggerChange(this); + } + + /** + * @param context {HTMLDivElement} The dialog + */ + bindEvents(context) { + const query = this.options.query; + + if (this.options.mode === 'livetvchannels') { + for (const elem of context.querySelectorAll('.chkFavorite')) { + elem.addEventListener('change', () => this.onFavoriteChange(elem)); + } } else { for (const elem of context.querySelectorAll('.chkStandardFilter')) { - const filters = `,${query.Filters || ''}`; - const filterName = elem.getAttribute('data-filter'); - elem.checked = filters.includes(`,${filterName}`); + elem.addEventListener('change', () => this.onStandardFilterChange(elem)); } } for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { - const filters = `,${query.VideoTypes || ''}`; - const filterName = elem.getAttribute('data-filter'); - elem.checked = filters.includes(`,${filterName}`); + elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); } - 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 === false; - 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') { - hideByClass(context, 'videoStandard'); - } - - if (enableDynamicFilters(options.mode)) { - context.querySelector('.genreFilters').classList.remove('hide'); - context.querySelector('.officialRatingFilters').classList.remove('hide'); - context.querySelector('.tagFilters').classList.remove('hide'); - context.querySelector('.yearFilters').classList.remove('hide'); - } - - if (options.mode === 'movies' || options.mode === 'episodes') { - context.querySelector('.videoTypeFilters').classList.remove('hide'); - } - - if (options.mode === 'movies' || options.mode === 'series' || options.mode === 'episodes') { - context.querySelector('.features').classList.remove('hide'); - } - - if (options.mode === 'series') { - context.querySelector('.seriesStatus').classList.remove('hide'); - } - - if (options.mode === 'episodes') { - showByClass(context, 'episodeFilter'); - } - } - - function showByClass(context, className) { - for (const elem of context.querySelectorAll(`.${className}`)) { - elem.classList.remove('hide'); - } - } - - function hideByClass(context, className) { - 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'; - } - - class FilterDialog { - constructor(options) { - /** - * @private - */ - this.options = options; - } - - /** - * @private - */ - onFavoriteChange(elem) { - const query = this.options.query; + const chk3DFilter = context.querySelector('.chk3DFilter'); + chk3DFilter.addEventListener('change', () => { query.StartIndex = 0; - query.IsFavorite = !!elem.checked || null; + query.Is3D = chk3DFilter.checked ? true : null; triggerChange(this); - } - - /** - * @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; - } - + }); + const chk4KFilter = context.querySelector('.chk4KFilter'); + chk4KFilter.addEventListener('change', () => { query.StartIndex = 0; - query.Filters = filters; + query.Is4K = chk4KFilter.checked ? true : null; triggerChange(this); - } - - /** - * @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 (elem.checked) { - filters = filters ? `${filters},${filterName}` : filterName; - } - + }); + const chkHDFilter = context.querySelector('.chkHDFilter'); + const chkSDFilter = context.querySelector('.chkSDFilter'); + chkHDFilter.addEventListener('change', () => { query.StartIndex = 0; - query.VideoTypes = filters; - triggerChange(this); - } - - /** - * @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 (elem.checked) { - filters = filters ? `${filters},${filterName}` : filterName; - } - - query.SeriesStatus = filters; - query.StartIndex = 0; - triggerChange(this); - } - - /** - * @param context {HTMLDivElement} The dialog - */ - bindEvents(context) { - const query = this.options.query; - - if (this.options.mode === 'livetvchannels') { - for (const elem of context.querySelectorAll('.chkFavorite')) { - elem.addEventListener('change', () => this.onFavoriteChange(elem)); - } + if (chkHDFilter.checked) { + chkSDFilter.checked = false; + query.IsHD = true; } else { - for (const elem of context.querySelectorAll('.chkStandardFilter')) { - elem.addEventListener('change', () => this.onStandardFilterChange(elem)); - } + query.IsHD = null; } - - for (const elem of context.querySelectorAll('.chkVideoTypeFilter')) { - elem.addEventListener('change', () => this.onVideoTypeFilterChange(elem)); + triggerChange(this); + }); + chkSDFilter.addEventListener('change', () => { + query.StartIndex = 0; + if (chkSDFilter.checked) { + chkHDFilter.checked = false; + query.IsHD = false; + } else { + query.IsHD = null; } - const chk3DFilter = context.querySelector('.chk3DFilter'); - chk3DFilter.addEventListener('change', () => { - query.StartIndex = 0; - query.Is3D = chk3DFilter.checked ? true : null; - triggerChange(this); - }); - const chk4KFilter = context.querySelector('.chk4KFilter'); - chk4KFilter.addEventListener('change', () => { - query.StartIndex = 0; - query.Is4K = chk4KFilter.checked ? true : null; - triggerChange(this); - }); - const chkHDFilter = context.querySelector('.chkHDFilter'); - const chkSDFilter = context.querySelector('.chkSDFilter'); - chkHDFilter.addEventListener('change', () => { - query.StartIndex = 0; - if (chkHDFilter.checked) { - chkSDFilter.checked = false; - query.IsHD = true; - } else { - query.IsHD = null; - } - triggerChange(this); - }); - chkSDFilter.addEventListener('change', () => { - query.StartIndex = 0; - if (chkSDFilter.checked) { - chkHDFilter.checked = false; - query.IsHD = false; - } else { - query.IsHD = null; - } - triggerChange(this); - }); - for (const elem of context.querySelectorAll('.chkStatus')) { - elem.addEventListener('change', () => this.onStatusChange(elem)); - } - const chkTrailer = context.querySelector('#chkTrailer'); - chkTrailer.addEventListener('change', () => { - query.StartIndex = 0; - query.HasTrailer = chkTrailer.checked ? true : null; - triggerChange(this); - }); - const chkThemeSong = context.querySelector('#chkThemeSong'); - chkThemeSong.addEventListener('change', () => { - query.StartIndex = 0; - query.HasThemeSong = chkThemeSong.checked ? true : null; - triggerChange(this); - }); - const chkSpecialFeature = context.querySelector('#chkSpecialFeature'); - chkSpecialFeature.addEventListener('change', () => { - query.StartIndex = 0; - query.HasSpecialFeature = chkSpecialFeature.checked ? true : null; - triggerChange(this); - }); - const chkThemeVideo = context.querySelector('#chkThemeVideo'); - chkThemeVideo.addEventListener('change', () => { - query.StartIndex = 0; - query.HasThemeVideo = chkThemeVideo.checked ? true : null; - triggerChange(this); - }); - const chkMissingEpisode = context.querySelector('#chkMissingEpisode'); - chkMissingEpisode.addEventListener('change', () => { - query.StartIndex = 0; - query.IsMissing = !!chkMissingEpisode.checked; - triggerChange(this); - }); - const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode'); - chkSpecialEpisode.addEventListener('change', () => { - query.StartIndex = 0; - query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null; - triggerChange(this); - }); - const chkFutureEpisode = context.querySelector('#chkFutureEpisode'); - chkFutureEpisode.addEventListener('change', () => { - query.StartIndex = 0; - if (chkFutureEpisode.checked) { - query.IsUnaired = true; - query.IsVirtualUnaired = null; - } else { - query.IsUnaired = null; - query.IsVirtualUnaired = false; - } - triggerChange(this); - }); - const chkSubtitle = context.querySelector('#chkSubtitle'); - chkSubtitle.addEventListener('change', () => { - query.StartIndex = 0; - query.HasSubtitles = chkSubtitle.checked ? true : null; - triggerChange(this); - }); - context.addEventListener('change', (e) => { - const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter'); - if (chkGenreFilter) { - 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(this); - return; - } - const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter'); - if (chkTagFilter) { - 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(this); - return; - } - const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter'); - if (chkYearFilter) { - 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(this); - return; - } - const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter'); - if (chkOfficialRatingFilter) { - 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(this); - } - }); + triggerChange(this); + }); + for (const elem of context.querySelectorAll('.chkStatus')) { + elem.addEventListener('change', () => this.onStatusChange(elem)); } - - show() { - return new Promise((resolve) => { - const dlg = dialogHelper.createDialog({ - removeOnClose: true, - modal: false - }); - dlg.classList.add('ui-body-a'); - dlg.classList.add('background-theme-a'); - dlg.classList.add('formDialog'); - dlg.classList.add('filterDialog'); - dlg.innerHTML = globalize.translateHtml(template); - setVisibility(dlg, this.options); - dialogHelper.open(dlg); - dlg.addEventListener('close', resolve); - updateFilterControls(dlg, this.options); - this.bindEvents(dlg); - if (enableDynamicFilters(this.options.mode)) { - dlg.classList.add('dynamicFilterDialog'); - const apiClient = ServerConnections.getApiClient(this.options.serverId); - loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); + const chkTrailer = context.querySelector('#chkTrailer'); + chkTrailer.addEventListener('change', () => { + query.StartIndex = 0; + query.HasTrailer = chkTrailer.checked ? true : null; + triggerChange(this); + }); + const chkThemeSong = context.querySelector('#chkThemeSong'); + chkThemeSong.addEventListener('change', () => { + query.StartIndex = 0; + query.HasThemeSong = chkThemeSong.checked ? true : null; + triggerChange(this); + }); + const chkSpecialFeature = context.querySelector('#chkSpecialFeature'); + chkSpecialFeature.addEventListener('change', () => { + query.StartIndex = 0; + query.HasSpecialFeature = chkSpecialFeature.checked ? true : null; + triggerChange(this); + }); + const chkThemeVideo = context.querySelector('#chkThemeVideo'); + chkThemeVideo.addEventListener('change', () => { + query.StartIndex = 0; + query.HasThemeVideo = chkThemeVideo.checked ? true : null; + triggerChange(this); + }); + const chkMissingEpisode = context.querySelector('#chkMissingEpisode'); + chkMissingEpisode.addEventListener('change', () => { + query.StartIndex = 0; + query.IsMissing = !!chkMissingEpisode.checked; + triggerChange(this); + }); + const chkSpecialEpisode = context.querySelector('#chkSpecialEpisode'); + chkSpecialEpisode.addEventListener('change', () => { + query.StartIndex = 0; + query.ParentIndexNumber = chkSpecialEpisode.checked ? 0 : null; + triggerChange(this); + }); + const chkFutureEpisode = context.querySelector('#chkFutureEpisode'); + chkFutureEpisode.addEventListener('change', () => { + query.StartIndex = 0; + if (chkFutureEpisode.checked) { + query.IsUnaired = true; + query.IsVirtualUnaired = null; + } else { + query.IsUnaired = null; + query.IsVirtualUnaired = false; + } + triggerChange(this); + }); + const chkSubtitle = context.querySelector('#chkSubtitle'); + chkSubtitle.addEventListener('change', () => { + query.StartIndex = 0; + query.HasSubtitles = chkSubtitle.checked ? true : null; + triggerChange(this); + }); + context.addEventListener('change', (e) => { + const chkGenreFilter = dom.parentWithClass(e.target, 'chkGenreFilter'); + if (chkGenreFilter) { + 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(this); + return; + } + const chkTagFilter = dom.parentWithClass(e.target, 'chkTagFilter'); + if (chkTagFilter) { + 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(this); + return; + } + const chkYearFilter = dom.parentWithClass(e.target, 'chkYearFilter'); + if (chkYearFilter) { + 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(this); + return; + } + const chkOfficialRatingFilter = dom.parentWithClass(e.target, 'chkOfficialRatingFilter'); + if (chkOfficialRatingFilter) { + 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(this); + } + }); } -/* eslint-enable indent */ + show() { + return new Promise((resolve) => { + const dlg = dialogHelper.createDialog({ + removeOnClose: true, + modal: false + }); + dlg.classList.add('ui-body-a'); + dlg.classList.add('background-theme-a'); + dlg.classList.add('formDialog'); + dlg.classList.add('filterDialog'); + dlg.innerHTML = globalize.translateHtml(template); + setVisibility(dlg, this.options); + dialogHelper.open(dlg); + dlg.addEventListener('close', resolve); + updateFilterControls(dlg, this.options); + this.bindEvents(dlg); + if (enableDynamicFilters(this.options.mode)) { + dlg.classList.add('dynamicFilterDialog'); + const apiClient = ServerConnections.getApiClient(this.options.serverId); + loadDynamicFilters(dlg, apiClient, apiClient.getCurrentUserId(), this.options.query); + } + }); + } +} export default FilterDialog; diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 4c1b6d625b..d7e9c87650 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -297,10 +297,8 @@ class FilterMenu { } if (submitted) { - //if (!options.onChange) { saveValues(dlg, options.settings, options.settingsKey, options.setfilters); return resolve(); - //} } return resolve(); }); diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 1eacc49e77..48ccd6b92c 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -1,470 +1,466 @@ -/* eslint-disable indent */ - import dom from '../scripts/dom'; import scrollManager from './scrollManager'; - const scopes = []; - function pushScope(elem) { - scopes.push(elem); - } +const scopes = []; +function pushScope(elem) { + scopes.push(elem); +} - function popScope() { - if (scopes.length) { - scopes.length -= 1; +function popScope() { + if (scopes.length) { + scopes.length -= 1; + } +} + +function autoFocus(view, defaultToFirst, findAutoFocusElement) { + let element; + if (findAutoFocusElement !== false) { + element = view.querySelector('*[autofocus]'); + if (element) { + focus(element); + return element; } } - function autoFocus(view, defaultToFirst, findAutoFocusElement) { - let element; - if (findAutoFocusElement !== false) { - element = view.querySelector('*[autofocus]'); - if (element) { - focus(element); - return element; - } - } + if (defaultToFirst !== false) { + element = getFocusableElements(view, 1, 'noautofocus')[0]; - if (defaultToFirst !== false) { - element = getFocusableElements(view, 1, 'noautofocus')[0]; - - if (element) { - focus(element); - return element; - } - } - - return null; - } - - function focus(element) { - try { - element.focus({ - preventScroll: scrollManager.isEnabled() - }); - } catch (err) { - console.error('Error in focusManager.autoFocus: ' + err); + if (element) { + focus(element); + return element; } } - const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A']; - const focusableContainerTagNames = ['BODY', 'DIALOG']; - const focusableQuery = focusableTagNames.map(function (t) { - if (t === 'INPUT') { - t += ':not([type="range"]):not([type="file"])'; - } - return t + ':not([tabindex="-1"]):not(:disabled)'; - }).join(',') + ',.focusable'; + return null; +} - function isFocusable(elem) { - return focusableTagNames.indexOf(elem.tagName) !== -1 +function focus(element) { + try { + element.focus({ + preventScroll: scrollManager.isEnabled() + }); + } catch (err) { + console.error('Error in focusManager.autoFocus: ' + err); + } +} + +const focusableTagNames = ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON', 'A']; +const focusableContainerTagNames = ['BODY', 'DIALOG']; +const focusableQuery = focusableTagNames.map(function (t) { + if (t === 'INPUT') { + t += ':not([type="range"]):not([type="file"])'; + } + return t + ':not([tabindex="-1"]):not(:disabled)'; +}).join(',') + ',.focusable'; + +function isFocusable(elem) { + return focusableTagNames.indexOf(elem.tagName) !== -1 || (elem.classList?.contains('focusable')); +} + +function normalizeFocusable(elem, originalElement) { + if (elem) { + const tagName = elem.tagName; + if (!tagName || tagName === 'HTML' || tagName === 'BODY') { + elem = originalElement; + } } - function normalizeFocusable(elem, originalElement) { - if (elem) { - const tagName = elem.tagName; - if (!tagName || tagName === 'HTML' || tagName === 'BODY') { - elem = originalElement; - } + return elem; +} + +function focusableParent(elem) { + const originalElement = elem; + + while (!isFocusable(elem)) { + const parent = elem.parentNode; + + if (!parent) { + return normalizeFocusable(elem, originalElement); } - return elem; + elem = parent; } - function focusableParent(elem) { - const originalElement = elem; + return normalizeFocusable(elem, originalElement); +} - while (!isFocusable(elem)) { - const parent = elem.parentNode; - - if (!parent) { - return normalizeFocusable(elem, originalElement); - } - - elem = parent; - } - - return normalizeFocusable(elem, originalElement); - } - - // Determines if a focusable element can be focused at a given point in time - function isCurrentlyFocusableInternal(elem) { - // http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom - return elem.offsetParent !== null; - } - - // Determines if a focusable element can be focused at a given point in time - function isCurrentlyFocusable(elem) { - if (elem.disabled) { - return false; - } - - if (elem.getAttribute('tabindex') === '-1') { - return false; - } - - if (elem.tagName === 'INPUT') { - const type = elem.type; - if (type === 'range') { - return false; - } - if (type === 'file') { - return false; - } - } - - return isCurrentlyFocusableInternal(elem); - } - - function getDefaultScope() { - return scopes[0] || document.body; - } - - function getFocusableElements(parent, limit, excludeClass) { - const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery); - const focusableElements = []; - - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[i]; - - if (excludeClass && elem.classList.contains(excludeClass)) { - continue; - } - - if (isCurrentlyFocusableInternal(elem)) { - focusableElements.push(elem); - - if (limit && focusableElements.length >= limit) { - break; - } - } - } - - return focusableElements; - } - - function isFocusContainer(elem, direction) { - if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) { - return true; - } - - const classList = elem.classList; - - if (classList.contains('focuscontainer')) { - return true; - } - - if (direction === 0) { - if (classList.contains('focuscontainer-x')) { - return true; - } - if (classList.contains('focuscontainer-left')) { - return true; - } - } else if (direction === 1) { - if (classList.contains('focuscontainer-x')) { - return true; - } - if (classList.contains('focuscontainer-right')) { - return true; - } - } else if (direction === 2) { - if (classList.contains('focuscontainer-y')) { - return true; - } - } else if (direction === 3) { - if (classList.contains('focuscontainer-y')) { - return true; - } - if (classList.contains('focuscontainer-down')) { - return true; - } - } +// Determines if a focusable element can be focused at a given point in time +function isCurrentlyFocusableInternal(elem) { + // http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom + return elem.offsetParent !== null; +} +// Determines if a focusable element can be focused at a given point in time +function isCurrentlyFocusable(elem) { + if (elem.disabled) { return false; } - function getFocusContainer(elem, direction) { - while (!isFocusContainer(elem, direction)) { - elem = elem.parentNode; + if (elem.getAttribute('tabindex') === '-1') { + return false; + } - if (!elem) { - return getDefaultScope(); + if (elem.tagName === 'INPUT') { + const type = elem.type; + if (type === 'range') { + return false; + } + if (type === 'file') { + return false; + } + } + + return isCurrentlyFocusableInternal(elem); +} + +function getDefaultScope() { + return scopes[0] || document.body; +} + +function getFocusableElements(parent, limit, excludeClass) { + const elems = (parent || getDefaultScope()).querySelectorAll(focusableQuery); + const focusableElements = []; + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (excludeClass && elem.classList.contains(excludeClass)) { + continue; + } + + if (isCurrentlyFocusableInternal(elem)) { + focusableElements.push(elem); + + if (limit && focusableElements.length >= limit) { + break; } } - - return elem; } - function getOffset(elem) { - let box; + return focusableElements; +} - // Support: BlackBerry 5, iOS 3 (original iPhone) - // If we don't have gBCR, just use 0,0 rather than error - if (elem.getBoundingClientRect) { - box = elem.getBoundingClientRect(); - } else { - box = { - top: 0, - left: 0, - width: 0, - height: 0 - }; - } - - if (box.right === null) { - // Create a new object because some browsers will throw an error when trying to set data onto the Rect object - const newBox = { - top: box.top, - left: box.left, - width: box.width, - height: box.height - }; - - box = newBox; - - box.right = box.left + box.width; - box.bottom = box.top + box.height; - } - - return box; +function isFocusContainer(elem, direction) { + if (focusableContainerTagNames.indexOf(elem.tagName) !== -1) { + return true; } - function nav(activeElement, direction, container, focusableElements) { - activeElement = activeElement || document.activeElement; + const classList = elem.classList; + if (classList.contains('focuscontainer')) { + return true; + } + + if (direction === 0) { + if (classList.contains('focuscontainer-x')) { + return true; + } + if (classList.contains('focuscontainer-left')) { + return true; + } + } else if (direction === 1) { + if (classList.contains('focuscontainer-x')) { + return true; + } + if (classList.contains('focuscontainer-right')) { + return true; + } + } else if (direction === 2) { + if (classList.contains('focuscontainer-y')) { + return true; + } + } else if (direction === 3) { + if (classList.contains('focuscontainer-y')) { + return true; + } + if (classList.contains('focuscontainer-down')) { + return true; + } + } + + return false; +} + +function getFocusContainer(elem, direction) { + while (!isFocusContainer(elem, direction)) { + elem = elem.parentNode; + + if (!elem) { + return getDefaultScope(); + } + } + + return elem; +} + +function getOffset(elem) { + let box; + + // Support: BlackBerry 5, iOS 3 (original iPhone) + // If we don't have gBCR, just use 0,0 rather than error + if (elem.getBoundingClientRect) { + box = elem.getBoundingClientRect(); + } else { + box = { + top: 0, + left: 0, + width: 0, + height: 0 + }; + } + + if (box.right === null) { + // Create a new object because some browsers will throw an error when trying to set data onto the Rect object + const newBox = { + top: box.top, + left: box.left, + width: box.width, + height: box.height + }; + + box = newBox; + + box.right = box.left + box.width; + box.bottom = box.top + box.height; + } + + return box; +} + +function nav(activeElement, direction, container, focusableElements) { + activeElement = activeElement || document.activeElement; + + if (activeElement) { + activeElement = focusableParent(activeElement); + } + + container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope()); + + if (!activeElement) { + autoFocus(container, true, false); + return; + } + + const focusableContainer = dom.parentWithClass(activeElement, 'focusable'); + + const rect = getOffset(activeElement); + + // Get elements and work out x/y points + const point1x = parseFloat(rect.left) || 0; + const point1y = parseFloat(rect.top) || 0; + const point2x = parseFloat(point1x + rect.width - 1) || point1x; + const point2y = parseFloat(point1y + rect.height - 1) || point1y; + + const sourceMidX = rect.left + (rect.width / 2); + const sourceMidY = rect.top + (rect.height / 2); + + const focusable = focusableElements || container.querySelectorAll(focusableQuery); + + const maxDistance = Infinity; + let minDistance = maxDistance; + let nearestElement; + + for (let i = 0, length = focusable.length; i < length; i++) { + const curr = focusable[i]; + + if (curr === activeElement) { + continue; + } + // Don't refocus into the same container + if (curr === focusableContainer) { + continue; + } + + const elementRect = getOffset(curr); + + // not currently visible + if (!elementRect.width && !elementRect.height) { + continue; + } + + switch (direction) { + case 0: + // left + if (elementRect.left >= rect.left) { + continue; + } + if (elementRect.right === rect.right) { + continue; + } + break; + case 1: + // right + if (elementRect.right <= rect.right) { + continue; + } + if (elementRect.left === rect.left) { + continue; + } + break; + case 2: + // up + if (elementRect.top >= rect.top) { + continue; + } + if (elementRect.bottom >= rect.bottom) { + continue; + } + break; + case 3: + // down + if (elementRect.bottom <= rect.bottom) { + continue; + } + if (elementRect.top <= rect.top) { + continue; + } + break; + default: + break; + } + + const x = elementRect.left; + const y = elementRect.top; + const x2 = x + elementRect.width - 1; + const y2 = y + elementRect.height - 1; + + const intersectX = intersects(point1x, point2x, x, x2); + const intersectY = intersects(point1y, point2y, y, y2); + + const midX = elementRect.left + (elementRect.width / 2); + const midY = elementRect.top + (elementRect.height / 2); + + let distX; + let distY; + + switch (direction) { + case 0: + // left + distX = Math.abs(point1x - Math.min(point1x, x2)); + distY = intersectY ? 0 : Math.abs(sourceMidY - midY); + break; + case 1: + // right + distX = Math.abs(point2x - Math.max(point2x, x)); + distY = intersectY ? 0 : Math.abs(sourceMidY - midY); + break; + case 2: + // up + distY = Math.abs(point1y - Math.min(point1y, y2)); + distX = intersectX ? 0 : Math.abs(sourceMidX - midX); + break; + case 3: + // down + distY = Math.abs(point2y - Math.max(point2y, y)); + distX = intersectX ? 0 : Math.abs(sourceMidX - midX); + break; + default: + break; + } + + const dist = Math.sqrt(distX * distX + distY * distY); + + if (dist < minDistance) { + nearestElement = curr; + minDistance = dist; + } + } + + if (nearestElement) { + // See if there's a focusable container, and if so, send the focus command to that if (activeElement) { - activeElement = focusableParent(activeElement); - } - - container = container || (activeElement ? getFocusContainer(activeElement, direction) : getDefaultScope()); - - if (!activeElement) { - autoFocus(container, true, false); - return; - } - - const focusableContainer = dom.parentWithClass(activeElement, 'focusable'); - - const rect = getOffset(activeElement); - - // Get elements and work out x/y points - const point1x = parseFloat(rect.left) || 0; - const point1y = parseFloat(rect.top) || 0; - const point2x = parseFloat(point1x + rect.width - 1) || point1x; - const point2y = parseFloat(point1y + rect.height - 1) || point1y; - - const sourceMidX = rect.left + (rect.width / 2); - const sourceMidY = rect.top + (rect.height / 2); - - const focusable = focusableElements || container.querySelectorAll(focusableQuery); - - const maxDistance = Infinity; - let minDistance = maxDistance; - let nearestElement; - - for (let i = 0, length = focusable.length; i < length; i++) { - const curr = focusable[i]; - - if (curr === activeElement) { - continue; - } - // Don't refocus into the same container - if (curr === focusableContainer) { - continue; - } - - const elementRect = getOffset(curr); - - // not currently visible - if (!elementRect.width && !elementRect.height) { - continue; - } - - switch (direction) { - case 0: - // left - if (elementRect.left >= rect.left) { - continue; - } - if (elementRect.right === rect.right) { - continue; - } - break; - case 1: - // right - if (elementRect.right <= rect.right) { - continue; - } - if (elementRect.left === rect.left) { - continue; - } - break; - case 2: - // up - if (elementRect.top >= rect.top) { - continue; - } - if (elementRect.bottom >= rect.bottom) { - continue; - } - break; - case 3: - // down - if (elementRect.bottom <= rect.bottom) { - continue; - } - if (elementRect.top <= rect.top) { - continue; - } - break; - default: - break; - } - - const x = elementRect.left; - const y = elementRect.top; - const x2 = x + elementRect.width - 1; - const y2 = y + elementRect.height - 1; - - const intersectX = intersects(point1x, point2x, x, x2); - const intersectY = intersects(point1y, point2y, y, y2); - - const midX = elementRect.left + (elementRect.width / 2); - const midY = elementRect.top + (elementRect.height / 2); - - let distX; - let distY; - - switch (direction) { - case 0: - // left - distX = Math.abs(point1x - Math.min(point1x, x2)); - distY = intersectY ? 0 : Math.abs(sourceMidY - midY); - break; - case 1: - // right - distX = Math.abs(point2x - Math.max(point2x, x)); - distY = intersectY ? 0 : Math.abs(sourceMidY - midY); - break; - case 2: - // up - distY = Math.abs(point1y - Math.min(point1y, y2)); - distX = intersectX ? 0 : Math.abs(sourceMidX - midX); - break; - case 3: - // down - distY = Math.abs(point2y - Math.max(point2y, y)); - distX = intersectX ? 0 : Math.abs(sourceMidX - midX); - break; - default: - break; - } - - const dist = Math.sqrt(distX * distX + distY * distY); - - if (dist < minDistance) { - nearestElement = curr; - minDistance = dist; - } - } - - if (nearestElement) { - // See if there's a focusable container, and if so, send the focus command to that - if (activeElement) { - const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable'); - if (nearestElementFocusableParent + const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable'); + if (nearestElementFocusableParent && nearestElementFocusableParent !== nearestElement && focusableContainer !== nearestElementFocusableParent - ) { - nearestElement = nearestElementFocusableParent; - } - } - focus(nearestElement); - } - } - - function intersectsInternal(a1, a2, b1, b2) { - return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2); - } - - function intersects(a1, a2, b1, b2) { - return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2); - } - - function sendText(text) { - const elem = document.activeElement; - - elem.value = text; - } - - function focusFirst(container, focusableSelector) { - const elems = container.querySelectorAll(focusableSelector); - - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[i]; - - if (isCurrentlyFocusableInternal(elem)) { - focus(elem); - break; + ) { + nearestElement = nearestElementFocusableParent; } } + focus(nearestElement); } +} - function focusLast(container, focusableSelector) { - const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(); +function intersectsInternal(a1, a2, b1, b2) { + return (b1 >= a1 && b1 <= a2) || (b2 >= a1 && b2 <= a2); +} - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[i]; +function intersects(a1, a2, b1, b2) { + return intersectsInternal(a1, a2, b1, b2) || intersectsInternal(b1, b2, a1, a2); +} - if (isCurrentlyFocusableInternal(elem)) { - focus(elem); - break; - } +function sendText(text) { + const elem = document.activeElement; + + elem.value = text; +} + +function focusFirst(container, focusableSelector) { + const elems = container.querySelectorAll(focusableSelector); + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + focus(elem); + break; + } + } +} + +function focusLast(container, focusableSelector) { + const elems = [].slice.call(container.querySelectorAll(focusableSelector), 0).reverse(); + + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + focus(elem); + break; + } + } +} + +function moveFocus(sourceElement, container, focusableSelector, offset) { + const elems = container.querySelectorAll(focusableSelector); + const list = []; + let i; + let length; + let elem; + + for (i = 0, length = elems.length; i < length; i++) { + elem = elems[i]; + + if (isCurrentlyFocusableInternal(elem)) { + list.push(elem); } } - function moveFocus(sourceElement, container, focusableSelector, offset) { - const elems = container.querySelectorAll(focusableSelector); - const list = []; - let i; - let length; - let elem; + let currentIndex = -1; - for (i = 0, length = elems.length; i < length; i++) { - elem = elems[i]; + for (i = 0, length = list.length; i < length; i++) { + elem = list[i]; - if (isCurrentlyFocusableInternal(elem)) { - list.push(elem); - } - } - - let currentIndex = -1; - - for (i = 0, length = list.length; i < length; i++) { - elem = list[i]; - - if (sourceElement === elem || elem.contains(sourceElement)) { - currentIndex = i; - break; - } - } - - if (currentIndex === -1) { - return; - } - - let newIndex = currentIndex + offset; - newIndex = Math.max(0, newIndex); - newIndex = Math.min(newIndex, list.length - 1); - - const newElem = list[newIndex]; - if (newElem) { - focus(newElem); + if (sourceElement === elem || elem.contains(sourceElement)) { + currentIndex = i; + break; } } -/* eslint-enable indent */ + if (currentIndex === -1) { + return; + } + + let newIndex = currentIndex + offset; + newIndex = Math.max(0, newIndex); + newIndex = Math.min(newIndex, list.length - 1); + + const newElem = list[newIndex]; + if (newElem) { + focus(newElem); + } +} export default { autoFocus: autoFocus, diff --git a/src/components/groupedcards.js b/src/components/groupedcards.js index 295c3170cc..a2f7b757e0 100644 --- a/src/components/groupedcards.js +++ b/src/components/groupedcards.js @@ -1,47 +1,43 @@ -/* eslint-disable indent */ - import dom from '../scripts/dom'; import { appRouter } from './appRouter'; import Dashboard from '../utils/dashboard'; import ServerConnections from './ServerConnections'; - function onGroupedCardClick(e, card) { - const itemId = card.getAttribute('data-id'); - const serverId = card.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - const userId = apiClient.getCurrentUserId(); - const playedIndicator = card.querySelector('.playedIndicator'); - const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; - const options = { - Limit: parseInt(playedIndicatorHtml || '10', 10), - Fields: 'PrimaryImageAspectRatio,DateCreated', - ParentId: itemId, - GroupItems: false - }; - const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']); +function onGroupedCardClick(e, card) { + const itemId = card.getAttribute('data-id'); + const serverId = card.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + const userId = apiClient.getCurrentUserId(); + const playedIndicator = card.querySelector('.playedIndicator'); + const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; + const options = { + Limit: parseInt(playedIndicatorHtml || '10', 10), + Fields: 'PrimaryImageAspectRatio,DateCreated', + ParentId: itemId, + GroupItems: false + }; + const actionableParent = dom.parentWithTag(e.target, ['A', 'BUTTON', 'INPUT']); - if (!actionableParent || actionableParent.classList.contains('cardContent')) { - apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - if (items.length === 1) { - appRouter.showItem(items[0]); - return; - } + if (!actionableParent || actionableParent.classList.contains('cardContent')) { + apiClient.getJSON(apiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + if (items.length === 1) { + appRouter.showItem(items[0]); + return; + } - const url = 'details?id=' + itemId + '&serverId=' + serverId; - Dashboard.navigate(url); - }); - e.stopPropagation(); - e.preventDefault(); - return false; - } + const url = 'details?id=' + itemId + '&serverId=' + serverId; + Dashboard.navigate(url); + }); + e.stopPropagation(); + e.preventDefault(); + return false; } +} - export default function onItemsContainerClick(e) { - const groupedCard = dom.parentWithClass(e.target, 'groupedCard'); +export default function onItemsContainerClick(e) { + const groupedCard = dom.parentWithClass(e.target, 'groupedCard'); - if (groupedCard) { - onGroupedCardClick(e, groupedCard); - } + if (groupedCard) { + onGroupedCardClick(e, groupedCard); } - -/* eslint-enable indent */ +} diff --git a/src/components/homeScreenSettings/homeScreenSettings.js b/src/components/homeScreenSettings/homeScreenSettings.js index a28a18438c..64bc69a810 100644 --- a/src/components/homeScreenSettings/homeScreenSettings.js +++ b/src/components/homeScreenSettings/homeScreenSettings.js @@ -14,498 +14,494 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './homeScreenSettings.template.html'; -/* eslint-disable indent */ +const numConfigurableSections = 7; - const numConfigurableSections = 7; +function renderViews(page, user, result) { + let folderHtml = ''; - function renderViews(page, user, result) { - let folderHtml = ''; + folderHtml += '
'; + folderHtml += result.map(i => { + let currentHtml = ''; - folderHtml += '
'; - folderHtml += result.map(i => { - let currentHtml = ''; + const id = `chkGroupFolder${i.Id}`; - const id = `chkGroupFolder${i.Id}`; + const isChecked = user.Configuration.GroupedFolders.includes(i.Id); - const isChecked = user.Configuration.GroupedFolders.includes(i.Id); + const checkedHtml = isChecked ? ' checked="checked"' : ''; - const checkedHtml = isChecked ? ' checked="checked"' : ''; + currentHtml += ''; - currentHtml += ''; + return currentHtml; + }).join(''); - return currentHtml; - }).join(''); + folderHtml += '
'; - folderHtml += '
'; + page.querySelector('.folderGroupList').innerHTML = folderHtml; +} - page.querySelector('.folderGroupList').innerHTML = folderHtml; +function getLandingScreenOptions(type) { + const list = []; + + if (type === 'movies') { + list.push({ + name: globalize.translate('Movies'), + value: 'movies', + isDefault: true + }); + list.push({ + name: globalize.translate('Suggestions'), + value: 'suggestions' + }); + list.push({ + name: globalize.translate('Trailers'), + value: 'trailers' + }); + list.push({ + name: globalize.translate('Favorites'), + value: 'favorites' + }); + list.push({ + name: globalize.translate('Collections'), + value: 'collections' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + } else if (type === 'tvshows') { + list.push({ + name: globalize.translate('Shows'), + value: 'shows', + isDefault: true + }); + list.push({ + name: globalize.translate('Suggestions'), + value: 'suggestions' + }); + list.push({ + name: globalize.translate('TabUpcoming'), + value: 'upcoming' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + list.push({ + name: globalize.translate('TabNetworks'), + value: 'networks' + }); + list.push({ + name: globalize.translate('Episodes'), + value: 'episodes' + }); + } else if (type === 'music') { + list.push({ + name: globalize.translate('Albums'), + value: 'albums', + isDefault: true + }); + list.push({ + name: globalize.translate('Suggestions'), + value: 'suggestions' + }); + list.push({ + name: globalize.translate('HeaderAlbumArtists'), + value: 'albumartists' + }); + list.push({ + name: globalize.translate('Artists'), + value: 'artists' + }); + list.push({ + name: globalize.translate('Playlists'), + value: 'playlists' + }); + list.push({ + name: globalize.translate('Songs'), + value: 'songs' + }); + list.push({ + name: globalize.translate('Genres'), + value: 'genres' + }); + } else if (type === 'livetv') { + list.push({ + name: globalize.translate('Programs'), + value: 'programs', + isDefault: true + }); + list.push({ + name: globalize.translate('Guide'), + value: 'guide' + }); + list.push({ + name: globalize.translate('Channels'), + value: 'channels' + }); + list.push({ + name: globalize.translate('Recordings'), + value: 'recordings' + }); + list.push({ + name: globalize.translate('Schedule'), + value: 'schedule' + }); + list.push({ + name: globalize.translate('Series'), + value: 'series' + }); } - function getLandingScreenOptions(type) { - const list = []; + return list; +} - if (type === 'movies') { - list.push({ - name: globalize.translate('Movies'), - value: 'movies', - isDefault: true - }); - list.push({ - name: globalize.translate('Suggestions'), - value: 'suggestions' - }); - list.push({ - name: globalize.translate('Trailers'), - value: 'trailers' - }); - list.push({ - name: globalize.translate('Favorites'), - value: 'favorites' - }); - list.push({ - name: globalize.translate('Collections'), - value: 'collections' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - } else if (type === 'tvshows') { - list.push({ - name: globalize.translate('Shows'), - value: 'shows', - isDefault: true - }); - list.push({ - name: globalize.translate('Suggestions'), - value: 'suggestions' - }); - list.push({ - name: globalize.translate('TabUpcoming'), - value: 'upcoming' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - list.push({ - name: globalize.translate('TabNetworks'), - value: 'networks' - }); - list.push({ - name: globalize.translate('Episodes'), - value: 'episodes' - }); - } else if (type === 'music') { - list.push({ - name: globalize.translate('Albums'), - value: 'albums', - isDefault: true - }); - list.push({ - name: globalize.translate('Suggestions'), - value: 'suggestions' - }); - list.push({ - name: globalize.translate('HeaderAlbumArtists'), - value: 'albumartists' - }); - list.push({ - name: globalize.translate('Artists'), - value: 'artists' - }); - list.push({ - name: globalize.translate('Playlists'), - value: 'playlists' - }); - list.push({ - name: globalize.translate('Songs'), - value: 'songs' - }); - list.push({ - name: globalize.translate('Genres'), - value: 'genres' - }); - } else if (type === 'livetv') { - list.push({ - name: globalize.translate('Programs'), - value: 'programs', - isDefault: true - }); - list.push({ - name: globalize.translate('Guide'), - value: 'guide' - }); - list.push({ - name: globalize.translate('Channels'), - value: 'channels' - }); - list.push({ - name: globalize.translate('Recordings'), - value: 'recordings' - }); - list.push({ - name: globalize.translate('Schedule'), - value: 'schedule' - }); - list.push({ - name: globalize.translate('Series'), - value: 'series' - }); +function getLandingScreenOptionsHtml(type, userValue) { + return getLandingScreenOptions(type).map(o => { + const selected = userValue === o.value || (o.isDefault && !userValue); + const selectedHtml = selected ? ' selected' : ''; + const optionValue = o.isDefault ? '' : o.value; + + return ``; + }).join(''); +} + +function renderViewOrder(context, user, result) { + let html = ''; + + html += result.Items.map((view) => { + let currentHtml = ''; + + currentHtml += `
`; + + currentHtml += ''; + + currentHtml += '
'; + + currentHtml += '
'; + currentHtml += escapeHtml(view.Name); + currentHtml += '
'; + + currentHtml += '
'; + + currentHtml += ``; + currentHtml += ``; + + currentHtml += '
'; + + return currentHtml; + }).join(''); + + context.querySelector('.viewOrderList').innerHTML = html; +} + +function updateHomeSectionValues(context, userSettings) { + for (let i = 1; i <= 7; i++) { + const select = context.querySelector(`#selectHomeSection${i}`); + const defaultValue = homeSections.getDefaultSection(i - 1); + + const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]'); + + const userValue = userSettings.get(`homesection${i - 1}`); + + option.value = ''; + + if (userValue === defaultValue || !userValue) { + select.value = ''; + } else { + select.value = userValue; } - - return list; } - function getLandingScreenOptionsHtml(type, userValue) { - return getLandingScreenOptions(type).map(o => { - const selected = userValue === o.value || (o.isDefault && !userValue); - const selectedHtml = selected ? ' selected' : ''; - const optionValue = o.isDefault ? '' : o.value; + context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || ''; +} - return ``; - }).join(''); +function getPerLibrarySettingsHtml(item, user, userSettings) { + let html = ''; + + let isChecked; + + if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') { + isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id); + html += '
'; + html += ''; + html += '
'; } - function renderViewOrder(context, user, result) { - let html = ''; - - html += result.Items.map((view) => { - let currentHtml = ''; - - currentHtml += `
`; - - currentHtml += ''; - - currentHtml += '
'; - - currentHtml += '
'; - currentHtml += escapeHtml(view.Name); - currentHtml += '
'; - - currentHtml += '
'; - - currentHtml += ``; - currentHtml += ``; - - currentHtml += '
'; - - return currentHtml; - }).join(''); - - context.querySelector('.viewOrderList').innerHTML = html; + const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels']; + if (!excludeFromLatest.includes(item.CollectionType || '')) { + isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id); + html += ''; } - function updateHomeSectionValues(context, userSettings) { - for (let i = 1; i <= 7; i++) { - const select = context.querySelector(`#selectHomeSection${i}`); - const defaultValue = homeSections.getDefaultSection(i - 1); + if (html) { + html = `
${html}
`; + } - const option = select.querySelector(`option[value=${defaultValue}]`) || select.querySelector('option[value=""]'); + if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') { + const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id; + html += '
'; + html += `'; + html += '
'; + } + + if (html) { + let prefix = ''; + prefix += '
'; + + prefix += '

'; + prefix += escapeHtml(item.Name); + prefix += '

'; + + html = prefix + html; + html += '
'; + } + + return html; +} + +function renderPerLibrarySettings(context, user, userViews, userSettings) { + const elem = context.querySelector('.perLibrarySettings'); + let html = ''; + + for (let i = 0, length = userViews.length; i < length; i++) { + html += getPerLibrarySettingsHtml(userViews[i], user, userSettings); + } + + elem.innerHTML = html; +} + +function loadForm(context, user, userSettings, apiClient) { + context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false; + + updateHomeSectionValues(context, userSettings); + + const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); + const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`)); + + Promise.all([promise1, promise2]).then(responses => { + renderViewOrder(context, user, responses[0]); + + renderPerLibrarySettings(context, user, responses[0].Items, userSettings); + + renderViews(context, user, responses[1]); + + loading.hide(); + }); +} + +function onSectionOrderListClick(e) { + const target = dom.parentWithClass(e.target, 'btnViewItemMove'); + + if (target) { + const viewItem = dom.parentWithClass(target, 'viewItem'); + + if (viewItem) { + if (target.classList.contains('btnViewItemDown')) { + const next = viewItem.nextSibling; + + if (next) { + viewItem.parentNode.removeChild(viewItem); + next.parentNode.insertBefore(viewItem, next.nextSibling); + focusManager.focus(e.target); + } } else { - select.value = userValue; + const prev = viewItem.previousSibling; + + if (prev) { + viewItem.parentNode.removeChild(viewItem); + prev.parentNode.insertBefore(viewItem, prev); + focusManager.focus(e.target); + } } } + } +} - context.querySelector('.selectTVHomeScreen').value = userSettings.get('tvhome') || ''; +function getCheckboxItems(selector, context, isChecked) { + const inputs = context.querySelectorAll(selector); + const list = []; + + for (let i = 0, length = inputs.length; i < length; i++) { + if (inputs[i].checked === isChecked) { + list.push(inputs[i]); + } } - function getPerLibrarySettingsHtml(item, user, userSettings) { - let html = ''; + return list; +} - let isChecked; +function saveUser(context, user, userSettingsInstance, apiClient) { + user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked; - if (item.Type === 'Channel' || item.CollectionType === 'boxsets' || item.CollectionType === 'playlists') { - isChecked = !(user.Configuration.MyMediaExcludes || []).includes(item.Id); - html += '
'; - html += ''; - html += '
'; - } + user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => { + return i.getAttribute('data-folderid'); + }); - const excludeFromLatest = ['playlists', 'livetv', 'boxsets', 'channels']; - if (!excludeFromLatest.includes(item.CollectionType || '')) { - isChecked = !user.Configuration.LatestItemsExcludes.includes(item.Id); - html += ''; - } + user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => { + return i.getAttribute('data-folderid'); + }); - if (html) { - html = `
${html}
`; - } + user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => { + return i.getAttribute('data-folderid'); + }); - if (item.CollectionType === 'movies' || item.CollectionType === 'tvshows' || item.CollectionType === 'music' || item.CollectionType === 'livetv') { - const idForLanding = item.CollectionType === 'livetv' ? item.CollectionType : item.Id; - html += '
'; - html += `'; - html += '
'; - } - - if (html) { - let prefix = ''; - prefix += '
'; - - prefix += '

'; - prefix += escapeHtml(item.Name); - prefix += '

'; - - html = prefix + html; - html += '
'; - } - - return html; + const viewItems = context.querySelectorAll('.viewItem'); + const orderedViews = []; + let i; + let length; + for (i = 0, length = viewItems.length; i < length; i++) { + orderedViews.push(viewItems[i].getAttribute('data-viewid')); } - function renderPerLibrarySettings(context, user, userViews, userSettings) { - const elem = context.querySelector('.perLibrarySettings'); - let html = ''; + user.Configuration.OrderedViews = orderedViews; - for (let i = 0, length = userViews.length; i < length; i++) { - html += getPerLibrarySettingsHtml(userViews[i], user, userSettings); - } + userSettingsInstance.set('tvhome', context.querySelector('.selectTVHomeScreen').value); - elem.innerHTML = html; + userSettingsInstance.set('homesection0', context.querySelector('#selectHomeSection1').value); + userSettingsInstance.set('homesection1', context.querySelector('#selectHomeSection2').value); + userSettingsInstance.set('homesection2', context.querySelector('#selectHomeSection3').value); + userSettingsInstance.set('homesection3', context.querySelector('#selectHomeSection4').value); + userSettingsInstance.set('homesection4', context.querySelector('#selectHomeSection5').value); + userSettingsInstance.set('homesection5', context.querySelector('#selectHomeSection6').value); + userSettingsInstance.set('homesection6', context.querySelector('#selectHomeSection7').value); + + const selectLandings = context.querySelectorAll('.selectLanding'); + for (i = 0, length = selectLandings.length; i < length; i++) { + const selectLanding = selectLandings[i]; + userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value); } - function loadForm(context, user, userSettings, apiClient) { - context.querySelector('.chkHidePlayedFromLatest').checked = user.Configuration.HidePlayedInLatest || false; + return apiClient.updateUserConfiguration(user.Id, user.Configuration); +} - updateHomeSectionValues(context, userSettings); +function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + loading.show(); - const promise1 = apiClient.getUserViews({ IncludeHidden: true }, user.Id); - const promise2 = apiClient.getJSON(apiClient.getUrl(`Users/${user.Id}/GroupingOptions`)); - - Promise.all([promise1, promise2]).then(responses => { - renderViewOrder(context, user, responses[0]); - - renderPerLibrarySettings(context, user, responses[0].Items, userSettings); - - renderViews(context, user, responses[1]); + apiClient.getUser(userId).then(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + Events.trigger(instance, 'saved'); + }, () => { loading.hide(); }); + }); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const enableSaveConfirmation = self.options.enableSaveConfirmation; + save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); + }); + + // Disable default form submission + if (e) { + e.preventDefault(); + } + return false; +} + +function onChange(e) { + const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia'); + if (!chkIncludeInMyMedia) { + return; } - function onSectionOrderListClick(e) { - const target = dom.parentWithClass(e.target, 'btnViewItemMove'); - - if (target) { - const viewItem = dom.parentWithClass(target, 'viewItem'); - - if (viewItem) { - if (target.classList.contains('btnViewItemDown')) { - const next = viewItem.nextSibling; - - if (next) { - viewItem.parentNode.removeChild(viewItem); - next.parentNode.insertBefore(viewItem, next.nextSibling); - focusManager.focus(e.target); - } - } else { - const prev = viewItem.previousSibling; - - if (prev) { - viewItem.parentNode.removeChild(viewItem); - prev.parentNode.insertBefore(viewItem, prev); - focusManager.focus(e.target); - } - } - } + const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection'); + const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest'); + if (fldIncludeInLatest) { + if (chkIncludeInMyMedia.checked) { + fldIncludeInLatest.classList.remove('hide'); + } else { + fldIncludeInLatest.classList.add('hide'); } } +} - function getCheckboxItems(selector, context, isChecked) { - const inputs = context.querySelectorAll(selector); - const list = []; - - for (let i = 0, length = inputs.length; i < length; i++) { - if (inputs[i].checked === isChecked) { - list.push(inputs[i]); - } - } - - return list; +function embed(options, self) { + let workingTemplate = template; + for (let i = 1; i <= numConfigurableSections; i++) { + workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i)); } - function saveUser(context, user, userSettingsInstance, apiClient) { - user.Configuration.HidePlayedInLatest = context.querySelector('.chkHidePlayedFromLatest').checked; + options.element.innerHTML = globalize.translateHtml(workingTemplate, 'core'); - user.Configuration.LatestItemsExcludes = getCheckboxItems('.chkIncludeInLatest', context, false).map(i => { - return i.getAttribute('data-folderid'); - }); + options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick); + options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); + options.element.addEventListener('change', onChange); - user.Configuration.MyMediaExcludes = getCheckboxItems('.chkIncludeInMyMedia', context, false).map(i => { - return i.getAttribute('data-folderid'); - }); - - user.Configuration.GroupedFolders = getCheckboxItems('.chkGroupFolder', context, true).map(i => { - return i.getAttribute('data-folderid'); - }); - - const viewItems = context.querySelectorAll('.viewItem'); - const orderedViews = []; - let i; - let length; - for (i = 0, length = viewItems.length; i < length; i++) { - orderedViews.push(viewItems[i].getAttribute('data-viewid')); - } - - user.Configuration.OrderedViews = orderedViews; - - userSettingsInstance.set('tvhome', context.querySelector('.selectTVHomeScreen').value); - - userSettingsInstance.set('homesection0', context.querySelector('#selectHomeSection1').value); - userSettingsInstance.set('homesection1', context.querySelector('#selectHomeSection2').value); - userSettingsInstance.set('homesection2', context.querySelector('#selectHomeSection3').value); - userSettingsInstance.set('homesection3', context.querySelector('#selectHomeSection4').value); - userSettingsInstance.set('homesection4', context.querySelector('#selectHomeSection5').value); - userSettingsInstance.set('homesection5', context.querySelector('#selectHomeSection6').value); - userSettingsInstance.set('homesection6', context.querySelector('#selectHomeSection7').value); - - const selectLandings = context.querySelectorAll('.selectLanding'); - for (i = 0, length = selectLandings.length; i < length; i++) { - const selectLanding = selectLandings[i]; - userSettingsInstance.set(`landing-${selectLanding.getAttribute('data-folderid')}`, selectLanding.value); - } - - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + if (options.enableSaveButton) { + options.element.querySelector('.btnSave').classList.remove('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + if (layoutManager.tv) { + options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide'); + } else { + options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide'); + } + + self.loadData(options.autoFocus); +} + +class HomeScreenSettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData(autoFocus) { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const enableSaveConfirmation = self.options.enableSaveConfirmation; - save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); - }); + apiClient.getUser(userId).then(user => { + userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; - // Disable default form submission - if (e) { - e.preventDefault(); - } - return false; - } + loadForm(context, user, userSettings, apiClient); - function onChange(e) { - const chkIncludeInMyMedia = dom.parentWithClass(e.target, 'chkIncludeInMyMedia'); - if (!chkIncludeInMyMedia) { - return; - } - - const section = dom.parentWithClass(chkIncludeInMyMedia, 'verticalSection'); - const fldIncludeInLatest = section.querySelector('.fldIncludeInLatest'); - if (fldIncludeInLatest) { - if (chkIncludeInMyMedia.checked) { - fldIncludeInLatest.classList.remove('hide'); - } else { - fldIncludeInLatest.classList.add('hide'); - } - } - } - - function embed(options, self) { - let workingTemplate = template; - for (let i = 1; i <= numConfigurableSections; i++) { - workingTemplate = workingTemplate.replace(`{section${i}label}`, globalize.translate('LabelHomeScreenSectionValue', i)); - } - - options.element.innerHTML = globalize.translateHtml(workingTemplate, 'core'); - - options.element.querySelector('.viewOrderList').addEventListener('click', onSectionOrderListClick); - options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); - options.element.addEventListener('change', onChange); - - if (options.enableSaveButton) { - options.element.querySelector('.btnSave').classList.remove('hide'); - } - - if (layoutManager.tv) { - options.element.querySelector('.selectTVHomeScreenContainer').classList.remove('hide'); - } else { - options.element.querySelector('.selectTVHomeScreenContainer').classList.add('hide'); - } - - self.loadData(options.autoFocus); - } - - class HomeScreenSettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData(autoFocus) { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - apiClient.getUser(userId).then(user => { - userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - - loadForm(context, user, userSettings, apiClient); - - if (autoFocus) { - focusManager.autoFocus(context); - } - }); + if (autoFocus) { + focusManager.autoFocus(context); + } }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} export default HomeScreenSettings; diff --git a/src/components/homesections/homesections.js b/src/components/homesections/homesections.js index 86e154f7b6..5cf83ced94 100644 --- a/src/components/homesections/homesections.js +++ b/src/components/homesections/homesections.js @@ -13,733 +13,731 @@ import './homesections.scss'; import Dashboard from '../../utils/dashboard'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +export function getDefaultSection(index) { + switch (index) { + case 0: + return 'smalllibrarytiles'; + case 1: + return 'resume'; + case 2: + return 'resumeaudio'; + case 3: + return 'resumebook'; + case 4: + return 'livetv'; + case 5: + return 'nextup'; + case 6: + return 'latestmedia'; + case 7: + return 'none'; + default: + return ''; + } +} - export function getDefaultSection(index) { - switch (index) { - case 0: - return 'smalllibrarytiles'; - case 1: - return 'resume'; - case 2: - return 'resumeaudio'; - case 3: - return 'resumebook'; - case 4: - return 'livetv'; - case 5: - return 'nextup'; - case 6: - return 'latestmedia'; - case 7: - return 'none'; - default: - return ''; +function getAllSectionsToShow(userSettings, sectionCount) { + const sections = []; + for (let i = 0, length = sectionCount; i < length; i++) { + let section = userSettings.get('homesection' + i) || getDefaultSection(i); + if (section === 'folders') { + section = getDefaultSection(0); } + + sections.push(section); } - function getAllSectionsToShow(userSettings, sectionCount) { - const sections = []; - for (let i = 0, length = sectionCount; i < length; i++) { - let section = userSettings.get('homesection' + i) || getDefaultSection(i); - if (section === 'folders') { - section = getDefaultSection(0); + return sections; +} + +export function loadSections(elem, apiClient, user, userSettings) { + return getUserViews(apiClient, user.Id).then(function (userViews) { + let html = ''; + + if (userViews.length) { + const sectionCount = 7; + for (let i = 0; i < sectionCount; i++) { + html += '
'; } - sections.push(section); - } + elem.innerHTML = html; + elem.classList.add('homeSectionsContainer'); - return sections; - } + const promises = []; + const sections = getAllSectionsToShow(userSettings, sectionCount); + for (let i = 0; i < sections.length; i++) { + promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i)); + } - export function loadSections(elem, apiClient, user, userSettings) { - return getUserViews(apiClient, user.Id).then(function (userViews) { - let html = ''; - - if (userViews.length) { - const sectionCount = 7; - for (let i = 0; i < sectionCount; i++) { - html += '
'; - } - - elem.innerHTML = html; - elem.classList.add('homeSectionsContainer'); - - const promises = []; - const sections = getAllSectionsToShow(userSettings, sectionCount); - for (let i = 0; i < sections.length; i++) { - promises.push(loadSection(elem, apiClient, user, userSettings, userViews, sections, i)); - } - - return Promise.all(promises).then(function () { - return resume(elem, { - refresh: true - }); + return Promise.all(promises).then(function () { + return resume(elem, { + refresh: true }); - } else { - let noLibDescription; - if (user['Policy'] && user['Policy']['IsAdministrator']) { - noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); - } else { - noLibDescription = globalize.translate('AskAdminToCreateLibrary'); - } - - html += '
'; - html += '

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

'; - html += '

' + noLibDescription + '

'; - html += '
'; - elem.innerHTML = html; - - const createNowLink = elem.querySelector('#button-createLibrary'); - if (createNowLink) { - createNowLink.addEventListener('click', function () { - Dashboard.navigate('library.html'); - }); - } - } - }); - } - - export function destroySections(elem) { - const elems = elem.querySelectorAll('.itemsContainer'); - for (let i = 0; i < elems.length; i++) { - elems[i].fetchData = null; - elems[i].parentContainer = null; - elems[i].getItemsHtml = null; - } - - elem.innerHTML = ''; - } - - export function pause(elem) { - const elems = elem.querySelectorAll('.itemsContainer'); - for (let i = 0; i < elems.length; i++) { - elems[i].pause(); - } - } - - export function resume(elem, options) { - const elems = elem.querySelectorAll('.itemsContainer'); - const promises = []; - - for (let i = 0, length = elems.length; i < length; i++) { - promises.push(elems[i].resume(options)); - } - - return Promise.all(promises); - } - - function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) { - const section = allSections[index]; - const elem = page.querySelector('.section' + index); - - if (section === 'latestmedia') { - loadRecentlyAdded(elem, apiClient, user, userViews); - } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { - loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews); - } else if (section === 'librarybuttons') { - loadlibraryButtons(elem, apiClient, user, userSettings, userViews); - } else if (section === 'resume') { - return loadResume(elem, apiClient, 'HeaderContinueWatching', 'Video', userSettings); - } else if (section === 'resumeaudio') { - return loadResume(elem, apiClient, 'HeaderContinueListening', 'Audio', userSettings); - } else if (section === 'activerecordings') { - loadLatestLiveTvRecordings(elem, true, apiClient); - } else if (section === 'nextup') { - loadNextUp(elem, apiClient, userSettings); - } else if (section === 'onnow' || section === 'livetv') { - return loadOnNow(elem, apiClient, user); - } else if (section === 'resumebook') { - return loadResume(elem, apiClient, 'HeaderContinueReading', 'Book', userSettings); + }); } else { - elem.innerHTML = ''; - return Promise.resolve(); + let noLibDescription; + if (user['Policy'] && user['Policy']['IsAdministrator']) { + noLibDescription = globalize.translate('NoCreatedLibraries', '
', ''); + } else { + noLibDescription = globalize.translate('AskAdminToCreateLibrary'); + } + + html += '
'; + html += '

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

'; + html += '

' + noLibDescription + '

'; + html += '
'; + elem.innerHTML = html; + + const createNowLink = elem.querySelector('#button-createLibrary'); + if (createNowLink) { + createNowLink.addEventListener('click', function () { + Dashboard.navigate('library.html'); + }); + } } + }); +} + +export function destroySections(elem) { + const elems = elem.querySelectorAll('.itemsContainer'); + for (let i = 0; i < elems.length; i++) { + elems[i].fetchData = null; + elems[i].parentContainer = null; + elems[i].getItemsHtml = null; + } + + elem.innerHTML = ''; +} + +export function pause(elem) { + const elems = elem.querySelectorAll('.itemsContainer'); + for (let i = 0; i < elems.length; i++) { + elems[i].pause(); + } +} + +export function resume(elem, options) { + const elems = elem.querySelectorAll('.itemsContainer'); + const promises = []; + + for (let i = 0, length = elems.length; i < length; i++) { + promises.push(elems[i].resume(options)); + } + + return Promise.all(promises); +} + +function loadSection(page, apiClient, user, userSettings, userViews, allSections, index) { + const section = allSections[index]; + const elem = page.querySelector('.section' + index); + + if (section === 'latestmedia') { + loadRecentlyAdded(elem, apiClient, user, userViews); + } else if (section === 'librarytiles' || section === 'smalllibrarytiles' || section === 'smalllibrarytiles-automobile' || section === 'librarytiles-automobile') { + loadLibraryTiles(elem, apiClient, user, userSettings, 'smallBackdrop', userViews); + } else if (section === 'librarybuttons') { + loadlibraryButtons(elem, apiClient, user, userSettings, userViews); + } else if (section === 'resume') { + return loadResume(elem, apiClient, 'HeaderContinueWatching', 'Video', userSettings); + } else if (section === 'resumeaudio') { + return loadResume(elem, apiClient, 'HeaderContinueListening', 'Audio', userSettings); + } else if (section === 'activerecordings') { + loadLatestLiveTvRecordings(elem, true, apiClient); + } else if (section === 'nextup') { + loadNextUp(elem, apiClient, userSettings); + } else if (section === 'onnow' || section === 'livetv') { + return loadOnNow(elem, apiClient, user); + } else if (section === 'resumebook') { + return loadResume(elem, apiClient, 'HeaderContinueReading', 'Book', userSettings); + } else { + elem.innerHTML = ''; return Promise.resolve(); } + return Promise.resolve(); +} - function getUserViews(apiClient, userId) { - return apiClient.getUserViews({}, userId || apiClient.getCurrentUserId()).then(function (result) { - return result.Items; - }); +function getUserViews(apiClient, userId) { + return apiClient.getUserViews({}, userId || apiClient.getCurrentUserId()).then(function (result) { + return result.Items; + }); +} + +function enableScrollX() { + return true; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} + +function getLibraryButtonsHtml(items) { + let html = ''; + + html += '
'; + html += '

' + globalize.translate('HeaderMyMedia') + '

'; + + html += '
'; + + // library card background images + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + const icon = imageHelper.getLibraryIcon(item.CollectionType); + html += '' + escapeHtml(item.Name) + ''; } - function enableScrollX() { - return true; - } + html += '
'; + html += '
'; - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } + return html; +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) { + elem.classList.remove('verticalSection'); + const html = getLibraryButtonsHtml(userViews); - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} - function getLibraryButtonsHtml(items) { - let html = ''; +function getFetchLatestItemsFn(serverId, parentId, collectionType) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + let limit = 16; - html += '
'; - html += '

' + globalize.translate('HeaderMyMedia') + '

'; - - html += '
'; - - // library card background images - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; - const icon = imageHelper.getLibraryIcon(item.CollectionType); - html += '' + escapeHtml(item.Name) + ''; - } - - html += '
'; - html += '
'; - - return html; - } - - function loadlibraryButtons(elem, apiClient, user, userSettings, userViews) { - elem.classList.remove('verticalSection'); - const html = getLibraryButtonsHtml(userViews); - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - function getFetchLatestItemsFn(serverId, parentId, collectionType) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - let limit = 16; - - if (enableScrollX()) { - if (collectionType === 'music') { - limit = 30; - } - } else { - if (collectionType === 'tvshows') { - limit = 5; - } else if (collectionType === 'music') { - limit = 9; - } else { - limit = 8; - } + if (enableScrollX()) { + if (collectionType === 'music') { + limit = 30; } - - const options = { - Limit: limit, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - ParentId: parentId - }; - - return apiClient.getLatestItems(options); - }; - } - - function getLatestItemsHtmlFn(itemType, viewType) { - return function (items) { - const cardLayout = false; - let shape; - if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { - shape = getPortraitShape(); - } else if (viewType === 'music' || viewType === 'homevideos') { - shape = getSquareShape(); - } else { - shape = getThumbShape(); - } - - return cardBuilder.getCardsHtml({ - items: items, - shape: shape, - preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, - showUnplayedIndicator: false, - showChildCountIndicator: true, - context: 'home', - overlayText: false, - centerText: !cardLayout, - overlayPlayButton: viewType !== 'photos', - allowBottomPadding: !enableScrollX() && !cardLayout, - cardLayout: cardLayout, - showTitle: viewType !== 'photos', - showYear: viewType === 'movies' || viewType === 'tvshows' || !viewType, - showParentTitle: viewType === 'music' || viewType === 'tvshows' || !viewType || (cardLayout && (viewType === 'tvshows')), - lines: 2 - }); - }; - } - - function renderLatestSection(elem, apiClient, user, parent) { - let html = ''; - - html += '
'; - if (!layoutManager.tv) { - html += ''; - html += '

'; - html += globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)); - html += '

'; - html += ''; - html += '
'; } else { - html += '

' + globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)) + '

'; + if (collectionType === 'tvshows') { + limit = 5; + } else if (collectionType === 'music') { + limit = 9; + } else { + limit = 8; + } } - html += '
'; + const options = { + Limit: limit, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo,Path', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + ParentId: parentId + }; + + return apiClient.getLatestItems(options); + }; +} + +function getLatestItemsHtmlFn(itemType, viewType) { + return function (items) { + const cardLayout = false; + let shape; + if (itemType === 'Channel' || viewType === 'movies' || viewType === 'books' || viewType === 'tvshows') { + shape = getPortraitShape(); + } else if (viewType === 'music' || viewType === 'homevideos') { + shape = getSquareShape(); + } else { + shape = getThumbShape(); + } + + return cardBuilder.getCardsHtml({ + items: items, + shape: shape, + preferThumb: viewType !== 'movies' && viewType !== 'tvshows' && itemType !== 'Channel' && viewType !== 'music' ? 'auto' : null, + showUnplayedIndicator: false, + showChildCountIndicator: true, + context: 'home', + overlayText: false, + centerText: !cardLayout, + overlayPlayButton: viewType !== 'photos', + allowBottomPadding: !enableScrollX() && !cardLayout, + cardLayout: cardLayout, + showTitle: viewType !== 'photos', + showYear: viewType === 'movies' || viewType === 'tvshows' || !viewType, + showParentTitle: viewType === 'music' || viewType === 'tvshows' || !viewType || (cardLayout && (viewType === 'tvshows')), + lines: 2 + }); + }; +} + +function renderLatestSection(elem, apiClient, user, parent) { + let html = ''; + + html += '
'; + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate('LatestFromLibrary', escapeHtml(parent.Name)) + '

'; + } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType); + itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType); + itemsContainer.parentContainer = elem; +} + +function loadRecentlyAdded(elem, apiClient, user, userViews) { + elem.classList.remove('verticalSection'); + const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels']; + + for (let i = 0, length = userViews.length; i < length; i++) { + const item = userViews[i]; + if (user.Configuration.LatestItemsExcludes.indexOf(item.Id) !== -1) { + continue; + } + + if (excludeViewTypes.indexOf(item.CollectionType || []) !== -1) { + continue; + } + + const frag = document.createElement('div'); + frag.classList.add('verticalSection'); + frag.classList.add('hide'); + elem.appendChild(frag); + + renderLatestSection(frag, apiClient, user, item); + } +} + +export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews) { + let html = ''; + if (userViews.length) { + html += '

' + globalize.translate('HeaderMyMedia') + '

'; if (enableScrollX()) { html += '
'; html += '
'; } else { - html += '
'; + html += '
'; } - if (enableScrollX()) { - html += '
'; - } - html += '
'; - - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getFetchLatestItemsFn(apiClient.serverId(), parent.Id, parent.CollectionType); - itemsContainer.getItemsHtml = getLatestItemsHtmlFn(parent.Type, parent.CollectionType); - itemsContainer.parentContainer = elem; - } - - function loadRecentlyAdded(elem, apiClient, user, userViews) { - elem.classList.remove('verticalSection'); - const excludeViewTypes = ['playlists', 'livetv', 'boxsets', 'channels']; - - for (let i = 0, length = userViews.length; i < length; i++) { - const item = userViews[i]; - if (user.Configuration.LatestItemsExcludes.indexOf(item.Id) !== -1) { - continue; - } - - if (excludeViewTypes.indexOf(item.CollectionType || []) !== -1) { - continue; - } - - const frag = document.createElement('div'); - frag.classList.add('verticalSection'); - frag.classList.add('hide'); - elem.appendChild(frag); - - renderLatestSection(frag, apiClient, user, item); - } - } - - export function loadLibraryTiles(elem, apiClient, user, userSettings, shape, userViews) { - let html = ''; - if (userViews.length) { - html += '

' + globalize.translate('HeaderMyMedia') + '

'; - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - html += cardBuilder.getCardsHtml({ - items: userViews, - shape: getThumbShape(), - showTitle: true, - centerText: true, - overlayText: false, - lazy: true, - transition: false, - allowBottomPadding: !enableScrollX() - }); - - if (enableScrollX()) { - html += '
'; - } - html += '
'; - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - const dataMonitorHints = { - 'Audio': 'audioplayback,markplayed', - 'Video': 'videoplayback,markplayed' - }; - - function loadResume(elem, apiClient, headerText, mediaType, userSettings) { - let html = ''; - - const dataMonitor = dataMonitorHints[mediaType] || 'markplayed'; - - html += '

' + globalize.translate(headerText) + '

'; - if (enableScrollX()) { - html += '
'; - html += `
`; - } else { - html += `
`; - } - - if (enableScrollX()) { - html += '
'; - } - html += '
'; - - elem.classList.add('hide'); - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId()); - itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType); - itemsContainer.parentContainer = elem; - } - - function getItemsToResumeFn(mediaType, serverId) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - - const limit = enableScrollX() ? 12 : 5; - - const options = { - Limit: limit, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false, - MediaTypes: mediaType - }; - - return apiClient.getResumableItems(apiClient.getCurrentUserId(), options); - }; - } - - function getItemsToResumeHtmlFn(useEpisodeImages, mediaType) { - return function (items) { - const cardLayout = false; - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: true, - inheritThumb: !useEpisodeImages, - shape: (mediaType === 'Book') ? getPortraitShape() : getThumbShape(), - overlayText: false, - showTitle: true, - showParentTitle: true, - lazy: true, - showDetailsMenu: true, - overlayPlayButton: true, - context: 'home', - centerText: !cardLayout, - allowBottomPadding: false, - cardLayout: cardLayout, - showYear: true, - lines: 2 - }); - }; - } - - function getOnNowFetchFn(serverId) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getLiveTvRecommendedPrograms({ - userId: apiClient.getCurrentUserId(), - IsAiring: true, - limit: 24, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Thumb,Backdrop', - EnableTotalRecordCount: false, - Fields: 'ChannelInfo,PrimaryImageAspectRatio' - }); - }; - } - - function getOnNowItemsHtml(items) { - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: 'auto', - inheritThumb: false, - shape: (enableScrollX() ? 'autooverflow' : 'auto'), - showParentTitleOrTitle: true, + html += cardBuilder.getCardsHtml({ + items: userViews, + shape: getThumbShape(), showTitle: true, centerText: true, - coverImage: true, overlayText: false, - allowBottomPadding: !enableScrollX(), - showAirTime: true, - showChannelName: false, - showAirDateTime: false, - showAirEndTime: true, - defaultShape: getThumbShape(), - lines: 3, - overlayPlayButton: true + lazy: true, + transition: false, + allowBottomPadding: !enableScrollX() }); + + if (enableScrollX()) { + html += '
'; + } + html += '
'; } - function loadOnNow(elem, apiClient, user) { - if (!user.Policy.EnableLiveTvAccess) { - return Promise.resolve(); - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} +const dataMonitorHints = { + 'Audio': 'audioplayback,markplayed', + 'Video': 'videoplayback,markplayed' +}; + +function loadResume(elem, apiClient, headerText, mediaType, userSettings) { + let html = ''; + + const dataMonitor = dataMonitorHints[mediaType] || 'markplayed'; + + html += '

' + globalize.translate(headerText) + '

'; + if (enableScrollX()) { + html += '
'; + html += `
`; + } else { + html += `
`; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getItemsToResumeFn(mediaType, apiClient.serverId()); + itemsContainer.getItemsHtml = getItemsToResumeHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume(), mediaType); + itemsContainer.parentContainer = elem; +} + +function getItemsToResumeFn(mediaType, serverId) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + + const limit = enableScrollX() ? 12 : 5; + + const options = { + Limit: limit, + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false, + MediaTypes: mediaType + }; + + return apiClient.getResumableItems(apiClient.getCurrentUserId(), options); + }; +} + +function getItemsToResumeHtmlFn(useEpisodeImages, mediaType) { + return function (items) { + const cardLayout = false; + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: true, + inheritThumb: !useEpisodeImages, + shape: (mediaType === 'Book') ? getPortraitShape() : getThumbShape(), + overlayText: false, + showTitle: true, + showParentTitle: true, + lazy: true, + showDetailsMenu: true, + overlayPlayButton: true, + context: 'home', + centerText: !cardLayout, + allowBottomPadding: false, + cardLayout: cardLayout, + showYear: true, + lines: 2 + }); + }; +} + +function getOnNowFetchFn(serverId) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); return apiClient.getLiveTvRecommendedPrograms({ userId: apiClient.getCurrentUserId(), IsAiring: true, - limit: 1, + limit: 24, ImageTypeLimit: 1, EnableImageTypes: 'Primary,Thumb,Backdrop', EnableTotalRecordCount: false, Fields: 'ChannelInfo,PrimaryImageAspectRatio' - }).then(function (result) { - let html = ''; - if (result.Items.length) { - elem.classList.remove('padded-left'); - elem.classList.remove('padded-right'); - elem.classList.remove('padded-bottom'); - elem.classList.remove('verticalSection'); - - html += '
'; - html += '
'; - html += '

' + globalize.translate('LiveTV') + '

'; - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += ''; - if (enableScrollX()) { - html += '
'; - } - html += '
'; - html += '
'; - - html += '
'; - html += '
'; - - if (!layoutManager.tv) { - html += ''; - html += '

'; - html += globalize.translate('HeaderOnNow'); - html += '

'; - html += ''; - html += '
'; - } else { - html += '

' + globalize.translate('HeaderOnNow') + '

'; - } - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - if (enableScrollX()) { - html += '
'; - } - - html += '
'; - html += '
'; - - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.parentContainer = elem; - itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId()); - itemsContainer.getItemsHtml = getOnNowItemsHtml; - } }); + }; +} + +function getOnNowItemsHtml(items) { + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: 'auto', + inheritThumb: false, + shape: (enableScrollX() ? 'autooverflow' : 'auto'), + showParentTitleOrTitle: true, + showTitle: true, + centerText: true, + coverImage: true, + overlayText: false, + allowBottomPadding: !enableScrollX(), + showAirTime: true, + showChannelName: false, + showAirDateTime: false, + showAirEndTime: true, + defaultShape: getThumbShape(), + lines: 3, + overlayPlayButton: true + }); +} + +function loadOnNow(elem, apiClient, user) { + if (!user.Policy.EnableLiveTvAccess) { + return Promise.resolve(); } - function getNextUpFetchFn(serverId, userSettings) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - const oldestDateForNextUp = new Date(); - oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp()); - return apiClient.getNextUpEpisodes({ - Limit: enableScrollX() ? 24 : 15, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount', - UserId: apiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false, - DisableFirstEpisode: false, - NextUpDateCutoff: oldestDateForNextUp.toISOString(), - EnableRewatching: userSettings.enableRewatchingInNextUp() - }); - }; - } - - function getNextUpItemsHtmlFn(useEpisodeImages) { - return function (items) { - const cardLayout = false; - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: true, - inheritThumb: !useEpisodeImages, - shape: getThumbShape(), - overlayText: false, - showTitle: true, - showParentTitle: true, - lazy: true, - overlayPlayButton: true, - context: 'home', - centerText: !cardLayout, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout - }); - }; - } - - function loadNextUp(elem, apiClient, userSettings) { + return apiClient.getLiveTvRecommendedPrograms({ + userId: apiClient.getCurrentUserId(), + IsAiring: true, + limit: 1, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Thumb,Backdrop', + EnableTotalRecordCount: false, + Fields: 'ChannelInfo,PrimaryImageAspectRatio' + }).then(function (result) { let html = ''; + if (result.Items.length) { + elem.classList.remove('padded-left'); + elem.classList.remove('padded-right'); + elem.classList.remove('padded-bottom'); + elem.classList.remove('verticalSection'); - html += '
'; - if (!layoutManager.tv) { - html += ''; + html += '
'; + html += '

' + globalize.translate('LiveTV') + '

'; + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += ''; + }) + '" class="raised">' + globalize.translate('Recordings') + ''; - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } + html += '' + globalize.translate('Schedule') + ''; + + html += '' + globalize.translate('Series') + ''; - if (enableScrollX()) { html += '
'; - } - html += '
'; - - elem.classList.add('hide'); - elem.innerHTML = html; - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings); - itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume()); - itemsContainer.parentContainer = elem; - } - - function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) { - return function () { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getLiveTvRecordings({ - userId: apiClient.getCurrentUserId(), - Limit: enableScrollX() ? 12 : 5, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - EnableTotalRecordCount: false, - IsLibraryItem: activeRecordingsOnly ? null : false, - IsInProgress: activeRecordingsOnly ? true : null - }); - }; - } - - function getLatestRecordingItemsHtml(activeRecordingsOnly) { - return function (items) { - return cardBuilder.getCardsHtml({ - items: items, - shape: enableScrollX() ? 'autooverflow' : 'auto', - showTitle: true, - showParentTitle: true, - coverImage: true, - lazy: true, - showDetailsMenu: true, - centerText: true, - overlayText: false, - showYear: true, - lines: 2, - overlayPlayButton: !activeRecordingsOnly, - allowBottomPadding: !enableScrollX(), - preferThumb: true, - cardLayout: false, - overlayMoreButton: activeRecordingsOnly, - action: activeRecordingsOnly ? 'none' : null, - centerPlayButton: activeRecordingsOnly - }); - }; - } - - function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient) { - const title = activeRecordingsOnly ? - globalize.translate('HeaderActiveRecordings') : - globalize.translate('HeaderLatestRecordings'); - - let html = ''; - - html += '
'; - html += '

' + title + '

'; - html += '
'; - - if (enableScrollX()) { - html += '
'; - html += '
'; - } else { - html += '
'; - } - - if (enableScrollX()) { + if (enableScrollX()) { + html += '
'; + } html += '
'; + html += '
'; + + html += '
'; + html += '
'; + + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('HeaderOnNow'); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

' + globalize.translate('HeaderOnNow') + '

'; + } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + + html += '
'; + html += '
'; + + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.parentContainer = elem; + itemsContainer.fetchData = getOnNowFetchFn(apiClient.serverId()); + itemsContainer.getItemsHtml = getOnNowItemsHtml; } - html += '
'; + }); +} - elem.classList.add('hide'); - elem.innerHTML = html; +function getNextUpFetchFn(serverId, userSettings) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + const oldestDateForNextUp = new Date(); + oldestDateForNextUp.setDate(oldestDateForNextUp.getDate() - userSettings.maxDaysForNextUp()); + return apiClient.getNextUpEpisodes({ + Limit: enableScrollX() ? 24 : 15, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,Path,MediaSourceCount', + UserId: apiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false, + DisableFirstEpisode: false, + NextUpDateCutoff: oldestDateForNextUp.toISOString(), + EnableRewatching: userSettings.enableRewatchingInNextUp() + }); + }; +} - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly); - itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly); - itemsContainer.parentContainer = elem; +function getNextUpItemsHtmlFn(useEpisodeImages) { + return function (items) { + const cardLayout = false; + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: true, + inheritThumb: !useEpisodeImages, + shape: getThumbShape(), + overlayText: false, + showTitle: true, + showParentTitle: true, + lazy: true, + overlayPlayButton: true, + context: 'home', + centerText: !cardLayout, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout + }); + }; +} + +function loadNextUp(elem, apiClient, userSettings) { + let html = ''; + + html += '
'; + if (!layoutManager.tv) { + html += ''; + html += '

'; + html += globalize.translate('NextUp'); + html += '

'; + html += ''; + html += '
'; + } else { + html += '

'; + html += globalize.translate('NextUp'); + html += '

'; } + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getNextUpFetchFn(apiClient.serverId(), userSettings); + itemsContainer.getItemsHtml = getNextUpItemsHtmlFn(userSettings.useEpisodeImagesInNextUpAndResume()); + itemsContainer.parentContainer = elem; +} + +function getLatestRecordingsFetchFn(serverId, activeRecordingsOnly) { + return function () { + const apiClient = ServerConnections.getApiClient(serverId); + return apiClient.getLiveTvRecordings({ + userId: apiClient.getCurrentUserId(), + Limit: enableScrollX() ? 12 : 5, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + EnableTotalRecordCount: false, + IsLibraryItem: activeRecordingsOnly ? null : false, + IsInProgress: activeRecordingsOnly ? true : null + }); + }; +} + +function getLatestRecordingItemsHtml(activeRecordingsOnly) { + return function (items) { + return cardBuilder.getCardsHtml({ + items: items, + shape: enableScrollX() ? 'autooverflow' : 'auto', + showTitle: true, + showParentTitle: true, + coverImage: true, + lazy: true, + showDetailsMenu: true, + centerText: true, + overlayText: false, + showYear: true, + lines: 2, + overlayPlayButton: !activeRecordingsOnly, + allowBottomPadding: !enableScrollX(), + preferThumb: true, + cardLayout: false, + overlayMoreButton: activeRecordingsOnly, + action: activeRecordingsOnly ? 'none' : null, + centerPlayButton: activeRecordingsOnly + }); + }; +} + +function loadLatestLiveTvRecordings(elem, activeRecordingsOnly, apiClient) { + const title = activeRecordingsOnly ? + globalize.translate('HeaderActiveRecordings') : + globalize.translate('HeaderLatestRecordings'); + + let html = ''; + + html += '
'; + html += '

' + title + '

'; + html += '
'; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + + elem.classList.add('hide'); + elem.innerHTML = html; + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.fetchData = getLatestRecordingsFetchFn(apiClient.serverId(), activeRecordingsOnly); + itemsContainer.getItemsHtml = getLatestRecordingItemsHtml(activeRecordingsOnly); + itemsContainer.parentContainer = elem; +} export default { loadLibraryTiles: loadLibraryTiles, @@ -750,4 +748,3 @@ export default { resume: resume }; -/* eslint-enable indent */ diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 22816aee35..124caa5e0a 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -1,395 +1,382 @@ - -/* eslint-disable indent */ - import appSettings from '../scripts/settings/appSettings' ; import browser from '../scripts/browser'; import Events from '../utils/events.ts'; - export function getSavedVolume() { - return appSettings.get('volume') || 1; +export function getSavedVolume() { + return appSettings.get('volume') || 1; +} + +export function saveVolume(value) { + if (value) { + appSettings.set('volume', value); + } +} + +export function getCrossOriginValue(mediaSource) { + if (mediaSource.IsRemote) { + return null; } - export function saveVolume(value) { - if (value) { - appSettings.set('volume', value); - } - } + return 'anonymous'; +} - export function getCrossOriginValue(mediaSource) { - if (mediaSource.IsRemote) { - return null; - } +function canPlayNativeHls() { + const media = document.createElement('video'); - return 'anonymous'; - } - - function canPlayNativeHls() { - const media = document.createElement('video'); - - return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') + return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') || media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')); +} + +export function enableHlsJsPlayer(runTimeTicks, mediaType) { + if (window.MediaSource == null) { + return false; } - export function enableHlsJsPlayer(runTimeTicks, mediaType) { - if (window.MediaSource == null) { - return false; - } - - // hls.js is only in beta. needs more testing. - if (browser.iOS) { - return false; - } - - // The native players on these devices support seeking live streams, no need to use hls.js here - if (browser.tizen || browser.web0s) { - return false; - } - - if (canPlayNativeHls()) { - // Having trouble with chrome's native support and transcoded music - if (browser.android && mediaType === 'Audio') { - return true; - } - - if (browser.edge && mediaType === 'Video') { - //return true; - } - - // simple playback should use the native support - if (runTimeTicks) { - //if (!browser.edge) { - return false; - //} - } - - //return false; - } - - return true; + // hls.js is only in beta. needs more testing. + if (browser.iOS) { + return false; } - let recoverDecodingErrorDate; - let recoverSwapAudioCodecDate; - export function handleHlsJsMediaError(instance, reject) { - const hlsPlayer = instance._hlsPlayer; + // The native players on these devices support seeking live streams, no need to use hls.js here + if (browser.tizen || browser.web0s) { + return false; + } - if (!hlsPlayer) { - return; + if (canPlayNativeHls()) { + // Having trouble with chrome's native support and transcoded music + if (browser.android && mediaType === 'Audio') { + return true; } - let now = Date.now(); - - if (window.performance && window.performance.now) { - now = performance.now(); // eslint-disable-line compat/compat + // simple playback should use the native support + if (runTimeTicks) { + return false; } + } - if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { - recoverDecodingErrorDate = now; - console.debug('try to recover media Error ...'); + return true; +} + +let recoverDecodingErrorDate; +let recoverSwapAudioCodecDate; +export function handleHlsJsMediaError(instance, reject) { + const hlsPlayer = instance._hlsPlayer; + + if (!hlsPlayer) { + return; + } + + let now = Date.now(); + + if (window.performance && window.performance.now) { + now = performance.now(); // eslint-disable-line compat/compat + } + + if (!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) { + recoverDecodingErrorDate = now; + console.debug('try to recover media Error ...'); + hlsPlayer.recoverMediaError(); + } else { + if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { + recoverSwapAudioCodecDate = now; + console.debug('try to swap Audio Codec and recover media Error ...'); + hlsPlayer.swapAudioCodec(); hlsPlayer.recoverMediaError(); } else { - if (!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) { - recoverSwapAudioCodecDate = now; - console.debug('try to swap Audio Codec and recover media Error ...'); - hlsPlayer.swapAudioCodec(); - hlsPlayer.recoverMediaError(); + console.error('cannot recover, last media error recovery failed ...'); + + if (reject) { + reject(); } else { - console.error('cannot recover, last media error recovery failed ...'); - - if (reject) { - reject(); - } else { - onErrorInternal(instance, 'mediadecodeerror'); - } + onErrorInternal(instance, 'mediadecodeerror'); } } } +} - export function onErrorInternal(instance, type) { - // Needed for video - if (instance.destroyCustomTrack) { - instance.destroyCustomTrack(instance._mediaElement); - } - - Events.trigger(instance, 'error', [ - { - type: type - } - ]); +export function onErrorInternal(instance, type) { + // Needed for video + if (instance.destroyCustomTrack) { + instance.destroyCustomTrack(instance._mediaElement); } - export function isValidDuration(duration) { - return duration + Events.trigger(instance, 'error', [ + { + type: type + } + ]); +} + +export function isValidDuration(duration) { + return duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY; - } +} - function setCurrentTimeIfNeeded(element, seconds) { - // If it's worth skipping (1 sec or less of a difference) - if (Math.abs((element.currentTime || 0) - seconds) >= 1) { - element.currentTime = seconds; +function setCurrentTimeIfNeeded(element, seconds) { + // If it's worth skipping (1 sec or less of a difference) + if (Math.abs((element.currentTime || 0) - seconds) >= 1) { + element.currentTime = seconds; + } +} + +export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { + const seconds = (ticks || 0) / 10000000; + + if (seconds) { + // Appending #t=xxx to the query string doesn't seem to work with HLS + // For plain video files, not all browsers support it either + + if (element.duration >= seconds) { + // media is ready, seek immediately + setCurrentTimeIfNeeded(element, seconds); + if (onMediaReady) onMediaReady(); + } else { + // update video player position when media is ready to be sought + const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata']; + const onMediaChange = function(e) { + if (element.currentTime === 0 && element.duration >= seconds) { + // seek only when video position is exactly zero, + // as this is true only if video hasn't started yet or + // user rewound to the very beginning + // (but rewinding cannot happen as the first event with media of non-empty duration) + console.debug(`seeking to ${seconds} on ${e.type} event`); + setCurrentTimeIfNeeded(element, seconds); + events.forEach(name => { + element.removeEventListener(name, onMediaChange); + }); + if (onMediaReady) onMediaReady(); + } + }; + events.forEach(name => { + element.addEventListener(name, onMediaChange); + }); } } +} - export function seekOnPlaybackStart(instance, element, ticks, onMediaReady) { - const seconds = (ticks || 0) / 10000000; +export function applySrc(elem, src, options) { + if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) { + return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) { + const playlist = new Windows.Media.Playback.MediaPlaybackList(); - if (seconds) { - // Appending #t=xxx to the query string doesn't seem to work with HLS - // For plain video files, not all browsers support it either - - if (element.duration >= seconds) { - // media is ready, seek immediately - setCurrentTimeIfNeeded(element, seconds); - if (onMediaReady) onMediaReady(); - } else { - // update video player position when media is ready to be sought - const events = ['durationchange', 'loadeddata', 'play', 'loadedmetadata']; - const onMediaChange = function(e) { - if (element.currentTime === 0 && element.duration >= seconds) { - // seek only when video position is exactly zero, - // as this is true only if video hasn't started yet or - // user rewound to the very beginning - // (but rewinding cannot happen as the first event with media of non-empty duration) - console.debug(`seeking to ${seconds} on ${e.type} event`); - setCurrentTimeIfNeeded(element, seconds); - events.forEach(name => { - element.removeEventListener(name, onMediaChange); - }); - if (onMediaReady) onMediaReady(); - } - }; - events.forEach(name => { - element.addEventListener(name, onMediaChange); - }); - } - } + const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file); + const startTime = (options.playerStartPositionTicks || 0) / 10000; + playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime)); + elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true }); + return Promise.resolve(); + }); + } else { + elem.src = src; } - export function applySrc(elem, src, options) { - if (window.Windows && options.mediaSource && options.mediaSource.IsLocal) { - return Windows.Storage.StorageFile.getFileFromPathAsync(options.url).then(function (file) { - const playlist = new Windows.Media.Playback.MediaPlaybackList(); + return Promise.resolve(); +} - const source1 = Windows.Media.Core.MediaSource.createFromStorageFile(file); - const startTime = (options.playerStartPositionTicks || 0) / 10000; - playlist.items.append(new Windows.Media.Playback.MediaPlaybackItem(source1, startTime)); - elem.src = URL.createObjectURL(playlist, { oneTimeOnly: true }); +export function resetSrc(elem) { + elem.src = ''; + elem.innerHTML = ''; + elem.removeAttribute('src'); +} + +function onSuccessfulPlay(elem, onErrorFn) { + elem.addEventListener('error', onErrorFn); +} + +export function playWithPromise(elem, onErrorFn) { + try { + return elem.play() + .catch((e) => { + const errorName = (e.name || '').toLowerCase(); + // safari uses aborterror + if (errorName === 'notallowederror' + || errorName === 'aborterror') { + // swallow this error because the user can still click the play button on the video element + return Promise.resolve(); + } + return Promise.reject(); + }) + .then(() => { + onSuccessfulPlay(elem, onErrorFn); return Promise.resolve(); }); - } else { - elem.src = src; - } - - return Promise.resolve(); + } catch (err) { + console.error('error calling video.play: ' + err); + return Promise.reject(); } +} - export function resetSrc(elem) { - elem.src = ''; - elem.innerHTML = ''; - elem.removeAttribute('src'); - } - - function onSuccessfulPlay(elem, onErrorFn) { - elem.addEventListener('error', onErrorFn); - } - - export function playWithPromise(elem, onErrorFn) { +export function destroyCastPlayer(instance) { + const player = instance._castPlayer; + if (player) { try { - return elem.play() - .catch((e) => { - const errorName = (e.name || '').toLowerCase(); - // safari uses aborterror - if (errorName === 'notallowederror' - || errorName === 'aborterror') { - // swallow this error because the user can still click the play button on the video element - return Promise.resolve(); - } - return Promise.reject(); - }) - .then(() => { - onSuccessfulPlay(elem, onErrorFn); - return Promise.resolve(); - }); + player.unload(); } catch (err) { - console.error('error calling video.play: ' + err); - return Promise.reject(); + console.error(err); } - } - export function destroyCastPlayer(instance) { - const player = instance._castPlayer; - if (player) { - try { - player.unload(); - } catch (err) { - console.error(err); + instance._castPlayer = null; + } +} + +export function destroyHlsPlayer(instance) { + const player = instance._hlsPlayer; + if (player) { + try { + player.destroy(); + } catch (err) { + console.error(err); + } + + instance._hlsPlayer = null; + } +} + +export function destroyFlvPlayer(instance) { + const player = instance._flvPlayer; + if (player) { + try { + player.unload(); + player.detachMediaElement(); + player.destroy(); + } catch (err) { + console.error(err); + } + + instance._flvPlayer = null; + } +} + +export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) { + hls.on(Hls.Events.MANIFEST_PARSED, function () { + playWithPromise(elem, onErrorFn).then(resolve, function () { + if (reject) { + reject(); + reject = null; } - - instance._castPlayer = null; - } - } - - export function destroyHlsPlayer(instance) { - const player = instance._hlsPlayer; - if (player) { - try { - player.destroy(); - } catch (err) { - console.error(err); - } - - instance._hlsPlayer = null; - } - } - - export function destroyFlvPlayer(instance) { - const player = instance._flvPlayer; - if (player) { - try { - player.unload(); - player.detachMediaElement(); - player.destroy(); - } catch (err) { - console.error(err); - } - - instance._flvPlayer = null; - } - } - - export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, reject) { - hls.on(Hls.Events.MANIFEST_PARSED, function () { - playWithPromise(elem, onErrorFn).then(resolve, function () { - if (reject) { - reject(); - reject = null; - } - }); }); + }); - hls.on(Hls.Events.ERROR, function (event, data) { - console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); + hls.on(Hls.Events.ERROR, function (event, data) { + console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); - // try to recover network error - if (data.type === Hls.ErrorTypes.NETWORK_ERROR + // try to recover network error + if (data.type === Hls.ErrorTypes.NETWORK_ERROR && data.response?.code && data.response.code >= 400 - ) { - console.debug('hls.js response error code: ' + data.response.code); + ) { + console.debug('hls.js response error code: ' + data.response.code); - // Trigger failure differently depending on whether this is prior to start of playback, or after - hls.destroy(); + // Trigger failure differently depending on whether this is prior to start of playback, or after + hls.destroy(); - if (reject) { - reject('servererror'); - reject = null; - } else { - onErrorInternal(instance, 'servererror'); - } - - return; + if (reject) { + reject('servererror'); + reject = null; + } else { + onErrorInternal(instance, 'servererror'); } - if (data.fatal) { - switch (data.type) { - case Hls.ErrorTypes.NETWORK_ERROR: + return; + } - if (data.response && data.response.code === 0) { - // This could be a CORS error related to access control response headers + if (data.fatal) { + switch (data.type) { + case Hls.ErrorTypes.NETWORK_ERROR: - console.debug('hls.js response error code: ' + data.response.code); + if (data.response && data.response.code === 0) { + // This could be a CORS error related to access control response headers - // Trigger failure differently depending on whether this is prior to start of playback, or after - hls.destroy(); + console.debug('hls.js response error code: ' + data.response.code); - if (reject) { - reject('network'); - reject = null; - } else { - onErrorInternal(instance, 'network'); - } - } else { - console.debug('fatal network error encountered, try to recover'); - hls.startLoad(); - } - - break; - case Hls.ErrorTypes.MEDIA_ERROR: - console.debug('fatal media error encountered, try to recover'); - handleHlsJsMediaError(instance, reject); - reject = null; - break; - default: - - console.debug('Cannot recover from hls error - destroy and trigger error'); - // cannot recover // Trigger failure differently depending on whether this is prior to start of playback, or after hls.destroy(); if (reject) { - reject(); + reject('network'); reject = null; } else { - onErrorInternal(instance, 'mediadecodeerror'); + onErrorInternal(instance, 'network'); } - break; - } + } else { + console.debug('fatal network error encountered, try to recover'); + hls.startLoad(); + } + + break; + case Hls.ErrorTypes.MEDIA_ERROR: + console.debug('fatal media error encountered, try to recover'); + handleHlsJsMediaError(instance, reject); + reject = null; + break; + default: + + console.debug('Cannot recover from hls error - destroy and trigger error'); + // cannot recover + // Trigger failure differently depending on whether this is prior to start of playback, or after + hls.destroy(); + + if (reject) { + reject(); + reject = null; + } else { + onErrorInternal(instance, 'mediadecodeerror'); + } + break; } + } + }); +} + +export function onEndedInternal(instance, elem, onErrorFn) { + elem.removeEventListener('error', onErrorFn); + + resetSrc(elem); + + destroyHlsPlayer(instance); + destroyFlvPlayer(instance); + destroyCastPlayer(instance); + + const stopInfo = { + src: instance._currentSrc + }; + + Events.trigger(instance, 'stopped', [stopInfo]); + + instance._currentTime = null; + instance._currentSrc = null; + instance._currentPlayOptions = null; +} + +export function getBufferedRanges(instance, elem) { + const ranges = []; + const seekable = elem.buffered || []; + + let offset; + const currentPlayOptions = instance._currentPlayOptions; + if (currentPlayOptions) { + offset = currentPlayOptions.transcodingOffsetTicks; + } + + offset = offset || 0; + + for (let i = 0, length = seekable.length; i < length; i++) { + let start = seekable.start(i); + let end = seekable.end(i); + + if (!isValidDuration(start)) { + start = 0; + } + if (!isValidDuration(end)) { + end = 0; + continue; + } + + ranges.push({ + start: (start * 10000000) + offset, + end: (end * 10000000) + offset }); } - export function onEndedInternal(instance, elem, onErrorFn) { - elem.removeEventListener('error', onErrorFn); - - resetSrc(elem); - - destroyHlsPlayer(instance); - destroyFlvPlayer(instance); - destroyCastPlayer(instance); - - const stopInfo = { - src: instance._currentSrc - }; - - Events.trigger(instance, 'stopped', [stopInfo]); - - instance._currentTime = null; - instance._currentSrc = null; - instance._currentPlayOptions = null; - } - - export function getBufferedRanges(instance, elem) { - const ranges = []; - const seekable = elem.buffered || []; - - let offset; - const currentPlayOptions = instance._currentPlayOptions; - if (currentPlayOptions) { - offset = currentPlayOptions.transcodingOffsetTicks; - } - - offset = offset || 0; - - for (let i = 0, length = seekable.length; i < length; i++) { - let start = seekable.start(i); - let end = seekable.end(i); - - if (!isValidDuration(start)) { - start = 0; - } - if (!isValidDuration(end)) { - end = 0; - continue; - } - - ranges.push({ - start: (start * 10000000) + offset, - end: (end * 10000000) + offset - }); - } - - return ranges; - } - -/* eslint-enable indent */ + return ranges; +} diff --git a/src/components/imageDownloader/imageDownloader.js b/src/components/imageDownloader/imageDownloader.js index b88eea6688..1955896180 100644 --- a/src/components/imageDownloader/imageDownloader.js +++ b/src/components/imageDownloader/imageDownloader.js @@ -15,371 +15,369 @@ import '../cardbuilder/card.scss'; import ServerConnections from '../ServerConnections'; import template from './imageDownloader.template.html'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +let currentItemId; +let currentItemType; +let currentResolve; +let currentReject; +let hasChanges = false; - let currentItemId; - let currentItemType; - let currentResolve; - let currentReject; - let hasChanges = false; +// These images can be large and we're seeing memory problems in safari +const browsableImagePageSize = browser.slow ? 6 : 30; - // These images can be large and we're seeing memory problems in safari - const browsableImagePageSize = browser.slow ? 6 : 30; +let browsableImageStartIndex = 0; +let browsableImageType = 'Primary'; +let selectedProvider; +let browsableParentId; - let browsableImageStartIndex = 0; - let browsableImageType = 'Primary'; - let selectedProvider; - let browsableParentId; +function getBaseRemoteOptions(page, forceCurrentItemId = false) { + const options = {}; - function getBaseRemoteOptions(page, forceCurrentItemId = false) { - const options = {}; - - if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) { - options.itemId = browsableParentId; - } else { - options.itemId = currentItemId; - } - - return options; + if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) { + options.itemId = browsableParentId; + } else { + options.itemId = currentItemId; } - function reloadBrowsableImages(page, apiClient) { - loading.show(); + return options; +} - const options = getBaseRemoteOptions(page); +function reloadBrowsableImages(page, apiClient) { + loading.show(); - options.type = browsableImageType; - options.startIndex = browsableImageStartIndex; - options.limit = browsableImagePageSize; - options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked; + const options = getBaseRemoteOptions(page); - const provider = selectedProvider || ''; + options.type = browsableImageType; + options.startIndex = browsableImageStartIndex; + options.limit = browsableImagePageSize; + options.IncludeAllLanguages = page.querySelector('#chkAllLanguages').checked; - if (provider) { - options.ProviderName = provider; - } + const provider = selectedProvider || ''; - apiClient.getAvailableRemoteImages(options).then(function (result) { - renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit); - - page.querySelector('#selectBrowsableImageType').value = browsableImageType; - - const providersHtml = result.Providers.map(function (p) { - return ''; - }); - - const selectImageProvider = page.querySelector('#selectImageProvider'); - selectImageProvider.innerHTML = '' + providersHtml; - selectImageProvider.value = provider; - - loading.hide(); - }); - } - - function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) { - page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount); - - let html = ''; - - for (let i = 0, length = imagesResult.Images.length; i < length; i++) { - html += getRemoteImageHtml(imagesResult.Images[i], imageType); - } - - const availableImagesList = page.querySelector('.availableImagesList'); - availableImagesList.innerHTML = html; - imageLoader.lazyChildren(availableImagesList); - - const btnNextPage = page.querySelector('.btnNextPage'); - const btnPreviousPage = page.querySelector('.btnPreviousPage'); - - if (btnNextPage) { - btnNextPage.addEventListener('click', function () { - browsableImageStartIndex += browsableImagePageSize; - reloadBrowsableImages(page, apiClient); - }); - } - - if (btnPreviousPage) { - btnPreviousPage.addEventListener('click', function () { - browsableImageStartIndex -= browsableImagePageSize; - reloadBrowsableImages(page, apiClient); - }); - } - } - - function getPagingHtml(startIndex, limit, totalRecordCount) { - let html = ''; - - const recordsEnd = Math.min(startIndex + limit, totalRecordCount); - - // 20 is the minimum page size - const showControls = totalRecordCount > limit; - - html += '
'; - - html += ''; - - const startAtDisplay = totalRecordCount ? startIndex + 1 : 0; - html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount); - - html += ''; - - if (showControls) { - html += '
'; - - html += ``; - html += ``; - html += '
'; - } - - html += '
'; - - return html; - } - - function downloadRemoteImage(page, apiClient, url, type, provider) { - const options = getBaseRemoteOptions(page, true); - - options.Type = type; - options.ImageUrl = url; + if (provider) { options.ProviderName = provider; - - loading.show(); - - apiClient.downloadRemoteImage(options).then(function () { - hasChanges = true; - const dlg = dom.parentWithClass(page, 'dialog'); - dialogHelper.close(dlg); - }); } - function getRemoteImageHtml(image, imageType) { - const tagName = layoutManager.tv ? 'button' : 'div'; - const enableFooterButtons = !layoutManager.tv; + apiClient.getAvailableRemoteImages(options).then(function (result) { + renderRemoteImages(page, apiClient, result, browsableImageType, options.startIndex, options.limit); - // TODO move card creation code to Card component + page.querySelector('#selectBrowsableImageType').value = browsableImageType; - let html = ''; - - let cssClass = 'card scalableCard imageEditorCard'; - const cardBoxCssClass = 'cardBox visualCardBox'; - - let shape; - if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') { - shape = 'backdrop'; - } else if (imageType === 'Banner') { - shape = 'banner'; - } else if (imageType === 'Disc') { - shape = 'square'; - } else { - if (currentItemType === 'Episode') { - shape = 'backdrop'; - } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { - shape = 'square'; - } else { - shape = 'portrait'; - } - } - - cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable'; - if (tagName === 'button') { - cssClass += ' btnImageCard'; - - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - html += '`; - html += '
'; - } - - html += '
'; - // end footer - - html += '
'; - - html += ''; - - return html; - } - - function reloadBrowsableImagesFirstPage(page, apiClient) { - browsableImageStartIndex = 0; - reloadBrowsableImages(page, apiClient); - } - - function initEditor(page, apiClient) { - page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { - browsableImageType = this.value; - selectedProvider = null; - - reloadBrowsableImagesFirstPage(page, apiClient); + const providersHtml = result.Providers.map(function (p) { + return ''; }); - page.querySelector('#selectImageProvider').addEventListener('change', function () { - selectedProvider = this.value; - - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.querySelector('#chkAllLanguages').addEventListener('change', function () { - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.querySelector('#chkShowParentImages').addEventListener('change', function () { - reloadBrowsableImagesFirstPage(page, apiClient); - }); - - page.addEventListener('click', function (e) { - const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage'); - if (btnDownloadRemoteImage) { - const card = dom.parentWithClass(btnDownloadRemoteImage, 'card'); - downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider')); - return; - } - - const btnImageCard = dom.parentWithClass(e.target, 'btnImageCard'); - if (btnImageCard) { - downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider')); - } - }); - } - - function showEditor(itemId, serverId, itemType) { - loading.show(); - - const apiClient = ServerConnections.getApiClient(serverId); - - currentItemId = itemId; - currentItemType = itemType; - - const dialogOptions = { - removeOnClose: true - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } - - if (browsableParentId) { - dlg.querySelector('#lblShowParentImages').classList.remove('hide'); - } - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', onDialogClosed); - - dialogHelper.open(dlg); - - const editorContent = dlg.querySelector('.formDialogContent'); - initEditor(editorContent, apiClient); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - reloadBrowsableImages(editorContent, apiClient); - } - - function onDialogClosed() { - const dlg = this; - - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } + const selectImageProvider = page.querySelector('#selectImageProvider'); + selectImageProvider.innerHTML = '' + providersHtml; + selectImageProvider.value = provider; loading.hide(); - if (hasChanges) { - currentResolve(); + }); +} + +function renderRemoteImages(page, apiClient, imagesResult, imageType, startIndex, limit) { + page.querySelector('.availableImagesPaging').innerHTML = getPagingHtml(startIndex, limit, imagesResult.TotalRecordCount); + + let html = ''; + + for (let i = 0, length = imagesResult.Images.length; i < length; i++) { + html += getRemoteImageHtml(imagesResult.Images[i], imageType); + } + + const availableImagesList = page.querySelector('.availableImagesList'); + availableImagesList.innerHTML = html; + imageLoader.lazyChildren(availableImagesList); + + const btnNextPage = page.querySelector('.btnNextPage'); + const btnPreviousPage = page.querySelector('.btnPreviousPage'); + + if (btnNextPage) { + btnNextPage.addEventListener('click', function () { + browsableImageStartIndex += browsableImagePageSize; + reloadBrowsableImages(page, apiClient); + }); + } + + if (btnPreviousPage) { + btnPreviousPage.addEventListener('click', function () { + browsableImageStartIndex -= browsableImagePageSize; + reloadBrowsableImages(page, apiClient); + }); + } +} + +function getPagingHtml(startIndex, limit, totalRecordCount) { + let html = ''; + + const recordsEnd = Math.min(startIndex + limit, totalRecordCount); + + // 20 is the minimum page size + const showControls = totalRecordCount > limit; + + html += '
'; + + html += ''; + + const startAtDisplay = totalRecordCount ? startIndex + 1 : 0; + html += globalize.translate('ListPaging', startAtDisplay, recordsEnd, totalRecordCount); + + html += ''; + + if (showControls) { + html += '
'; + + html += ``; + html += ``; + html += '
'; + } + + html += '
'; + + return html; +} + +function downloadRemoteImage(page, apiClient, url, type, provider) { + const options = getBaseRemoteOptions(page, true); + + options.Type = type; + options.ImageUrl = url; + options.ProviderName = provider; + + loading.show(); + + apiClient.downloadRemoteImage(options).then(function () { + hasChanges = true; + const dlg = dom.parentWithClass(page, 'dialog'); + dialogHelper.close(dlg); + }); +} + +function getRemoteImageHtml(image, imageType) { + const tagName = layoutManager.tv ? 'button' : 'div'; + const enableFooterButtons = !layoutManager.tv; + + // TODO move card creation code to Card component + + let html = ''; + + let cssClass = 'card scalableCard imageEditorCard'; + const cardBoxCssClass = 'cardBox visualCardBox'; + + let shape; + if (imageType === 'Backdrop' || imageType === 'Art' || imageType === 'Thumb' || imageType === 'Logo') { + shape = 'backdrop'; + } else if (imageType === 'Banner') { + shape = 'banner'; + } else if (imageType === 'Disc') { + shape = 'square'; + } else { + if (currentItemType === 'Episode') { + shape = 'backdrop'; + } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { + shape = 'square'; } else { - currentReject(); + shape = 'portrait'; } } + cssClass += ' ' + shape + 'Card ' + shape + 'Card-scalable'; + if (tagName === 'button') { + cssClass += ' btnImageCard'; + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + html += '`; + html += '
'; + } + + html += '
'; + // end footer + + html += '
'; + + html += ''; + + return html; +} + +function reloadBrowsableImagesFirstPage(page, apiClient) { + browsableImageStartIndex = 0; + reloadBrowsableImages(page, apiClient); +} + +function initEditor(page, apiClient) { + page.querySelector('#selectBrowsableImageType').addEventListener('change', function () { + browsableImageType = this.value; + selectedProvider = null; + + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#selectImageProvider').addEventListener('change', function () { + selectedProvider = this.value; + + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#chkAllLanguages').addEventListener('change', function () { + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.querySelector('#chkShowParentImages').addEventListener('change', function () { + reloadBrowsableImagesFirstPage(page, apiClient); + }); + + page.addEventListener('click', function (e) { + const btnDownloadRemoteImage = dom.parentWithClass(e.target, 'btnDownloadRemoteImage'); + if (btnDownloadRemoteImage) { + const card = dom.parentWithClass(btnDownloadRemoteImage, 'card'); + downloadRemoteImage(page, apiClient, card.getAttribute('data-imageurl'), card.getAttribute('data-imagetype'), card.getAttribute('data-imageprovider')); + return; + } + + const btnImageCard = dom.parentWithClass(e.target, 'btnImageCard'); + if (btnImageCard) { + downloadRemoteImage(page, apiClient, btnImageCard.getAttribute('data-imageurl'), btnImageCard.getAttribute('data-imagetype'), btnImageCard.getAttribute('data-imageprovider')); + } + }); +} + +function showEditor(itemId, serverId, itemType) { + loading.show(); + + const apiClient = ServerConnections.getApiClient(serverId); + + currentItemId = itemId; + currentItemType = itemType; + + const dialogOptions = { + removeOnClose: true + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg, false); + } + + if (browsableParentId) { + dlg.querySelector('#lblShowParentImages').classList.remove('hide'); + } + + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', onDialogClosed); + + dialogHelper.open(dlg); + + const editorContent = dlg.querySelector('.formDialogContent'); + initEditor(editorContent, apiClient); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + reloadBrowsableImages(editorContent, apiClient); +} + +function onDialogClosed() { + const dlg = this; + + if (layoutManager.tv) { + scrollHelper.centerFocus.off(dlg, false); + } + + loading.hide(); + if (hasChanges) { + currentResolve(); + } else { + currentReject(); + } +} + export function show(itemId, serverId, itemType, imageType, parentId) { return new Promise(function (resolve, reject) { currentResolve = resolve; @@ -397,4 +395,3 @@ export default { show: show }; -/* eslint-enable indent */ diff --git a/src/components/imageOptionsEditor/imageOptionsEditor.js b/src/components/imageOptionsEditor/imageOptionsEditor.js index 39d2e69df8..0b9e0ad0b7 100644 --- a/src/components/imageOptionsEditor/imageOptionsEditor.js +++ b/src/components/imageOptionsEditor/imageOptionsEditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for image Options Editor. @@ -13,95 +12,95 @@ import '../../elements/emby-select/emby-select'; import '../../elements/emby-input/emby-input'; import template from './imageOptionsEditor.template.html'; - function getDefaultImageConfig(itemType, type) { - return { - Type: type, - MinWidth: 0, - Limit: type === 'Primary' ? 1 : 0 - }; - } +function getDefaultImageConfig(itemType, type) { + return { + Type: type, + MinWidth: 0, + Limit: type === 'Primary' ? 1 : 0 + }; +} - function findImageOptions(imageOptions, type) { - return imageOptions.filter(i => { - return i.Type == type; - })[0]; - } +function findImageOptions(imageOptions, type) { + return imageOptions.filter(i => { + return i.Type == type; + })[0]; +} - function getImageConfig(options, availableOptions, imageType, itemType) { - return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType); - } +function getImageConfig(options, availableOptions, imageType, itemType) { + return findImageOptions(options.ImageOptions || [], imageType) || findImageOptions(availableOptions.DefaultImageOptions || [], imageType) || getDefaultImageConfig(itemType, imageType); +} - function setVisibilityOfBackdrops(elem, visible) { - if (visible) { - elem.classList.remove('hide'); - elem.querySelector('input').setAttribute('required', 'required'); +function setVisibilityOfBackdrops(elem, visible) { + if (visible) { + elem.classList.remove('hide'); + elem.querySelector('input').setAttribute('required', 'required'); + } else { + elem.classList.add('hide'); + elem.querySelector('input').setAttribute('required', ''); + elem.querySelector('input').removeAttribute('required'); + } +} + +function loadValues(context, itemType, options, availableOptions) { + const supportedImageTypes = availableOptions.SupportedImageTypes || []; + setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop')); + Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => { + const imageType = i.getAttribute('data-imagetype'); + const container = dom.parentWithTag(i, 'LABEL'); + + if (!supportedImageTypes.includes(imageType)) { + container.classList.add('hide'); } else { - elem.classList.add('hide'); - elem.querySelector('input').setAttribute('required', ''); - elem.querySelector('input').removeAttribute('required'); + container.classList.remove('hide'); } - } - function loadValues(context, itemType, options, availableOptions) { - const supportedImageTypes = availableOptions.SupportedImageTypes || []; - setVisibilityOfBackdrops(context.querySelector('.backdropFields'), supportedImageTypes.includes('Backdrop')); - Array.prototype.forEach.call(context.querySelectorAll('.imageType'), i => { - const imageType = i.getAttribute('data-imagetype'); - const container = dom.parentWithTag(i, 'LABEL'); + if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { + i.checked = true; + } else { + i.checked = false; + } + }); + const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); + context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; + context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; +} - if (!supportedImageTypes.includes(imageType)) { - container.classList.add('hide'); - } else { - container.classList.remove('hide'); - } +function saveValues(context, options) { + options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => { + return { + Type: c.getAttribute('data-imagetype'), + Limit: c.checked ? 1 : 0, + MinWidth: 0 + }; + }); + options.ImageOptions.push({ + Type: 'Backdrop', + Limit: context.querySelector('#txtMaxBackdrops').value, + MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value + }); +} - if (getImageConfig(options, availableOptions, imageType, itemType).Limit) { - i.checked = true; - } else { - i.checked = false; - } - }); - const backdropConfig = getImageConfig(options, availableOptions, 'Backdrop', itemType); - context.querySelector('#txtMaxBackdrops').value = backdropConfig.Limit; - context.querySelector('#txtMinBackdropDownloadWidth').value = backdropConfig.MinWidth; - } - - function saveValues(context, options) { - options.ImageOptions = Array.prototype.map.call(context.querySelectorAll('.imageType:not(.hide)'), c => { - return { - Type: c.getAttribute('data-imagetype'), - Limit: c.checked ? 1 : 0, - MinWidth: 0 - }; - }); - options.ImageOptions.push({ - Type: 'Backdrop', - Limit: context.querySelector('#txtMaxBackdrops').value, - MinWidth: context.querySelector('#txtMinBackdropDownloadWidth').value - }); - } - - function showEditor(itemType, options, availableOptions) { - const dlg = dialogHelper.createDialog({ - size: 'small', - removeOnClose: true, - scrollY: false - }); - dlg.classList.add('formDialog'); - dlg.innerHTML = globalize.translateHtml(template); - dlg.addEventListener('close', function () { - saveValues(dlg, options); - }); - loadValues(dlg, itemType, options, availableOptions); - dialogHelper.open(dlg).then(() => { - return; - }).catch(() => { - return; - }); - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - } +function showEditor(itemType, options, availableOptions) { + const dlg = dialogHelper.createDialog({ + size: 'small', + removeOnClose: true, + scrollY: false + }); + dlg.classList.add('formDialog'); + dlg.innerHTML = globalize.translateHtml(template); + dlg.addEventListener('close', function () { + saveValues(dlg, options); + }); + loadValues(dlg, itemType, options, availableOptions); + dialogHelper.open(dlg).then(() => { + return; + }).catch(() => { + return; + }); + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); +} export class editor { constructor() { @@ -109,5 +108,4 @@ export class editor { } } -/* eslint-enable indent */ export default editor; diff --git a/src/components/imageUploader/imageUploader.js b/src/components/imageUploader/imageUploader.js index 246bf92f7f..c2c70d32ec 100644 --- a/src/components/imageUploader/imageUploader.js +++ b/src/components/imageUploader/imageUploader.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for imageUploader. @@ -19,169 +18,168 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './imageUploader.template.html'; - let currentItemId; - let currentServerId; - let currentFile; - let hasChanges = false; +let currentItemId; +let currentServerId; +let currentFile; +let hasChanges = false; - function onFileReaderError(evt) { +function onFileReaderError(evt) { + loading.hide(); + + switch (evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + toast(globalize.translate('MessageFileReadError')); + break; + case evt.target.error.ABORT_ERR: + break; // noop + default: + toast(globalize.translate('MessageFileReadError')); + break; + } +} + +function setFiles(page, files) { + const file = files[0]; + + if (!file || !file.type.match('image.*')) { + page.querySelector('#imageOutput').innerHTML = ''; + page.querySelector('#fldUpload').classList.add('hide'); + currentFile = null; + return; + } + + currentFile = file; + + const reader = new FileReader(); + + reader.onerror = onFileReaderError; + reader.onloadstart = () => { + page.querySelector('#fldUpload').classList.add('hide'); + }; + reader.onabort = () => { loading.hide(); + console.debug('File read cancelled'); + }; - switch (evt.target.error.code) { - case evt.target.error.NOT_FOUND_ERR: - toast(globalize.translate('MessageFileReadError')); - break; - case evt.target.error.ABORT_ERR: - break; // noop - default: - toast(globalize.translate('MessageFileReadError')); - break; - } + // Closure to capture the file information. + reader.onload = (theFile => { + return e => { + // Render thumbnail. + const html = [''].join(''); + + page.querySelector('#imageOutput').innerHTML = html; + page.querySelector('#dropImageText').classList.add('hide'); + page.querySelector('#fldUpload').classList.remove('hide'); + }; + })(file); + + // Read in the image file as a data URL. + reader.readAsDataURL(file); +} + +function onSubmit(e) { + const file = currentFile; + + if (!file) { + return false; } - function setFiles(page, files) { - const file = files[0]; - - if (!file || !file.type.match('image.*')) { - page.querySelector('#imageOutput').innerHTML = ''; - page.querySelector('#fldUpload').classList.add('hide'); - currentFile = null; - return; - } - - currentFile = file; - - const reader = new FileReader(); - - reader.onerror = onFileReaderError; - reader.onloadstart = () => { - page.querySelector('#fldUpload').classList.add('hide'); - }; - reader.onabort = () => { - loading.hide(); - console.debug('File read cancelled'); - }; - - // Closure to capture the file information. - reader.onload = (theFile => { - return e => { - // Render thumbnail. - const html = [''].join(''); - - page.querySelector('#imageOutput').innerHTML = html; - page.querySelector('#dropImageText').classList.add('hide'); - page.querySelector('#fldUpload').classList.remove('hide'); - }; - })(file); - - // Read in the image file as a data URL. - reader.readAsDataURL(file); - } - - function onSubmit(e) { - const file = currentFile; - - if (!file) { - return false; - } - - if (!file.type.startsWith('image/')) { - toast(globalize.translate('MessageImageFileTypeAllowed')); - e.preventDefault(); - return false; - } - - loading.show(); - - const dlg = dom.parentWithClass(this, 'dialog'); - - const imageType = dlg.querySelector('#selectImageType').value; - if (imageType === 'None') { - toast(globalize.translate('MessageImageTypeNotSelected')); - e.preventDefault(); - return false; - } - - ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => { - dlg.querySelector('#uploadImage').value = ''; - - loading.hide(); - hasChanges = true; - dialogHelper.close(dlg); - }); - + if (!file.type.startsWith('image/')) { + toast(globalize.translate('MessageImageFileTypeAllowed')); e.preventDefault(); return false; } - function initEditor(page) { - page.querySelector('form').addEventListener('submit', onSubmit); + loading.show(); - page.querySelector('#uploadImage').addEventListener('change', function () { - setFiles(page, this.files); - }); + const dlg = dom.parentWithClass(this, 'dialog'); - page.querySelector('.btnBrowse').addEventListener('click', () => { - page.querySelector('#uploadImage').click(); - }); + const imageType = dlg.querySelector('#selectImageType').value; + if (imageType === 'None') { + toast(globalize.translate('MessageImageTypeNotSelected')); + e.preventDefault(); + return false; } - function showEditor(options, resolve) { - options = options || {}; + ServerConnections.getApiClient(currentServerId).uploadItemImage(currentItemId, imageType, file).then(() => { + dlg.querySelector('#uploadImage').value = ''; - currentItemId = options.itemId; - currentServerId = options.serverId; + loading.hide(); + hasChanges = true; + dialogHelper.close(dlg); + }); - const dialogOptions = { - removeOnClose: true - }; + e.preventDefault(); + return false; +} +function initEditor(page) { + page.querySelector('form').addEventListener('submit', onSubmit); + + page.querySelector('#uploadImage').addEventListener('change', function () { + setFiles(page, this.files); + }); + + page.querySelector('.btnBrowse').addEventListener('click', () => { + page.querySelector('#uploadImage').click(); + }); +} + +function showEditor(options, resolve) { + options = options || {}; + + currentItemId = options.itemId; + currentServerId = options.serverId; + + const dialogOptions = { + removeOnClose: true + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + dlg.innerHTML = globalize.translateHtml(template, 'core'); + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg, false); + } + + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', () => { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } else { - dialogOptions.size = 'small'; + scrollHelper.centerFocus.off(dlg, false); } - const dlg = dialogHelper.createDialog(dialogOptions); + loading.hide(); + resolve(hasChanges); + }); - dlg.classList.add('formDialog'); + dialogHelper.open(dlg); - dlg.innerHTML = globalize.translateHtml(template, 'core'); + initEditor(dlg); - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } + dlg.querySelector('#selectImageType').value = options.imageType || 'Primary'; - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', () => { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); +} - loading.hide(); - resolve(hasChanges); - }); +export function show(options) { + return new Promise(resolve => { + hasChanges = false; - dialogHelper.open(dlg); + showEditor(options, resolve); + }); +} - initEditor(dlg); - - dlg.querySelector('#selectImageType').value = options.imageType || 'Primary'; - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - } - - export function show(options) { - return new Promise(resolve => { - hasChanges = false; - - showEditor(options, resolve); - }); - } - -/* eslint-enable indent */ export default { show: show }; diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index b2c9033bc3..6a7982cca9 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -18,441 +18,439 @@ import alert from '../alert'; import confirm from '../confirm/confirm'; import template from './imageeditor.template.html'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +let currentItem; +let hasChanges = false; - let currentItem; - let hasChanges = false; +function getBaseRemoteOptions() { + return { itemId: currentItem.Id }; +} - function getBaseRemoteOptions() { - return { itemId: currentItem.Id }; +function reload(page, item, focusContext) { + loading.show(); + + let apiClient; + + if (item) { + apiClient = ServerConnections.getApiClient(item.ServerId); + reloadItem(page, item, apiClient, focusContext); + } else { + apiClient = ServerConnections.getApiClient(currentItem.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) { + reloadItem(page, itemToReload, apiClient, focusContext); + }); } +} - function reload(page, item, focusContext) { - loading.show(); - - let apiClient; - - if (item) { - apiClient = ServerConnections.getApiClient(item.ServerId); - reloadItem(page, item, apiClient, focusContext); - } else { - apiClient = ServerConnections.getApiClient(currentItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), currentItem.Id).then(function (itemToReload) { - reloadItem(page, itemToReload, apiClient, focusContext); - }); +function addListeners(container, className, eventName, fn) { + container.addEventListener(eventName, function (e) { + const elem = dom.parentWithClass(e.target, className); + if (elem) { + fn.call(elem, e); } - } + }); +} - function addListeners(container, className, eventName, fn) { - container.addEventListener(eventName, function (e) { - const elem = dom.parentWithClass(e.target, className); - if (elem) { - fn.call(elem, e); +function reloadItem(page, item, apiClient, focusContext) { + currentItem = item; + + apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) { + const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages'); + for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) { + if (providers.length) { + btnBrowseAllImages[i].classList.remove('hide'); + } else { + btnBrowseAllImages[i].classList.add('hide'); + } + } + + apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) { + renderStandardImages(page, apiClient, item, imageInfos, providers); + renderBackdrops(page, apiClient, item, imageInfos, providers); + loading.hide(); + + if (layoutManager.tv) { + focusManager.autoFocus((focusContext || page)); } }); + }); +} + +function getImageUrl(item, apiClient, type, index, options) { + options = options || {}; + options.type = type; + options.index = index; + + if (type === 'Backdrop') { + options.tag = item.BackdropImageTags[index]; + } else if (type === 'Primary') { + options.tag = item.PrimaryImageTag || item.ImageTags[type]; + } else { + options.tag = item.ImageTags[type]; } - function reloadItem(page, item, apiClient, focusContext) { - currentItem = item; + // For search hints + return apiClient.getScaledImageUrl(item.Id || item.ItemId, options); +} - apiClient.getRemoteImageProviders(getBaseRemoteOptions()).then(function (providers) { - const btnBrowseAllImages = page.querySelectorAll('.btnBrowseAllImages'); - for (let i = 0, length = btnBrowseAllImages.length; i < length; i++) { - if (providers.length) { - btnBrowseAllImages[i].classList.remove('hide'); - } else { - btnBrowseAllImages[i].classList.add('hide'); - } +function getCardHtml(image, apiClient, options) { + // TODO move card creation code to Card component + + let html = ''; + + let cssClass = 'card scalableCard imageEditorCard'; + const cardBoxCssClass = 'cardBox visualCardBox'; + + cssClass += ' backdropCard backdropCard-scalable'; + + if (options.tagName === 'button') { + cssClass += ' btnImageCard'; + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + html += ''; + } else { + html += ''; } - apiClient.getItemImageInfos(currentItem.Id).then(function (imageInfos) { - renderStandardImages(page, apiClient, item, imageInfos, providers); - renderBackdrops(page, apiClient, item, imageInfos, providers); - loading.hide(); + if (options.index < options.numImages - 1) { + html += ''; + } else { + html += ''; + } + } else { + if (options.imageProviders.length) { + html += ''; + } + } - if (layoutManager.tv) { - focusManager.autoFocus((focusContext || page)); - } - }); - }); + html += ''; + html += '
'; } - function getImageUrl(item, apiClient, type, index, options) { - options = options || {}; - options.type = type; - options.index = index; + html += '
'; + html += '
'; + html += ''; + + return html; +} + +function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) { + const afterConfirm = function () { + apiClient.deleteItemImage(itemId, type, index).then(function () { + hasChanges = true; + reload(context); + }); + }; + + if (!enableConfirmation) { + afterConfirm(); + return; + } + + confirm({ + text: globalize.translate('ConfirmDeleteImage'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(afterConfirm); +} + +function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) { + apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () { + hasChanges = true; + reload(context, null, focusContext); + }, function () { + alert(globalize.translate('ErrorDefault')); + }); +} + +function renderImages(page, item, apiClient, images, imageProviders, elem) { + let html = ''; + + let imageSize = 300; + const windowSize = dom.getWindowSize(); + if (windowSize.innerWidth >= 1280) { + imageSize = Math.round(windowSize.innerWidth / 4); + } + + const tagName = layoutManager.tv ? 'button' : 'div'; + const enableFooterButtons = !layoutManager.tv; + + for (let i = 0, length = images.length; i < length; i++) { + const image = images[i]; + const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons }; + html += getCardHtml(image, apiClient, options); + } + + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} + +function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { + const images = imageInfos.filter(function (i) { + return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter'; + }); + + renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); +} + +function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { + const images = imageInfos.filter(function (i) { + return i.ImageType === 'Backdrop'; + }).sort(function (a, b) { + return a.ImageIndex - b.ImageIndex; + }); + + if (images.length) { + page.querySelector('#backdropsContainer', page).classList.remove('hide'); + renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops')); + } else { + page.querySelector('#backdropsContainer', page).classList.add('hide'); + } +} + +function showImageDownloader(page, imageType) { + import('../imageDownloader/imageDownloader').then((ImageDownloader) => { + ImageDownloader.show( + currentItem.Id, + currentItem.ServerId, + currentItem.Type, + imageType, + currentItem.Type == 'Season' ? currentItem.ParentId : null + ).then(function () { + hasChanges = true; + reload(page); + }); + }); +} + +function showActionSheet(context, imageCard) { + const itemId = imageCard.getAttribute('data-id'); + const serverId = imageCard.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + const type = imageCard.getAttribute('data-imagetype'); + const index = parseInt(imageCard.getAttribute('data-index'), 10); + const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10); + const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10); + + import('../actionSheet/actionSheet').then(({ default: actionSheet }) => { + const commands = []; + + commands.push({ + name: globalize.translate('Delete'), + id: 'delete' + }); if (type === 'Backdrop') { - options.tag = item.BackdropImageTags[index]; - } else if (type === 'Primary') { - options.tag = item.PrimaryImageTag || item.ImageTags[type]; - } else { - options.tag = item.ImageTags[type]; - } - - // For search hints - return apiClient.getScaledImageUrl(item.Id || item.ItemId, options); - } - - function getCardHtml(image, apiClient, options) { - // TODO move card creation code to Card component - - let html = ''; - - let cssClass = 'card scalableCard imageEditorCard'; - const cardBoxCssClass = 'cardBox visualCardBox'; - - cssClass += ' backdropCard backdropCard-scalable'; - - if (options.tagName === 'button') { - cssClass += ' btnImageCard'; - - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } + if (index > 0) { + commands.push({ + name: globalize.translate('MoveLeft'), + id: 'moveleft' + }); } - html += ''; - } else { - html += ''; - } - - if (options.index < options.numImages - 1) { - html += ''; - } else { - html += ''; - } - } else { - if (options.imageProviders.length) { - html += ''; - } + if (index < numImages - 1) { + commands.push({ + name: globalize.translate('MoveRight'), + id: 'moveright' + }); } - - html += ''; - html += '
'; } - html += '
'; - html += '
'; - html += ''; - - return html; - } - - function deleteImage(context, itemId, type, index, apiClient, enableConfirmation) { - const afterConfirm = function () { - apiClient.deleteItemImage(itemId, type, index).then(function () { - hasChanges = true; - reload(context); + if (providerCount) { + commands.push({ + name: globalize.translate('Search'), + id: 'search' }); + } + + actionSheet.show({ + + items: commands, + positionTo: imageCard + + }).then(function (id) { + switch (id) { + case 'delete': + deleteImage(context, itemId, type, index, apiClient, false); + break; + case 'search': + showImageDownloader(context, type); + break; + case 'moveleft': + moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer')); + break; + case 'moveright': + moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer')); + break; + default: + break; + } + }); + }); +} + +function initEditor(context, options) { + const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu'); + const isFileInputSupported = appHost.supports('fileinput'); + for (let i = 0, length = uploadButtons.length; i < length; i++) { + if (isFileInputSupported) { + uploadButtons[i].classList.remove('hide'); + } else { + uploadButtons[i].classList.add('hide'); + } + } + + addListeners(context, 'btnOpenUploadMenu', 'click', function () { + const imageType = this.getAttribute('data-imagetype'); + + import('../imageUploader/imageUploader').then(({ default: imageUploader }) => { + imageUploader.show({ + + theme: options.theme, + imageType: imageType, + itemId: currentItem.Id, + serverId: currentItem.ServerId + + }).then(function (hasChanged) { + if (hasChanged) { + hasChanges = true; + reload(context); + } + }); + }); + }); + + addListeners(context, 'btnSearchImages', 'click', function () { + showImageDownloader(context, this.getAttribute('data-imagetype')); + }); + + addListeners(context, 'btnBrowseAllImages', 'click', function () { + showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary'); + }); + + addListeners(context, 'btnImageCard', 'click', function () { + showActionSheet(context, this); + }); + + addListeners(context, 'btnDeleteImage', 'click', function () { + const type = this.getAttribute('data-imagetype'); + let index = this.getAttribute('data-index'); + index = index === 'null' ? null : parseInt(index, 10); + const apiClient = ServerConnections.getApiClient(currentItem.ServerId); + deleteImage(context, currentItem.Id, type, index, apiClient, true); + }); + + addListeners(context, 'btnMoveImage', 'click', function () { + const type = this.getAttribute('data-imagetype'); + const index = this.getAttribute('data-index'); + const newIndex = this.getAttribute('data-newindex'); + const apiClient = ServerConnections.getApiClient(currentItem.ServerId); + moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer')); + }); +} + +function showEditor(options, resolve, reject) { + const itemId = options.itemId; + const serverId = options.serverId; + + loading.show(); + + const apiClient = ServerConnections.getApiClient(serverId); + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { + const dialogOptions = { + removeOnClose: true }; - if (!enableConfirmation) { - afterConfirm(); - return; - } - - confirm({ - text: globalize.translate('ConfirmDeleteImage'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(afterConfirm); - } - - function moveImage(context, apiClient, itemId, type, index, newIndex, focusContext) { - apiClient.updateItemImageIndex(itemId, type, index, newIndex).then(function () { - hasChanges = true; - reload(context, null, focusContext); - }, function () { - alert(globalize.translate('ErrorDefault')); - }); - } - - function renderImages(page, item, apiClient, images, imageProviders, elem) { - let html = ''; - - let imageSize = 300; - const windowSize = dom.getWindowSize(); - if (windowSize.innerWidth >= 1280) { - imageSize = Math.round(windowSize.innerWidth / 4); - } - - const tagName = layoutManager.tv ? 'button' : 'div'; - const enableFooterButtons = !layoutManager.tv; - - for (let i = 0, length = images.length; i < length; i++) { - const image = images[i]; - const options = { index: i, numImages: length, imageProviders, imageSize, tagName, enableFooterButtons }; - html += getCardHtml(image, apiClient, options); - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - function renderStandardImages(page, apiClient, item, imageInfos, imageProviders) { - const images = imageInfos.filter(function (i) { - return i.ImageType !== 'Backdrop' && i.ImageType !== 'Chapter'; - }); - - renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#images')); - } - - function renderBackdrops(page, apiClient, item, imageInfos, imageProviders) { - const images = imageInfos.filter(function (i) { - return i.ImageType === 'Backdrop'; - }).sort(function (a, b) { - return a.ImageIndex - b.ImageIndex; - }); - - if (images.length) { - page.querySelector('#backdropsContainer', page).classList.remove('hide'); - renderImages(page, item, apiClient, images, imageProviders, page.querySelector('#backdrops')); + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } else { - page.querySelector('#backdropsContainer', page).classList.add('hide'); - } - } - - function showImageDownloader(page, imageType) { - import('../imageDownloader/imageDownloader').then((ImageDownloader) => { - ImageDownloader.show( - currentItem.Id, - currentItem.ServerId, - currentItem.Type, - imageType, - currentItem.Type == 'Season' ? currentItem.ParentId : null - ).then(function () { - hasChanges = true; - reload(page); - }); - }); - } - - function showActionSheet(context, imageCard) { - const itemId = imageCard.getAttribute('data-id'); - const serverId = imageCard.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - - const type = imageCard.getAttribute('data-imagetype'); - const index = parseInt(imageCard.getAttribute('data-index'), 10); - const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10); - const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10); - - import('../actionSheet/actionSheet').then(({ default: actionSheet }) => { - const commands = []; - - commands.push({ - name: globalize.translate('Delete'), - id: 'delete' - }); - - if (type === 'Backdrop') { - if (index > 0) { - commands.push({ - name: globalize.translate('MoveLeft'), - id: 'moveleft' - }); - } - - if (index < numImages - 1) { - commands.push({ - name: globalize.translate('MoveRight'), - id: 'moveright' - }); - } - } - - if (providerCount) { - commands.push({ - name: globalize.translate('Search'), - id: 'search' - }); - } - - actionSheet.show({ - - items: commands, - positionTo: imageCard - - }).then(function (id) { - switch (id) { - case 'delete': - deleteImage(context, itemId, type, index, apiClient, false); - break; - case 'search': - showImageDownloader(context, type); - break; - case 'moveleft': - moveImage(context, apiClient, itemId, type, index, index - 1, dom.parentWithClass(imageCard, 'itemsContainer')); - break; - case 'moveright': - moveImage(context, apiClient, itemId, type, index, index + 1, dom.parentWithClass(imageCard, 'itemsContainer')); - break; - default: - break; - } - }); - }); - } - - function initEditor(context, options) { - const uploadButtons = context.querySelectorAll('.btnOpenUploadMenu'); - const isFileInputSupported = appHost.supports('fileinput'); - for (let i = 0, length = uploadButtons.length; i < length; i++) { - if (isFileInputSupported) { - uploadButtons[i].classList.remove('hide'); - } else { - uploadButtons[i].classList.add('hide'); - } + dialogOptions.size = 'small'; } - addListeners(context, 'btnOpenUploadMenu', 'click', function () { - const imageType = this.getAttribute('data-imagetype'); + const dlg = dialogHelper.createDialog(dialogOptions); - import('../imageUploader/imageUploader').then(({ default: imageUploader }) => { - imageUploader.show({ + dlg.classList.add('formDialog'); - theme: options.theme, - imageType: imageType, - itemId: currentItem.Id, - serverId: currentItem.ServerId + dlg.innerHTML = globalize.translateHtml(template, 'core'); - }).then(function (hasChanged) { - if (hasChanged) { - hasChanges = true; - reload(context); - } - }); - }); - }); + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg, false); + } - addListeners(context, 'btnSearchImages', 'click', function () { - showImageDownloader(context, this.getAttribute('data-imagetype')); - }); - - addListeners(context, 'btnBrowseAllImages', 'click', function () { - showImageDownloader(context, this.getAttribute('data-imagetype') || 'Primary'); - }); - - addListeners(context, 'btnImageCard', 'click', function () { - showActionSheet(context, this); - }); - - addListeners(context, 'btnDeleteImage', 'click', function () { - const type = this.getAttribute('data-imagetype'); - let index = this.getAttribute('data-index'); - index = index === 'null' ? null : parseInt(index, 10); - const apiClient = ServerConnections.getApiClient(currentItem.ServerId); - deleteImage(context, currentItem.Id, type, index, apiClient, true); - }); - - addListeners(context, 'btnMoveImage', 'click', function () { - const type = this.getAttribute('data-imagetype'); - const index = this.getAttribute('data-index'); - const newIndex = this.getAttribute('data-newindex'); - const apiClient = ServerConnections.getApiClient(currentItem.ServerId); - moveImage(context, apiClient, currentItem.Id, type, index, newIndex, dom.parentWithClass(this, 'itemsContainer')); - }); - } - - function showEditor(options, resolve, reject) { - const itemId = options.itemId; - const serverId = options.serverId; - - loading.show(); - - const apiClient = ServerConnections.getApiClient(serverId); - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(function (item) { - const dialogOptions = { - removeOnClose: true - }; + initEditor(dlg, options); + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', function () { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + scrollHelper.centerFocus.off(dlg, false); + } + + loading.hide(); + + if (hasChanges) { + resolve(); } else { - dialogOptions.size = 'small'; + reject(); } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - dlg.innerHTML = globalize.translateHtml(template, 'core'); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg, false); - } - - initEditor(dlg, options); - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - scrollHelper.centerFocus.off(dlg, false); - } - - loading.hide(); - - if (hasChanges) { - resolve(); - } else { - reject(); - } - }); - - dialogHelper.open(dlg); - - reload(dlg, item); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); }); - } + + dialogHelper.open(dlg); + + reload(dlg, item); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + }); +} export function show (options) { return new Promise(function (resolve, reject) { @@ -465,4 +463,3 @@ export default { show }; -/* eslint-enable indent */ diff --git a/src/components/images/imageLoader.js b/src/components/images/imageLoader.js index d1095a01dc..beb4bb31a5 100644 --- a/src/components/images/imageLoader.js +++ b/src/components/images/imageLoader.js @@ -17,237 +17,235 @@ worker.addEventListener( } } ); -/* eslint-disable indent */ - export function lazyImage(elem, source = elem.getAttribute('data-src')) { - if (!source) { - return; - } - - fillImageElement(elem, source); +export function lazyImage(elem, source = elem.getAttribute('data-src')) { + if (!source) { + return; } - function drawBlurhash(target, pixels, width, height) { - const canvas = document.createElement('canvas'); - canvas.setAttribute('aria-hidden', 'true'); - canvas.width = width; - canvas.height = height; - const ctx = canvas.getContext('2d'); - const imgData = ctx.createImageData(width, height); + fillImageElement(elem, source); +} - imgData.data.set(pixels); - ctx.putImageData(imgData, 0, 0); +function drawBlurhash(target, pixels, width, height) { + const canvas = document.createElement('canvas'); + canvas.setAttribute('aria-hidden', 'true'); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + const imgData = ctx.createImageData(width, height); - requestAnimationFrame(() => { - // This class is just an utility class, so users can customize the canvas using their own CSS. - canvas.classList.add('blurhash-canvas'); + imgData.data.set(pixels); + ctx.putImageData(imgData, 0, 0); - target.parentNode.insertBefore(canvas, target); - target.classList.add('blurhashed'); - target.removeAttribute('data-blurhash'); + requestAnimationFrame(() => { + // This class is just an utility class, so users can customize the canvas using their own CSS. + canvas.classList.add('blurhash-canvas'); + + target.parentNode.insertBefore(canvas, target); + target.classList.add('blurhashed'); + target.removeAttribute('data-blurhash'); + }); +} + +function itemBlurhashing(target, hash) { + try { + // Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us, + // improving the performance and reducing the memory usage, while retaining almost full blur quality. + // Lower values had more visible pixelation + const width = 20; + const height = 20; + targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target); + targetDic[hash].push(target); + + worker.postMessage({ + hash, + width, + height }); + } catch (err) { + console.error(err); + target.classList.add('non-blurhashable'); + return; + } +} + +export function fillImage(entry) { + if (!entry) { + throw new Error('entry cannot be null'); + } + const target = entry.target; + let source = undefined; + + if (target) { + source = target.getAttribute('data-src'); + } else { + source = entry; } - function itemBlurhashing(target, hash) { - try { - // Although the default values recommended by Blurhash developers is 32x32, a size of 20x20 seems to be the sweet spot for us, - // improving the performance and reducing the memory usage, while retaining almost full blur quality. - // Lower values had more visible pixelation - const width = 20; - const height = 20; - targetDic[hash] = (targetDic[hash] || []).filter(item => item !== target); - targetDic[hash].push(target); - - worker.postMessage({ - hash, - width, - height - }); - } catch (err) { - console.error(err); - target.classList.add('non-blurhashable'); - return; + if (entry.isIntersecting) { + if (source) { + fillImageElement(target, source); } + } else if (!source) { + emptyImageElement(target); } +} - export function fillImage(entry) { - if (!entry) { - throw new Error('entry cannot be null'); - } - const target = entry.target; - let source = undefined; - - if (target) { - source = target.getAttribute('data-src'); - } else { - source = entry; - } - - if (entry.isIntersecting) { - if (source) { - fillImageElement(target, source); - } - } else if (!source) { - emptyImageElement(target); - } - } - - function onAnimationEnd(event) { - const elem = event.target; - requestAnimationFrame(() => { - const canvas = elem.previousSibling; - if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') { - canvas.classList.add('lazy-hidden'); - } - - // HACK: Hide the content of the card padder - elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children'); - }); - elem.removeEventListener('animationend', onAnimationEnd); - } - - function fillImageElement(elem, url) { - if (url === undefined) { - throw new TypeError('url cannot be undefined'); - } - - const preloaderImg = new Image(); - preloaderImg.src = url; - - elem.classList.add('lazy-hidden'); - elem.addEventListener('animationend', onAnimationEnd); - - preloaderImg.addEventListener('load', () => { - requestAnimationFrame(() => { - if (elem.tagName !== 'IMG') { - elem.style.backgroundImage = "url('" + url + "')"; - } else { - elem.setAttribute('src', url); - } - elem.removeAttribute('data-src'); - - if (userSettings.enableFastFadein()) { - elem.classList.add('lazy-image-fadein-fast'); - } else { - elem.classList.add('lazy-image-fadein'); - } - elem.classList.remove('lazy-hidden'); - }); - }); - } - - function emptyImageElement(elem) { - elem.removeEventListener('animationend', onAnimationEnd); +function onAnimationEnd(event) { + const elem = event.target; + requestAnimationFrame(() => { const canvas = elem.previousSibling; - if (canvas?.tagName === 'CANVAS') { - canvas.classList.remove('lazy-hidden'); + if (elem.classList.contains('blurhashed') && canvas?.tagName === 'CANVAS') { + canvas.classList.add('lazy-hidden'); } - // HACK: Unhide the content of the card padder - elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children'); + // HACK: Hide the content of the card padder + elem.parentNode?.querySelector('.cardPadder')?.classList.add('lazy-hidden-children'); + }); + elem.removeEventListener('animationend', onAnimationEnd); +} - let url; - - if (elem.tagName !== 'IMG') { - url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); - elem.style.backgroundImage = 'none'; - } else { - url = elem.getAttribute('src'); - elem.setAttribute('src', ''); - } - elem.setAttribute('data-src', url); - - elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein'); - elem.classList.add('lazy-hidden'); +function fillImageElement(elem, url) { + if (url === undefined) { + throw new TypeError('url cannot be undefined'); } - export function lazyChildren(elem) { - if (userSettings.enableBlurhash()) { - for (const lazyElem of elem.querySelectorAll('.lazy')) { - const blurhashstr = lazyElem.getAttribute('data-blurhash'); - if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { - itemBlurhashing(lazyElem, blurhashstr); - } else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { - lazyElem.classList.add('non-blurhashable'); - } + const preloaderImg = new Image(); + preloaderImg.src = url; + + elem.classList.add('lazy-hidden'); + elem.addEventListener('animationend', onAnimationEnd); + + preloaderImg.addEventListener('load', () => { + requestAnimationFrame(() => { + if (elem.tagName !== 'IMG') { + elem.style.backgroundImage = "url('" + url + "')"; + } else { + elem.setAttribute('src', url); } - } + elem.removeAttribute('data-src'); - lazyLoader.lazyChildren(elem, fillImage); - } - - export function getPrimaryImageAspectRatio(items) { - const values = []; - - for (let i = 0, length = items.length; i < length; i++) { - const ratio = items[i].PrimaryImageAspectRatio || 0; - - if (!ratio) { - continue; + if (userSettings.enableFastFadein()) { + elem.classList.add('lazy-image-fadein-fast'); + } else { + elem.classList.add('lazy-image-fadein'); } - - values[values.length] = ratio; - } - - if (!values.length) { - return null; - } - - // Use the median - values.sort(function (a, b) { - return a - b; + elem.classList.remove('lazy-hidden'); }); + }); +} - const half = Math.floor(values.length / 2); - - let result; - - if (values.length % 2) { - result = values[half]; - } else { - result = (values[half - 1] + values[half]) / 2.0; - } - - // If really close to 2:3 (poster image), just return 2:3 - const aspect2x3 = 2 / 3; - if (Math.abs(aspect2x3 - result) <= 0.15) { - return aspect2x3; - } - - // If really close to 16:9 (episode image), just return 16:9 - const aspect16x9 = 16 / 9; - if (Math.abs(aspect16x9 - result) <= 0.2) { - return aspect16x9; - } - - // If really close to 1 (square image), just return 1 - if (Math.abs(1 - result) <= 0.15) { - return 1; - } - - // If really close to 4:3 (poster image), just return 2:3 - const aspect4x3 = 4 / 3; - if (Math.abs(aspect4x3 - result) <= 0.15) { - return aspect4x3; - } - - return result; +function emptyImageElement(elem) { + elem.removeEventListener('animationend', onAnimationEnd); + const canvas = elem.previousSibling; + if (canvas?.tagName === 'CANVAS') { + canvas.classList.remove('lazy-hidden'); } - export function fillImages(elems) { - for (let i = 0, length = elems.length; i < length; i++) { - const elem = elems[0]; - fillImage(elem); + // HACK: Unhide the content of the card padder + elem.parentNode?.querySelector('.cardPadder')?.classList.remove('lazy-hidden-children'); + + let url; + + if (elem.tagName !== 'IMG') { + url = elem.style.backgroundImage.slice(4, -1).replace(/"/g, ''); + elem.style.backgroundImage = 'none'; + } else { + url = elem.getAttribute('src'); + elem.setAttribute('src', ''); + } + elem.setAttribute('data-src', url); + + elem.classList.remove('lazy-image-fadein-fast', 'lazy-image-fadein'); + elem.classList.add('lazy-hidden'); +} + +export function lazyChildren(elem) { + if (userSettings.enableBlurhash()) { + for (const lazyElem of elem.querySelectorAll('.lazy')) { + const blurhashstr = lazyElem.getAttribute('data-blurhash'); + if (!lazyElem.classList.contains('blurhashed', 'non-blurhashable') && blurhashstr) { + itemBlurhashing(lazyElem, blurhashstr); + } else if (!blurhashstr && !lazyElem.classList.contains('blurhashed')) { + lazyElem.classList.add('non-blurhashable'); + } } } - export function setLazyImage(element, url) { - element.classList.add('lazy'); - element.setAttribute('data-src', url); - lazyImage(element); + lazyLoader.lazyChildren(elem, fillImage); +} + +export function getPrimaryImageAspectRatio(items) { + const values = []; + + for (let i = 0, length = items.length; i < length; i++) { + const ratio = items[i].PrimaryImageAspectRatio || 0; + + if (!ratio) { + continue; + } + + values[values.length] = ratio; } -/* eslint-enable indent */ + if (!values.length) { + return null; + } + + // Use the median + values.sort(function (a, b) { + return a - b; + }); + + const half = Math.floor(values.length / 2); + + let result; + + if (values.length % 2) { + result = values[half]; + } else { + result = (values[half - 1] + values[half]) / 2.0; + } + + // If really close to 2:3 (poster image), just return 2:3 + const aspect2x3 = 2 / 3; + if (Math.abs(aspect2x3 - result) <= 0.15) { + return aspect2x3; + } + + // If really close to 16:9 (episode image), just return 16:9 + const aspect16x9 = 16 / 9; + if (Math.abs(aspect16x9 - result) <= 0.2) { + return aspect16x9; + } + + // If really close to 1 (square image), just return 1 + if (Math.abs(1 - result) <= 0.15) { + return 1; + } + + // If really close to 4:3 (poster image), just return 2:3 + const aspect4x3 = 4 / 3; + if (Math.abs(aspect4x3 - result) <= 0.15) { + return aspect4x3; + } + + return result; +} + +export function fillImages(elems) { + for (let i = 0, length = elems.length; i < length; i++) { + const elem = elems[0]; + fillImage(elem); + } +} + +export function setLazyImage(element, url) { + element.classList.add('lazy'); + element.setAttribute('data-src', url); + lazyImage(element); +} + export default { setLazyImage: setLazyImage, fillImages: fillImages, diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index e132c706e3..a90effb336 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -9,636 +9,633 @@ import { playbackManager } from './playback/playbackmanager'; import ServerConnections from './ServerConnections'; import toast from './toast/toast'; -/* eslint-disable indent */ - export function getCommands(options) { - const item = options.item; - const user = options.user; +export function getCommands(options) { + const item = options.item; + const user = options.user; - const canPlay = playbackManager.canPlay(item); + const canPlay = playbackManager.canPlay(item); - const commands = []; + const commands = []; - if (canPlay && item.MediaType !== 'Photo') { - if (options.play !== false) { - commands.push({ - name: globalize.translate('Play'), - id: 'resume', - icon: 'play_arrow' - }); - } - - if (options.playAllFromHere && item.Type !== 'Program' && item.Type !== 'TvChannel') { - commands.push({ - name: globalize.translate('PlayAllFromHere'), - id: 'playallfromhere', - icon: 'play_arrow' - }); - } + if (canPlay && item.MediaType !== 'Photo') { + if (options.play !== false) { + commands.push({ + name: globalize.translate('Play'), + id: 'resume', + icon: 'play_arrow' + }); } - if (playbackManager.getCurrentPlayer() !== null) { - if (options.stopPlayback) { - commands.push({ - name: globalize.translate('StopPlayback'), - id: 'stopPlayback', - icon: 'stop' - }); - } - if (options.clearQueue) { - commands.push({ - name: globalize.translate('ClearQueue'), - id: 'clearQueue', - icon: 'clear_all' - }); - } + if (options.playAllFromHere && item.Type !== 'Program' && item.Type !== 'TvChannel') { + commands.push({ + name: globalize.translate('PlayAllFromHere'), + id: 'playallfromhere', + icon: 'play_arrow' + }); + } + } + + if (playbackManager.getCurrentPlayer() !== null) { + if (options.stopPlayback) { + commands.push({ + name: globalize.translate('StopPlayback'), + id: 'stopPlayback', + icon: 'stop' + }); + } + if (options.clearQueue) { + commands.push({ + name: globalize.translate('ClearQueue'), + id: 'clearQueue', + icon: 'clear_all' + }); + } + } + + if (playbackManager.canQueue(item)) { + if (options.queue !== false) { + commands.push({ + name: globalize.translate('AddToPlayQueue'), + id: 'queue', + icon: 'playlist_add' + }); } - if (playbackManager.canQueue(item)) { - if (options.queue !== false) { - commands.push({ - name: globalize.translate('AddToPlayQueue'), - id: 'queue', - icon: 'playlist_add' - }); - } - - if (options.queue !== false) { - commands.push({ - name: globalize.translate('PlayNext'), - id: 'queuenext', - icon: 'playlist_add' - }); - } + if (options.queue !== false) { + commands.push({ + name: globalize.translate('PlayNext'), + id: 'queuenext', + icon: 'playlist_add' + }); } + } - if ((item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + if ((item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') && item.CollectionType !== 'livetv' && options.shuffle !== false - ) { - commands.push({ - name: globalize.translate('Shuffle'), - id: 'shuffle', - icon: 'shuffle' - }); - } + ) { + commands.push({ + name: globalize.translate('Shuffle'), + id: 'shuffle', + icon: 'shuffle' + }); + } - if ((item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + if ((item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') && options.instantMix !== false && !itemHelper.isLocalItem(item) - ) { + ) { + commands.push({ + name: globalize.translate('InstantMix'), + id: 'instantmix', + icon: 'explore' + }); + } + + if (commands.length) { + commands.push({ + divider: true + }); + } + + if (!browser.tv) { + if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) { commands.push({ - name: globalize.translate('InstantMix'), - id: 'instantmix', - icon: 'explore' + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'playlist_add' }); } - if (commands.length) { + if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { commands.push({ - divider: true + name: globalize.translate('AddToPlaylist'), + id: 'addtoplaylist', + icon: 'playlist_add' }); } + } - if (!browser.tv) { - if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) { - commands.push({ - name: globalize.translate('AddToCollection'), - id: 'addtocollection', - icon: 'playlist_add' - }); - } + if ((item.Type === 'Timer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' + }); + } - if (itemHelper.supportsAddingToPlaylist(item) && options.playlist !== false) { - commands.push({ - name: globalize.translate('AddToPlaylist'), - id: 'addtoplaylist', - icon: 'playlist_add' - }); - } - } + if ((item.Type === 'Recording' && item.Status === 'InProgress') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelRecording'), + id: 'canceltimer', + icon: 'cancel' + }); + } - if ((item.Type === 'Timer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + if ((item.Type === 'SeriesTimer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { + commands.push({ + name: globalize.translate('CancelSeries'), + id: 'cancelseriestimer', + icon: 'cancel' + }); + } + + if (item.Type === 'Season' || item.Type == 'Series') { + commands.push({ + name: globalize.translate('DownloadAll'), + id: 'downloadall', + icon: 'file_download' + }); + } + + if (item.CanDelete && options.deleteItem !== false) { + if (item.Type === 'Playlist' || item.Type === 'BoxSet') { commands.push({ - name: globalize.translate('CancelRecording'), - id: 'canceltimer', - icon: 'cancel' + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' + }); + } else { + commands.push({ + name: globalize.translate('DeleteMedia'), + id: 'delete', + icon: 'delete' }); } + } - if ((item.Type === 'Recording' && item.Status === 'InProgress') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { - commands.push({ - name: globalize.translate('CancelRecording'), - id: 'canceltimer', - icon: 'cancel' - }); - } + // Books are promoted to major download Button and therefor excluded in the context menu + if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { + commands.push({ + name: globalize.translate('Download'), + id: 'download', + icon: 'file_download' + }); - if ((item.Type === 'SeriesTimer') && user.Policy.EnableLiveTvManagement && options.cancelTimer !== false) { - commands.push({ - name: globalize.translate('CancelSeries'), - id: 'cancelseriestimer', - icon: 'cancel' - }); - } + commands.push({ + name: globalize.translate('CopyStreamURL'), + id: 'copy-stream', + icon: 'content_copy' + }); + } - if (item.Type === 'Season' || item.Type == 'Series') { - commands.push({ - name: globalize.translate('DownloadAll'), - id: 'downloadall', - icon: 'file_download' - }); - } + if (commands.length) { + commands.push({ + divider: true + }); + } - if (item.CanDelete && options.deleteItem !== false) { - if (item.Type === 'Playlist' || item.Type === 'BoxSet') { - commands.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); - } else { - commands.push({ - name: globalize.translate('DeleteMedia'), - id: 'delete', - icon: 'delete' - }); - } - } + const canEdit = itemHelper.canEdit(user, item); + if (canEdit && options.edit !== false && item.Type !== 'SeriesTimer') { + const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); + commands.push({ + name: text, + id: 'edit', + icon: 'edit' + }); + } - // Books are promoted to major download Button and therefor excluded in the context menu - if ((item.CanDownload && appHost.supports('filedownload')) && item.Type !== 'Book') { - commands.push({ - name: globalize.translate('Download'), - id: 'download', - icon: 'file_download' - }); + if (itemHelper.canEditImages(user, item) && options.editImages !== false) { + commands.push({ + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'image' + }); + } - commands.push({ - name: globalize.translate('CopyStreamURL'), - id: 'copy-stream', - icon: 'content_copy' - }); - } - - if (commands.length) { - commands.push({ - divider: true - }); - } - - const canEdit = itemHelper.canEdit(user, item); - if (canEdit && options.edit !== false && item.Type !== 'SeriesTimer') { - const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); - commands.push({ - name: text, - id: 'edit', - icon: 'edit' - }); - } - - if (itemHelper.canEditImages(user, item) && options.editImages !== false) { - commands.push({ - name: globalize.translate('EditImages'), - id: 'editimages', - icon: 'image' - }); - } - - if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' + if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' && item.LocationType !== 'Virtual' && !(item.Type === 'Recording' && item.Status !== 'Completed') && options.editSubtitles !== false - ) { - commands.push({ - name: globalize.translate('EditSubtitles'), - id: 'editsubtitles', - icon: 'closed_caption' - }); - } - - if (options.identify !== false && itemHelper.canIdentify(user, item)) { - commands.push({ - name: globalize.translate('Identify'), - id: 'identify', - icon: 'edit' - }); - } - - if (item.MediaSources && options.moremediainfo !== false) { - commands.push({ - name: globalize.translate('MoreMediaInfo'), - id: 'moremediainfo', - icon: 'info' - }); - } - - if (item.Type === 'Program' && options.record !== false) { - if (item.TimerId) { - commands.push({ - name: globalize.translate('ManageRecording'), - id: 'record', - icon: 'fiber_manual_record' - }); - } else { - commands.push({ - name: globalize.translate('Record'), - id: 'record', - icon: 'fiber_manual_record' - }); - } - } - - if (itemHelper.canRefreshMetadata(item, user)) { - commands.push({ - name: globalize.translate('RefreshMetadata'), - id: 'refresh', - icon: 'refresh' - }); - } - - if (item.PlaylistItemId && options.playlistId) { - commands.push({ - name: globalize.translate('RemoveFromPlaylist'), - id: 'removefromplaylist', - icon: 'remove' - }); - } - - if (options.collectionId) { - commands.push({ - name: globalize.translate('RemoveFromCollection'), - id: 'removefromcollection', - icon: 'remove' - }); - } - - if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) { - commands.push({ - name: globalize.translate('Share'), - id: 'share', - icon: 'share' - }); - } - - if (options.sync !== false && itemHelper.canSync(user, item)) { - commands.push({ - name: globalize.translate('Sync'), - id: 'sync', - icon: 'sync' - }); - } - - if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { - commands.push({ - name: globalize.translate('ViewAlbum'), - id: 'album', - icon: 'album' - }); - } - // Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to? - // Although some albums can have multiple artists, it's not as common as songs. - if (options.openArtist !== false && item.AlbumArtists && item.AlbumArtists.length) { - commands.push({ - name: globalize.translate('ViewAlbumArtist'), - id: 'artist', - icon: 'person' - }); - } - - return commands; - } - - function getResolveFunction(resolve, id, changed, deleted) { - return function () { - resolve({ - command: id, - updated: changed, - deleted: deleted - }); - }; - } - - function executeCommand(item, id, options) { - const itemId = item.Id; - const serverId = item.ServerId; - const apiClient = ServerConnections.getApiClient(serverId); - - return new Promise(function (resolve, reject) { - // eslint-disable-next-line sonarjs/max-switch-cases - switch (id) { - case 'addtocollection': - import('./collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { - const collectionEditor = new CollectionEditor(); - collectionEditor.show({ - items: [itemId], - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'addtoplaylist': - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ - items: [itemId], - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'download': - import('../scripts/fileDownloader').then((fileDownloader) => { - const downloadHref = apiClient.getItemDownloadUrl(itemId); - fileDownloader.download([{ - url: downloadHref, - itemId: itemId, - serverId: serverId, - title: item.Name, - filename: item.Path.replace(/^.*[\\/]/, '') - }]); - getResolveFunction(getResolveFunction(resolve, id), id)(); - }); - break; - case 'downloadall': { - const downloadEpisodes = episodes => { - import('../scripts/fileDownloader').then((fileDownloader) => { - const downloads = episodes.map(episode => { - const downloadHref = apiClient.getItemDownloadUrl(episode.Id); - return { - url: downloadHref, - itemId: episode.Id, - serverId: serverId, - title: episode.Name, - filename: episode.Path.replace(/^.*[\\/]/, '') - }; - }); - - fileDownloader.download(downloads); - }); - }; - const downloadSeasons = seasons => { - Promise.all(seasons.map(seasonItem => { - return apiClient.getEpisodes(seasonItem.SeriesId, { - seasonId: seasonItem.Id, - userId: options.user.Id, - Fields: 'CanDownload,Path' - }); - } - )).then(seasonData => { - downloadEpisodes(seasonData.map(season => season.Items).flat()); - }); - }; - - if (item.Type === 'Season') { - downloadSeasons([item]); - } else if (item.Type === 'Series') { - apiClient.getSeasons(item.Id, { - userId: options.user.Id, - Fields: 'ItemCounts' - }).then(seasons => downloadSeasons(seasons.Items)); - } - - getResolveFunction(getResolveFunction(resolve, id), id)(); - break; - } - case 'copy-stream': { - const downloadHref = apiClient.getItemDownloadUrl(itemId); - copy(downloadHref).then(() => { - toast(globalize.translate('CopyStreamURLSuccess')); - }).catch(() => { - prompt(globalize.translate('CopyStreamURL'), downloadHref); - }); - getResolveFunction(resolve, id)(); - break; - } - case 'editsubtitles': - import('./subtitleeditor/subtitleeditor').then(({ default: subtitleEditor }) => { - subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'edit': - editItem(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - break; - case 'editimages': - import('./imageeditor/imageeditor').then((imageEditor) => { - imageEditor.show({ - itemId: itemId, - serverId: serverId - }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'identify': - import('./itemidentifier/itemidentifier').then((itemIdentifier) => { - itemIdentifier.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'moremediainfo': - import('./itemMediaInfo/itemMediaInfo').then((itemMediaInfo) => { - itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); - }); - break; - case 'refresh': - refresh(apiClient, item); - getResolveFunction(resolve, id)(); - break; - case 'open': - appRouter.showItem(item); - getResolveFunction(resolve, id)(); - break; - case 'play': - play(item, false); - getResolveFunction(resolve, id)(); - break; - case 'resume': - play(item, true); - getResolveFunction(resolve, id)(); - break; - case 'queue': - play(item, false, true); - getResolveFunction(resolve, id)(); - break; - case 'queuenext': - play(item, false, true, true); - getResolveFunction(resolve, id)(); - break; - case 'stopPlayback': - playbackManager.stop(); - break; - case 'clearQueue': - playbackManager.clearQueue(); - break; - case 'record': - import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { - recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); - }); - break; - case 'shuffle': - playbackManager.shuffle(item); - getResolveFunction(resolve, id)(); - break; - case 'instantmix': - playbackManager.instantMix(item); - getResolveFunction(resolve, id)(); - break; - case 'delete': - deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); - break; - case 'share': - navigator.share({ - title: item.Name, - text: item.Overview, - url: `${apiClient.serverAddress()}/web/index.html${appRouter.getRouteUrl(item)}` - }); - break; - case 'album': - appRouter.showItem(item.AlbumId, item.ServerId); - getResolveFunction(resolve, id)(); - break; - case 'artist': - appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); - getResolveFunction(resolve, id)(); - break; - case 'playallfromhere': - getResolveFunction(resolve, id)(); - break; - case 'queueallfromhere': - getResolveFunction(resolve, id)(); - break; - case 'removefromplaylist': - apiClient.ajax({ - url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', { - EntryIds: [item.PlaylistItemId].join(',') - }), - type: 'DELETE' - }).then(function () { - getResolveFunction(resolve, id, true)(); - }); - break; - case 'removefromcollection': - apiClient.ajax({ - type: 'DELETE', - url: apiClient.getUrl('Collections/' + options.collectionId + '/Items', { - - Ids: [item.Id].join(',') - }) - }).then(function () { - getResolveFunction(resolve, id, true)(); - }); - break; - case 'canceltimer': - deleteTimer(apiClient, item, resolve, id); - break; - case 'cancelseriestimer': - deleteSeriesTimer(apiClient, item, resolve, id); - break; - default: - reject(); - break; - } + ) { + commands.push({ + name: globalize.translate('EditSubtitles'), + id: 'editsubtitles', + icon: 'closed_caption' }); } - function deleteTimer(apiClient, item, resolve, command) { - import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { - const timerId = item.TimerId || item.Id; - recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () { - getResolveFunction(resolve, command, true)(); - }); + if (options.identify !== false && itemHelper.canIdentify(user, item)) { + commands.push({ + name: globalize.translate('Identify'), + id: 'identify', + icon: 'edit' }); } - function deleteSeriesTimer(apiClient, item, resolve, command) { - import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { - recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () { - getResolveFunction(resolve, command, true)(); - }); + if (item.MediaSources && options.moremediainfo !== false) { + commands.push({ + name: globalize.translate('MoreMediaInfo'), + id: 'moremediainfo', + icon: 'info' }); } - function play(item, resume, queue, queueNext) { - let method = 'play'; - if (queue) { - if (queueNext) { - method = 'queueNext'; - } else { - method = 'queue'; - } - } - - let startPosition = 0; - if (resume && item.UserData && item.UserData.PlaybackPositionTicks) { - startPosition = item.UserData.PlaybackPositionTicks; - } - - if (item.Type === 'Program') { - playbackManager[method]({ - ids: [item.ChannelId], - startPositionTicks: startPosition, - serverId: item.ServerId + if (item.Type === 'Program' && options.record !== false) { + if (item.TimerId) { + commands.push({ + name: globalize.translate('ManageRecording'), + id: 'record', + icon: 'fiber_manual_record' }); } else { - playbackManager[method]({ - items: [item], - startPositionTicks: startPosition + commands.push({ + name: globalize.translate('Record'), + id: 'record', + icon: 'fiber_manual_record' }); } } - function editItem(apiClient, item) { - return new Promise(function (resolve, reject) { - const serverId = apiClient.serverInfo().Id; + if (itemHelper.canRefreshMetadata(item, user)) { + commands.push({ + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' + }); + } - if (item.Type === 'Timer') { - import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, serverId).then(resolve, reject); + if (item.PlaylistItemId && options.playlistId) { + commands.push({ + name: globalize.translate('RemoveFromPlaylist'), + id: 'removefromplaylist', + icon: 'remove' + }); + } + + if (options.collectionId) { + commands.push({ + name: globalize.translate('RemoveFromCollection'), + id: 'removefromcollection', + icon: 'remove' + }); + } + + if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) { + commands.push({ + name: globalize.translate('Share'), + id: 'share', + icon: 'share' + }); + } + + if (options.sync !== false && itemHelper.canSync(user, item)) { + commands.push({ + name: globalize.translate('Sync'), + id: 'sync', + icon: 'sync' + }); + } + + if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { + commands.push({ + name: globalize.translate('ViewAlbum'), + id: 'album', + icon: 'album' + }); + } + // Show Album Artist by default, as a song can have multiple artists, which specific one would this option refer to? + // Although some albums can have multiple artists, it's not as common as songs. + if (options.openArtist !== false && item.AlbumArtists && item.AlbumArtists.length) { + commands.push({ + name: globalize.translate('ViewAlbumArtist'), + id: 'artist', + icon: 'person' + }); + } + + return commands; +} + +function getResolveFunction(resolve, id, changed, deleted) { + return function () { + resolve({ + command: id, + updated: changed, + deleted: deleted + }); + }; +} + +function executeCommand(item, id, options) { + const itemId = item.Id; + const serverId = item.ServerId; + const apiClient = ServerConnections.getApiClient(serverId); + + return new Promise(function (resolve, reject) { + // eslint-disable-next-line sonarjs/max-switch-cases + switch (id) { + case 'addtocollection': + import('./collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: [itemId], + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); - } else if (item.Type === 'SeriesTimer') { - import('./recordingcreator/seriesrecordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, serverId).then(resolve, reject); + break; + case 'addtoplaylist': + import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor({ + items: [itemId], + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); }); - } else { - import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { - metadataEditor.show(item.Id, serverId).then(resolve, reject); + break; + case 'download': + import('../scripts/fileDownloader').then((fileDownloader) => { + const downloadHref = apiClient.getItemDownloadUrl(itemId); + fileDownloader.download([{ + url: downloadHref, + itemId: itemId, + serverId: serverId, + title: item.Name, + filename: item.Path.replace(/^.*[\\/]/, '') + }]); + getResolveFunction(getResolveFunction(resolve, id), id)(); }); + break; + case 'downloadall': { + const downloadEpisodes = episodes => { + import('../scripts/fileDownloader').then((fileDownloader) => { + const downloads = episodes.map(episode => { + const downloadHref = apiClient.getItemDownloadUrl(episode.Id); + return { + url: downloadHref, + itemId: episode.Id, + serverId: serverId, + title: episode.Name, + filename: episode.Path.replace(/^.*[\\/]/, '') + }; + }); + + fileDownloader.download(downloads); + }); + }; + const downloadSeasons = seasons => { + Promise.all(seasons.map(seasonItem => { + return apiClient.getEpisodes(seasonItem.SeriesId, { + seasonId: seasonItem.Id, + userId: options.user.Id, + Fields: 'CanDownload,Path' + }); + } + )).then(seasonData => { + downloadEpisodes(seasonData.map(season => season.Items).flat()); + }); + }; + + if (item.Type === 'Season') { + downloadSeasons([item]); + } else if (item.Type === 'Series') { + apiClient.getSeasons(item.Id, { + userId: options.user.Id, + Fields: 'ItemCounts' + }).then(seasons => downloadSeasons(seasons.Items)); + } + + getResolveFunction(getResolveFunction(resolve, id), id)(); + break; } - }); - } - - function deleteItem(apiClient, item) { - return new Promise(function (resolve, reject) { - import('../scripts/deleteHelper').then((deleteHelper) => { - deleteHelper.deleteItem({ - item: item, - navigate: false + case 'copy-stream': { + const downloadHref = apiClient.getItemDownloadUrl(itemId); + copy(downloadHref).then(() => { + toast(globalize.translate('CopyStreamURLSuccess')); + }).catch(() => { + prompt(globalize.translate('CopyStreamURL'), downloadHref); + }); + getResolveFunction(resolve, id)(); + break; + } + case 'editsubtitles': + import('./subtitleeditor/subtitleeditor').then(({ default: subtitleEditor }) => { + subtitleEditor.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'edit': + editItem(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + break; + case 'editimages': + import('./imageeditor/imageeditor').then((imageEditor) => { + imageEditor.show({ + itemId: itemId, + serverId: serverId + }).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'identify': + import('./itemidentifier/itemidentifier').then((itemIdentifier) => { + itemIdentifier.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'moremediainfo': + import('./itemMediaInfo/itemMediaInfo').then((itemMediaInfo) => { + itemMediaInfo.show(itemId, serverId).then(getResolveFunction(resolve, id), getResolveFunction(resolve, id)); + }); + break; + case 'refresh': + refresh(apiClient, item); + getResolveFunction(resolve, id)(); + break; + case 'open': + appRouter.showItem(item); + getResolveFunction(resolve, id)(); + break; + case 'play': + play(item, false); + getResolveFunction(resolve, id)(); + break; + case 'resume': + play(item, true); + getResolveFunction(resolve, id)(); + break; + case 'queue': + play(item, false, true); + getResolveFunction(resolve, id)(); + break; + case 'queuenext': + play(item, false, true, true); + getResolveFunction(resolve, id)(); + break; + case 'stopPlayback': + playbackManager.stop(); + break; + case 'clearQueue': + playbackManager.clearQueue(); + break; + case 'record': + import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { + recordingCreator.show(itemId, serverId).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id)); + }); + break; + case 'shuffle': + playbackManager.shuffle(item); + getResolveFunction(resolve, id)(); + break; + case 'instantmix': + playbackManager.instantMix(item); + getResolveFunction(resolve, id)(); + break; + case 'delete': + deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); + break; + case 'share': + navigator.share({ + title: item.Name, + text: item.Overview, + url: `${apiClient.serverAddress()}/web/index.html${appRouter.getRouteUrl(item)}` + }); + break; + case 'album': + appRouter.showItem(item.AlbumId, item.ServerId); + getResolveFunction(resolve, id)(); + break; + case 'artist': + appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); + getResolveFunction(resolve, id)(); + break; + case 'playallfromhere': + getResolveFunction(resolve, id)(); + break; + case 'queueallfromhere': + getResolveFunction(resolve, id)(); + break; + case 'removefromplaylist': + apiClient.ajax({ + url: apiClient.getUrl('Playlists/' + options.playlistId + '/Items', { + EntryIds: [item.PlaylistItemId].join(',') + }), + type: 'DELETE' }).then(function () { - resolve(true); - }, reject); - }); - }); - } + getResolveFunction(resolve, id, true)(); + }); + break; + case 'removefromcollection': + apiClient.ajax({ + type: 'DELETE', + url: apiClient.getUrl('Collections/' + options.collectionId + '/Items', { - function refresh(apiClient, item) { - import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: [item.Id], - serverId: apiClient.serverInfo().Id, - mode: item.Type === 'CollectionFolder' ? 'scan' : null - }).show(); - }); - } - - export function show(options) { - const commands = getCommands(options); - if (!commands.length) { - return Promise.reject(); + Ids: [item.Id].join(',') + }) + }).then(function () { + getResolveFunction(resolve, id, true)(); + }); + break; + case 'canceltimer': + deleteTimer(apiClient, item, resolve, id); + break; + case 'cancelseriestimer': + deleteSeriesTimer(apiClient, item, resolve, id); + break; + default: + reject(); + break; } + }); +} - return actionsheet.show({ - items: commands, - positionTo: options.positionTo, - resolveOnClick: ['share'] - }).then(function (id) { - return executeCommand(options.item, id, options); +function deleteTimer(apiClient, item, resolve, command) { + import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { + const timerId = item.TimerId || item.Id; + recordingHelper.cancelTimerWithConfirmation(timerId, item.ServerId).then(function () { + getResolveFunction(resolve, command, true)(); }); + }); +} + +function deleteSeriesTimer(apiClient, item, resolve, command) { + import('./recordingcreator/recordinghelper').then(({ default: recordingHelper }) => { + recordingHelper.cancelSeriesTimerWithConfirmation(item.Id, item.ServerId).then(function () { + getResolveFunction(resolve, command, true)(); + }); + }); +} + +function play(item, resume, queue, queueNext) { + let method = 'play'; + if (queue) { + if (queueNext) { + method = 'queueNext'; + } else { + method = 'queue'; + } } -/* eslint-enable indent */ + let startPosition = 0; + if (resume && item.UserData && item.UserData.PlaybackPositionTicks) { + startPosition = item.UserData.PlaybackPositionTicks; + } + + if (item.Type === 'Program') { + playbackManager[method]({ + ids: [item.ChannelId], + startPositionTicks: startPosition, + serverId: item.ServerId + }); + } else { + playbackManager[method]({ + items: [item], + startPositionTicks: startPosition + }); + } +} + +function editItem(apiClient, item) { + return new Promise(function (resolve, reject) { + const serverId = apiClient.serverInfo().Id; + + if (item.Type === 'Timer') { + import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, serverId).then(resolve, reject); + }); + } else if (item.Type === 'SeriesTimer') { + import('./recordingcreator/seriesrecordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, serverId).then(resolve, reject); + }); + } else { + import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { + metadataEditor.show(item.Id, serverId).then(resolve, reject); + }); + } + }); +} + +function deleteItem(apiClient, item) { + return new Promise(function (resolve, reject) { + import('../scripts/deleteHelper').then((deleteHelper) => { + deleteHelper.deleteItem({ + item: item, + navigate: false + }).then(function () { + resolve(true); + }, reject); + }); + }); +} + +function refresh(apiClient, item) { + import('./refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: [item.Id], + serverId: apiClient.serverInfo().Id, + mode: item.Type === 'CollectionFolder' ? 'scan' : null + }).show(); + }); +} + +export function show(options) { + const commands = getCommands(options); + if (!commands.length) { + return Promise.reject(); + } + + return actionsheet.show({ + items: commands, + positionTo: options.positionTo, + resolveOnClick: ['share'] + }).then(function (id) { + return executeCommand(options.item, id, options); + }); +} export default { getCommands: getCommands, diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index ac04df4815..0eb1348760 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for display media info. @@ -30,229 +29,228 @@ const copyButtonHtml = layoutManager.tv ? '' : >`; const attributeDelimiterHtml = layoutManager.tv ? '' : ': '; - function setMediaInfo(user, page, item) { - let html = item.MediaSources.map(version => { - return getMediaSourceHtml(user, item, version); - }).join('
'); - if (item.MediaSources.length > 1) { - html = `
${html}`; - } - const mediaInfoContent = page.querySelector('#mediaInfoContent'); - mediaInfoContent.innerHTML = html; - - for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) { - btn.addEventListener('click', () => { - const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent; - - copy(infoBlock.textContent).then(() => { - toast(globalize.translate('Copied')); - }).catch(() => { - console.error('Could not copy text'); - toast(globalize.translate('CopyFailed')); - }); - }); - } +function setMediaInfo(user, page, item) { + let html = item.MediaSources.map(version => { + return getMediaSourceHtml(user, item, version); + }).join('
'); + if (item.MediaSources.length > 1) { + html = `
${html}`; } + const mediaInfoContent = page.querySelector('#mediaInfoContent'); + mediaInfoContent.innerHTML = html; - function getMediaSourceHtml(user, item, version) { - let html = '
'; - if (version.Name) { - html += `

${escapeHtml(version.Name)}${copyButtonHtml}

\n`; - } - if (version.Container) { - html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}
`; - } - if (version.Formats && version.Formats.length) { - html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}
`; - } - if (version.Path && user && user.Policy.IsAdministrator) { - html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}
`; - } - if (version.Size) { - const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`; - html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}
`; - } - version.MediaStreams.sort(itemHelper.sortTracks); - for (const stream of version.MediaStreams) { - if (stream.Type === 'Data') { - continue; - } + for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) { + btn.addEventListener('click', () => { + const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent; - html += '
'; - let translateString; - switch (stream.Type) { - case 'Audio': - case 'Data': - case 'Subtitle': - case 'Video': - translateString = stream.Type; - break; - case 'EmbeddedImage': - translateString = 'Image'; - break; - } - - const displayType = globalize.translate(translateString); - html += `\n

${displayType}${copyButtonHtml}

\n`; - const attributes = []; - if (stream.DisplayTitle) { - attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle)); - } - if (stream.Language && stream.Type !== 'Video') { - attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language)); - } - if (stream.Codec) { - attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase())); - } - if (stream.CodecTag) { - attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag)); - } - if (stream.IsAVC != null) { - attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No'))); - } - if (stream.Profile) { - attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile)); - } - if (stream.Level > 0) { - attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level)); - } - if (stream.Width || stream.Height) { - attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`)); - } - if (stream.AspectRatio && stream.Codec !== 'mjpeg') { - attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio)); - } - if (stream.Type === 'Video') { - if (stream.IsAnamorphic != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No'))); - } - attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No'))); - } - if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') { - attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate))); - } - if (stream.ChannelLayout) { - attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout)); - } - if (stream.Channels) { - attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`)); - } - if (stream.BitRate) { - attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`)); - } - if (stream.SampleRate) { - attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`)); - } - if (stream.BitDepth) { - attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`)); - } - if (stream.VideoRange) { - attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange)); - } - if (stream.VideoRangeType) { - attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType)); - } - if (stream.VideoDoViTitle) { - attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle)); - if (stream.DvVersionMajor != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor)); - } - if (stream.DvVersionMinor != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor)); - } - if (stream.DvProfile != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile)); - } - if (stream.DvLevel != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel)); - } - if (stream.RpuPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag)); - } - if (stream.ElPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag)); - } - if (stream.BlPresentFlag != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag)); - } - if (stream.DvBlSignalCompatibilityId != null) { - attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId)); - } - } - if (stream.ColorSpace) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace)); - } - if (stream.ColorTransfer) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer)); - } - if (stream.ColorPrimaries) { - attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries)); - } - if (stream.PixelFormat) { - attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat)); - } - if (stream.RefFrames) { - attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames)); - } - if (stream.NalLengthSize) { - attributes.push(createAttribute('NAL', stream.NalLengthSize)); - } - if (stream.Type === 'Subtitle' || stream.Type === 'Audio') { - attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No'))); - attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No'))); - attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No'))); - } - if (stream.Type === 'Video' && version.Timestamp) { - attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp)); - } - html += attributes.join('
'); - html += '
'; - } - html += '
'; - return html; - } - - // File Paths should be always ltr. The isLtr parameter allows this. - function createAttribute(label, value, isLtr) { - return `${label}${attributeDelimiterHtml}${escapeHtml(value)}\n`; - } - - function loadMediaInfo(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { - const dialogOptions = { - size: 'small', - removeOnClose: true, - scrollY: false - }; - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } - const dlg = dialogHelper.createDialog(dialogOptions); - dlg.classList.add('formDialog'); - let html = ''; - html += globalize.translateHtml(template, 'core'); - dlg.innerHTML = html; - if (layoutManager.tv) { - dlg.querySelector('.formDialogContent'); - } - dialogHelper.open(dlg); - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); + copy(infoBlock.textContent).then(() => { + toast(globalize.translate('Copied')); + }).catch(() => { + console.error('Could not copy text'); + toast(globalize.translate('CopyFailed')); }); - apiClient.getCurrentUser().then(user => { - setMediaInfo(user, dlg, item); - }); - loading.hide(); }); } +} - export function show(itemId, serverId) { - loading.show(); - return loadMediaInfo(itemId, serverId); +function getMediaSourceHtml(user, item, version) { + let html = '
'; + if (version.Name) { + html += `

${escapeHtml(version.Name)}${copyButtonHtml}

\n`; } + if (version.Container) { + html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}
`; + } + if (version.Formats && version.Formats.length) { + html += `${createAttribute(globalize.translate('MediaInfoFormat'), version.Formats.join(','))}
`; + } + if (version.Path && user && user.Policy.IsAdministrator) { + html += `${createAttribute(globalize.translate('MediaInfoPath'), version.Path, true)}
`; + } + if (version.Size) { + const size = `${(version.Size / (1024 * 1024)).toFixed(0)} MB`; + html += `${createAttribute(globalize.translate('MediaInfoSize'), size)}
`; + } + version.MediaStreams.sort(itemHelper.sortTracks); + for (const stream of version.MediaStreams) { + if (stream.Type === 'Data') { + continue; + } + + html += '
'; + let translateString; + switch (stream.Type) { + case 'Audio': + case 'Data': + case 'Subtitle': + case 'Video': + translateString = stream.Type; + break; + case 'EmbeddedImage': + translateString = 'Image'; + break; + } + + const displayType = globalize.translate(translateString); + html += `\n

${displayType}${copyButtonHtml}

\n`; + const attributes = []; + if (stream.DisplayTitle) { + attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle)); + } + if (stream.Language && stream.Type !== 'Video') { + attributes.push(createAttribute(globalize.translate('MediaInfoLanguage'), stream.Language)); + } + if (stream.Codec) { + attributes.push(createAttribute(globalize.translate('MediaInfoCodec'), stream.Codec.toUpperCase())); + } + if (stream.CodecTag) { + attributes.push(createAttribute(globalize.translate('MediaInfoCodecTag'), stream.CodecTag)); + } + if (stream.IsAVC != null) { + attributes.push(createAttribute('AVC', (stream.IsAVC ? 'Yes' : 'No'))); + } + if (stream.Profile) { + attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile)); + } + if (stream.Level > 0) { + attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level)); + } + if (stream.Width || stream.Height) { + attributes.push(createAttribute(globalize.translate('MediaInfoResolution'), `${stream.Width}x${stream.Height}`)); + } + if (stream.AspectRatio && stream.Codec !== 'mjpeg') { + attributes.push(createAttribute(globalize.translate('MediaInfoAspectRatio'), stream.AspectRatio)); + } + if (stream.Type === 'Video') { + if (stream.IsAnamorphic != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoAnamorphic'), (stream.IsAnamorphic ? 'Yes' : 'No'))); + } + attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No'))); + } + if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') { + attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate))); + } + if (stream.ChannelLayout) { + attributes.push(createAttribute(globalize.translate('MediaInfoLayout'), stream.ChannelLayout)); + } + if (stream.Channels) { + attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`)); + } + if (stream.BitRate) { + attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000, 10)} kbps`)); + } + if (stream.SampleRate) { + attributes.push(createAttribute(globalize.translate('MediaInfoSampleRate'), `${stream.SampleRate} Hz`)); + } + if (stream.BitDepth) { + attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`)); + } + if (stream.VideoRange) { + attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange)); + } + if (stream.VideoRangeType) { + attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType)); + } + if (stream.VideoDoViTitle) { + attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle)); + if (stream.DvVersionMajor != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor)); + } + if (stream.DvVersionMinor != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor)); + } + if (stream.DvProfile != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile)); + } + if (stream.DvLevel != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel)); + } + if (stream.RpuPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag)); + } + if (stream.ElPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag)); + } + if (stream.BlPresentFlag != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag)); + } + if (stream.DvBlSignalCompatibilityId != null) { + attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId)); + } + } + if (stream.ColorSpace) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace)); + } + if (stream.ColorTransfer) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorTransfer'), stream.ColorTransfer)); + } + if (stream.ColorPrimaries) { + attributes.push(createAttribute(globalize.translate('MediaInfoColorPrimaries'), stream.ColorPrimaries)); + } + if (stream.PixelFormat) { + attributes.push(createAttribute(globalize.translate('MediaInfoPixelFormat'), stream.PixelFormat)); + } + if (stream.RefFrames) { + attributes.push(createAttribute(globalize.translate('MediaInfoRefFrames'), stream.RefFrames)); + } + if (stream.NalLengthSize) { + attributes.push(createAttribute('NAL', stream.NalLengthSize)); + } + if (stream.Type === 'Subtitle' || stream.Type === 'Audio') { + attributes.push(createAttribute(globalize.translate('MediaInfoDefault'), (stream.IsDefault ? 'Yes' : 'No'))); + attributes.push(createAttribute(globalize.translate('MediaInfoForced'), (stream.IsForced ? 'Yes' : 'No'))); + attributes.push(createAttribute(globalize.translate('MediaInfoExternal'), (stream.IsExternal ? 'Yes' : 'No'))); + } + if (stream.Type === 'Video' && version.Timestamp) { + attributes.push(createAttribute(globalize.translate('MediaInfoTimestamp'), version.Timestamp)); + } + html += attributes.join('
'); + html += '
'; + } + html += '
'; + return html; +} + +// File Paths should be always ltr. The isLtr parameter allows this. +function createAttribute(label, value, isLtr) { + return `${label}${attributeDelimiterHtml}${escapeHtml(value)}\n`; +} + +function loadMediaInfo(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + return apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { + const dialogOptions = { + size: 'small', + removeOnClose: true, + scrollY: false + }; + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } + const dlg = dialogHelper.createDialog(dialogOptions); + dlg.classList.add('formDialog'); + let html = ''; + html += globalize.translateHtml(template, 'core'); + dlg.innerHTML = html; + if (layoutManager.tv) { + dlg.querySelector('.formDialogContent'); + } + dialogHelper.open(dlg); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + apiClient.getCurrentUser().then(user => { + setMediaInfo(user, dlg, item); + }); + loading.hide(); + }); +} + +export function show(itemId, serverId) { + loading.show(); + return loadMediaInfo(itemId, serverId); +} -/* eslint-enable indent */ export default { show: show }; diff --git a/src/components/itemidentifier/itemidentifier.js b/src/components/itemidentifier/itemidentifier.js index 9ada1d60c2..b1aa1a1b9b 100644 --- a/src/components/itemidentifier/itemidentifier.js +++ b/src/components/itemidentifier/itemidentifier.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for itemidentifier media item. @@ -24,388 +23,314 @@ import toast from '../toast/toast'; import template from './itemidentifier.template.html'; import datetime from '../../scripts/datetime'; - const enableFocusTransform = !browser.slow && !browser.edge; +const enableFocusTransform = !browser.slow && !browser.edge; - let currentItem; - let currentItemType; - let currentServerId; - let currentResolve; - let currentReject; - let hasChanges = false; - let currentSearchResult; +let currentItem; +let currentItemType; +let currentServerId; +let currentResolve; +let currentReject; +let hasChanges = false; +let currentSearchResult; - function getApiClient() { - return ServerConnections.getApiClient(currentServerId); +function getApiClient() { + return ServerConnections.getApiClient(currentServerId); +} + +function searchForIdentificationResults(page) { + let lookupInfo = { + ProviderIds: {} + }; + + let i; + let length; + const identifyField = page.querySelectorAll('.identifyField'); + let value; + for (i = 0, length = identifyField.length; i < length; i++) { + value = identifyField[i].value; + + if (value) { + if (identifyField[i].type === 'number') { + value = parseInt(value, 10); + } + + lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; + } } - function searchForIdentificationResults(page) { - let lookupInfo = { - ProviderIds: {} - }; + let hasId = false; - let i; - let length; - const identifyField = page.querySelectorAll('.identifyField'); - let value; - for (i = 0, length = identifyField.length; i < length; i++) { - value = identifyField[i].value; + const txtLookupId = page.querySelectorAll('.txtLookupId'); + for (i = 0, length = txtLookupId.length; i < length; i++) { + value = txtLookupId[i].value; - if (value) { - if (identifyField[i].type === 'number') { - value = parseInt(value, 10); - } - - lookupInfo[identifyField[i].getAttribute('data-lookup')] = value; - } + if (value) { + hasId = true; } + lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; + } - let hasId = false; + if (!hasId && !lookupInfo.Name) { + toast(globalize.translate('PleaseEnterNameOrId')); + return; + } - const txtLookupId = page.querySelectorAll('.txtLookupId'); - for (i = 0, length = txtLookupId.length; i < length; i++) { - value = txtLookupId[i].value; + lookupInfo = { + SearchInfo: lookupInfo + }; - if (value) { - hasId = true; - } - lookupInfo.ProviderIds[txtLookupId[i].getAttribute('data-providerkey')] = value; - } + if (currentItem && currentItem.Id) { + lookupInfo.ItemId = currentItem.Id; + } else { + lookupInfo.IncludeDisabledProviders = true; + } - if (!hasId && !lookupInfo.Name) { - toast(globalize.translate('PleaseEnterNameOrId')); - return; - } + loading.show(); - lookupInfo = { - SearchInfo: lookupInfo - }; + const apiClient = getApiClient(); - if (currentItem && currentItem.Id) { - lookupInfo.ItemId = currentItem.Id; + apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`), + data: JSON.stringify(lookupInfo), + contentType: 'application/json', + dataType: 'json' + + }).then(results => { + loading.hide(); + showIdentificationSearchResults(page, results); + }); +} + +function showIdentificationSearchResults(page, results) { + const identificationSearchResults = page.querySelector('.identificationSearchResults'); + + page.querySelector('.popupIdentifyForm').classList.add('hide'); + identificationSearchResults.classList.remove('hide'); + page.querySelector('.identifyOptionsForm').classList.add('hide'); + page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered'); + + let html = ''; + let i; + let length; + for (i = 0, length = results.length; i < length; i++) { + const result = results[i]; + html += getSearchResultHtml(result, i); + } + + const elem = page.querySelector('.identificationSearchResultList'); + elem.innerHTML = html; + + function onSearchImageClick() { + const index = parseInt(this.getAttribute('data-index'), 10); + + const currentResult = results[index]; + + if (currentItem != null) { + showIdentifyOptions(page, currentResult); } else { - lookupInfo.IncludeDisabledProviders = true; - } - - loading.show(); - - const apiClient = getApiClient(); - - apiClient.ajax({ - type: 'POST', - url: apiClient.getUrl(`Items/RemoteSearch/${currentItemType}`), - data: JSON.stringify(lookupInfo), - contentType: 'application/json', - dataType: 'json' - - }).then(results => { - loading.hide(); - showIdentificationSearchResults(page, results); - }); - } - - function showIdentificationSearchResults(page, results) { - const identificationSearchResults = page.querySelector('.identificationSearchResults'); - - page.querySelector('.popupIdentifyForm').classList.add('hide'); - identificationSearchResults.classList.remove('hide'); - page.querySelector('.identifyOptionsForm').classList.add('hide'); - page.querySelector('.dialogContentInner').classList.remove('dialog-content-centered'); - - let html = ''; - let i; - let length; - for (i = 0, length = results.length; i < length; i++) { - const result = results[i]; - html += getSearchResultHtml(result, i); - } - - const elem = page.querySelector('.identificationSearchResultList'); - elem.innerHTML = html; - - function onSearchImageClick() { - const index = parseInt(this.getAttribute('data-index'), 10); - - const currentResult = results[index]; - - if (currentItem != null) { - showIdentifyOptions(page, currentResult); - } else { - finishFindNewDialog(page, currentResult); - } - } - - const searchImages = elem.querySelectorAll('.card'); - for (i = 0, length = searchImages.length; i < length; i++) { - searchImages[i].addEventListener('click', onSearchImageClick); - } - - if (layoutManager.tv) { - focusManager.autoFocus(identificationSearchResults); + finishFindNewDialog(page, currentResult); } } - function finishFindNewDialog(dlg, identifyResult) { - currentSearchResult = identifyResult; + const searchImages = elem.querySelectorAll('.card'); + for (i = 0, length = searchImages.length; i < length; i++) { + searchImages[i].addEventListener('click', onSearchImageClick); + } + + if (layoutManager.tv) { + focusManager.autoFocus(identificationSearchResults); + } +} + +function finishFindNewDialog(dlg, identifyResult) { + currentSearchResult = identifyResult; + hasChanges = true; + loading.hide(); + + dialogHelper.close(dlg); +} + +function showIdentifyOptions(page, identifyResult) { + const identifyOptionsForm = page.querySelector('.identifyOptionsForm'); + + page.querySelector('.popupIdentifyForm').classList.add('hide'); + page.querySelector('.identificationSearchResults').classList.add('hide'); + identifyOptionsForm.classList.remove('hide'); + page.querySelector('#chkIdentifyReplaceImages').checked = true; + page.querySelector('.dialogContentInner').classList.add('dialog-content-centered'); + + currentSearchResult = identifyResult; + + const lines = []; + lines.push(escapeHtml(identifyResult.Name)); + + if (identifyResult.ProductionYear) { + lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false })); + } + + let resultHtml = lines.join('
'); + + if (identifyResult.ImageUrl) { + resultHtml = `
${resultHtml}
`; + } + + page.querySelector('.selectedSearchResult').innerHTML = resultHtml; + + focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit')); +} + +function getSearchResultHtml(result, index) { + // TODO move card creation code to Card component + + let html = ''; + let cssClass = 'card scalableCard'; + let cardBoxCssClass = 'cardBox'; + let padderClass; + + if (currentItemType === 'Episode') { + cssClass += ' backdropCard backdropCard-scalable'; + padderClass = 'cardPadder-backdrop'; + } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { + cssClass += ' squareCard squareCard-scalable'; + padderClass = 'cardPadder-square'; + } else { + cssClass += ' portraitCard portraitCard-scalable'; + padderClass = 'cardPadder-portrait'; + } + + if (layoutManager.tv) { + cssClass += ' show-focus'; + + if (enableFocusTransform) { + cssClass += ' show-animation'; + } + } + + cardBoxCssClass += ' cardBox-bottompadded'; + + html += `'; + return html; +} + +function submitIdentficationResult(page) { + loading.show(); + + const options = { + ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked + }; + + const apiClient = getApiClient(); + + apiClient.ajax({ + type: 'POST', + url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options), + data: JSON.stringify(currentSearchResult), + contentType: 'application/json' + + }).then(() => { hasChanges = true; loading.hide(); - dialogHelper.close(dlg); - } + dialogHelper.close(page); + }, () => { + loading.hide(); - function showIdentifyOptions(page, identifyResult) { - const identifyOptionsForm = page.querySelector('.identifyOptionsForm'); + dialogHelper.close(page); + }); +} - page.querySelector('.popupIdentifyForm').classList.add('hide'); - page.querySelector('.identificationSearchResults').classList.add('hide'); - identifyOptionsForm.classList.remove('hide'); - page.querySelector('#chkIdentifyReplaceImages').checked = true; - page.querySelector('.dialogContentInner').classList.add('dialog-content-centered'); - - currentSearchResult = identifyResult; - - const lines = []; - lines.push(escapeHtml(identifyResult.Name)); - - if (identifyResult.ProductionYear) { - lines.push(datetime.toLocaleString(identifyResult.ProductionYear, { useGrouping: false })); - } - - let resultHtml = lines.join('
'); - - if (identifyResult.ImageUrl) { - resultHtml = `
${resultHtml}
`; - } - - page.querySelector('.selectedSearchResult').innerHTML = resultHtml; - - focusManager.focus(identifyOptionsForm.querySelector('.btnSubmit')); - } - - function getSearchResultHtml(result, index) { - // TODO move card creation code to Card component +function showIdentificationForm(page, item) { + const apiClient = getApiClient(); + apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => { let html = ''; - let cssClass = 'card scalableCard'; - let cardBoxCssClass = 'cardBox'; - let padderClass; - if (currentItemType === 'Episode') { - cssClass += ' backdropCard backdropCard-scalable'; - padderClass = 'cardPadder-backdrop'; - } else if (currentItemType === 'MusicAlbum' || currentItemType === 'MusicArtist') { - cssClass += ' squareCard squareCard-scalable'; - padderClass = 'cardPadder-square'; - } else { - cssClass += ' portraitCard portraitCard-scalable'; - padderClass = 'cardPadder-portrait'; - } + for (let i = 0, length = idList.length; i < length; i++) { + const idInfo = idList[i]; - if (layoutManager.tv) { - cssClass += ' show-focus'; + const id = `txtLookup${idInfo.Key}`; - if (enableFocusTransform) { - cssClass += ' show-animation'; + html += '
'; + + let fullName = idInfo.Name; + if (idInfo.Type) { + fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`; } - } - cardBoxCssClass += ' cardBox-bottompadded'; + const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); - html += `'; - return html; - } + page.querySelector('#txtLookupName').value = ''; - function submitIdentficationResult(page) { - loading.show(); - - const options = { - ReplaceAllImages: page.querySelector('#chkIdentifyReplaceImages').checked - }; - - const apiClient = getApiClient(); - - apiClient.ajax({ - type: 'POST', - url: apiClient.getUrl(`Items/RemoteSearch/Apply/${currentItem.Id}`, options), - data: JSON.stringify(currentSearchResult), - contentType: 'application/json' - - }).then(() => { - hasChanges = true; - loading.hide(); - - dialogHelper.close(page); - }, () => { - loading.hide(); - - dialogHelper.close(page); - }); - } - - function showIdentificationForm(page, item) { - const apiClient = getApiClient(); - - apiClient.getJSON(apiClient.getUrl(`Items/${item.Id}/ExternalIdInfos`)).then(idList => { - let html = ''; - - for (let i = 0, length = idList.length; i < length; i++) { - const idInfo = idList[i]; - - const id = `txtLookup${idInfo.Key}`; - - html += '
'; - - let fullName = idInfo.Name; - if (idInfo.Type) { - fullName = `${idInfo.Name} ${globalize.translate(idInfo.Type)}`; - } - - const idLabel = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); - - html += ``; - - html += '
'; - } - - page.querySelector('#txtLookupName').value = ''; - - if (item.Type === 'Person' || item.Type === 'BoxSet') { - page.querySelector('.fldLookupYear').classList.add('hide'); - page.querySelector('#txtLookupYear').value = ''; - } else { - page.querySelector('.fldLookupYear').classList.remove('hide'); - page.querySelector('#txtLookupYear').value = ''; - } - - page.querySelector('.identifyProviderIds').innerHTML = html; - - page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify'); - }); - } - - function showEditor(itemId) { - loading.show(); - - const apiClient = getApiClient(); - - apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { - currentItem = item; - currentItemType = currentItem.Type; - - const dialogOptions = { - size: 'small', - removeOnClose: true, - scrollY: false - }; - - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; - } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - dlg.classList.add('recordingDialog'); - - let html = ''; - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - // Has to be assigned a z-index after the call to .open() - dlg.addEventListener('close', onDialogClosed); - - if (layoutManager.tv) { - scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); - } - - if (item.Path) { - dlg.querySelector('.fldPath').classList.remove('hide'); - } else { - dlg.querySelector('.fldPath').classList.add('hide'); - } - - dlg.querySelector('.txtPath').innerText = item.Path || ''; - - dialogHelper.open(dlg); - - dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { - e.preventDefault(); - searchForIdentificationResults(dlg); - return false; - }); - - dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => { - e.preventDefault(); - submitIdentficationResult(dlg); - return false; - }); - - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); - - dlg.classList.add('identifyDialog'); - - showIdentificationForm(dlg, item); - loading.hide(); - }); - } - - function onDialogClosed() { - loading.hide(); - if (hasChanges) { - currentResolve(); + if (item.Type === 'Person' || item.Type === 'BoxSet') { + page.querySelector('.fldLookupYear').classList.add('hide'); + page.querySelector('#txtLookupYear').value = ''; } else { - currentReject(); + page.querySelector('.fldLookupYear').classList.remove('hide'); + page.querySelector('#txtLookupYear').value = ''; } - } - // TODO investigate where this was used - function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) { - currentItem = null; - currentItemType = itemType; + page.querySelector('.identifyProviderIds').innerHTML = html; + + page.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Identify'); + }); +} + +function showEditor(itemId) { + loading.show(); + + const apiClient = getApiClient(); + + apiClient.getItem(apiClient.getCurrentUserId(), itemId).then(item => { + currentItem = item; + currentItemType = currentItem.Type; const dialogOptions = { size: 'small', @@ -427,15 +352,22 @@ import datetime from '../../scripts/datetime'; dlg.innerHTML = html; + // Has to be assigned a z-index after the call to .open() + dlg.addEventListener('close', onDialogClosed); + if (layoutManager.tv) { scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } - dialogHelper.open(dlg); + if (item.Path) { + dlg.querySelector('.fldPath').classList.remove('hide'); + } else { + dlg.querySelector('.fldPath').classList.add('hide'); + } - dlg.querySelector('.btnCancel').addEventListener('click', () => { - dialogHelper.close(dlg); - }); + dlg.querySelector('.txtPath').innerText = item.Path || ''; + + dialogHelper.open(dlg); dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { e.preventDefault(); @@ -443,53 +375,119 @@ import datetime from '../../scripts/datetime'; return false; }); - dlg.addEventListener('close', () => { - loading.hide(); - const foundItem = hasChanges ? currentSearchResult : null; + dlg.querySelector('.identifyOptionsForm').addEventListener('submit', e => { + e.preventDefault(); + submitIdentficationResult(dlg); + return false; + }); - resolveFunc(foundItem); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); }); dlg.classList.add('identifyDialog'); - showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); + showIdentificationForm(dlg, item); + loading.hide(); + }); +} + +function onDialogClosed() { + loading.hide(); + if (hasChanges) { + currentResolve(); + } else { + currentReject(); + } +} + +// TODO investigate where this was used +function showEditorFindNew(itemName, itemYear, itemType, resolveFunc) { + currentItem = null; + currentItemType = itemType; + + const dialogOptions = { + size: 'small', + removeOnClose: true, + scrollY: false + }; + + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; } - function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { - dlg.querySelector('#txtLookupName').value = itemName; + const dlg = dialogHelper.createDialog(dialogOptions); - if (itemType === 'Person' || itemType === 'BoxSet') { - dlg.querySelector('.fldLookupYear').classList.add('hide'); - dlg.querySelector('#txtLookupYear').value = ''; - } else { - dlg.querySelector('.fldLookupYear').classList.remove('hide'); - dlg.querySelector('#txtLookupYear').value = itemYear; - } + dlg.classList.add('formDialog'); + dlg.classList.add('recordingDialog'); - dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); + let html = ''; + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (layoutManager.tv) { + scrollHelper.centerFocus.on(dlg.querySelector('.formDialogContent'), false); } - export function show(itemId, serverId) { - return new Promise((resolve, reject) => { - currentResolve = resolve; - currentReject = reject; - currentServerId = serverId; - hasChanges = false; + dialogHelper.open(dlg); - showEditor(itemId); - }); + dlg.querySelector('.btnCancel').addEventListener('click', () => { + dialogHelper.close(dlg); + }); + + dlg.querySelector('.popupIdentifyForm').addEventListener('submit', e => { + e.preventDefault(); + searchForIdentificationResults(dlg); + return false; + }); + + dlg.addEventListener('close', () => { + loading.hide(); + const foundItem = hasChanges ? currentSearchResult : null; + + resolveFunc(foundItem); + }); + + dlg.classList.add('identifyDialog'); + + showIdentificationFormFindNew(dlg, itemName, itemYear, itemType); +} + +function showIdentificationFormFindNew(dlg, itemName, itemYear, itemType) { + dlg.querySelector('#txtLookupName').value = itemName; + + if (itemType === 'Person' || itemType === 'BoxSet') { + dlg.querySelector('.fldLookupYear').classList.add('hide'); + dlg.querySelector('#txtLookupYear').value = ''; + } else { + dlg.querySelector('.fldLookupYear').classList.remove('hide'); + dlg.querySelector('#txtLookupYear').value = itemYear; } - export function showFindNew(itemName, itemYear, itemType, serverId) { - return new Promise((resolve) => { - currentServerId = serverId; + dlg.querySelector('.formDialogHeaderTitle').innerHTML = globalize.translate('Search'); +} - hasChanges = false; - showEditorFindNew(itemName, itemYear, itemType, resolve); - }); - } +export function show(itemId, serverId) { + return new Promise((resolve, reject) => { + currentResolve = resolve; + currentReject = reject; + currentServerId = serverId; + hasChanges = false; + + showEditor(itemId); + }); +} + +export function showFindNew(itemName, itemYear, itemType, serverId) { + return new Promise((resolve) => { + currentServerId = serverId; + + hasChanges = false; + showEditorFindNew(itemName, itemYear, itemType, resolve); + }); +} -/* eslint-enable indent */ export default { show: show, showFindNew: showFindNew diff --git a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js index 3b78083cc2..81378710ff 100644 --- a/src/components/lazyLoader/lazyLoaderIntersectionObserver.js +++ b/src/components/lazyLoader/lazyLoaderIntersectionObserver.js @@ -1,69 +1,68 @@ -/* eslint-disable indent */ - export class LazyLoader { - constructor(options) { - this.options = options; - } - createObserver() { - const callback = this.options.callback; +export class LazyLoader { + constructor(options) { + this.options = options; + } - const observer = new IntersectionObserver( - (entries) => { - entries.forEach(entry => { - callback(entry); - }); - }, - { - rootMargin: '50%', - threshold: 0 + createObserver() { + const callback = this.options.callback; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach(entry => { + callback(entry); }); - - this.observer = observer; - } - - addElements(elements) { - let observer = this.observer; - - if (!observer) { - this.createObserver(); - observer = this.observer; - } - - Array.from(elements).forEach(element => { - observer.observe(element); + }, + { + rootMargin: '50%', + threshold: 0 }); - } - destroyObserver() { - const observer = this.observer; - - if (observer) { - observer.disconnect(); - this.observer = null; - } - } - - destroy() { - this.destroyObserver(); - this.options = null; - } + this.observer = observer; } - function unveilElements(elements, root, callback) { - if (!elements.length) { - return; + addElements(elements) { + let observer = this.observer; + + if (!observer) { + this.createObserver(); + observer = this.observer; } - const lazyLoader = new LazyLoader({ - callback: callback + + Array.from(elements).forEach(element => { + observer.observe(element); }); - lazyLoader.addElements(elements); } - export function lazyChildren(elem, callback) { - unveilElements(elem.getElementsByClassName('lazy'), elem, callback); + destroyObserver() { + const observer = this.observer; + + if (observer) { + observer.disconnect(); + this.observer = null; + } } -/* eslint-enable indent */ + destroy() { + this.destroyObserver(); + this.options = null; + } +} + +function unveilElements(elements, root, callback) { + if (!elements.length) { + return; + } + const lazyLoader = new LazyLoader({ + callback: callback + }); + lazyLoader.addElements(elements); +} + +export function lazyChildren(elem, callback) { + unveilElements(elem.getElementsByClassName('lazy'), elem, callback); +} + export default { LazyLoader: LazyLoader, lazyChildren: lazyChildren diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index dabaf1c8a9..0b0f66b61e 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for library options editor. @@ -14,606 +13,605 @@ import '../../elements/emby-input/emby-input'; import './style.scss'; import template from './libraryoptionseditor.template.html'; - function populateLanguages(parent) { - return ApiClient.getCultures().then(languages => { - populateLanguagesIntoSelect(parent.querySelector('#selectLanguage'), languages); - populateLanguagesIntoList(parent.querySelector('.subtitleDownloadLanguages'), languages); - }); - } +function populateLanguages(parent) { + return ApiClient.getCultures().then(languages => { + populateLanguagesIntoSelect(parent.querySelector('#selectLanguage'), languages); + populateLanguagesIntoList(parent.querySelector('.subtitleDownloadLanguages'), languages); + }); +} - function populateLanguagesIntoSelect(select, languages) { +function populateLanguagesIntoSelect(select, languages) { + let html = ''; + html += ""; + for (let i = 0; i < languages.length; i++) { + const culture = languages[i]; + html += ``; + } + select.innerHTML = html; +} + +function populateLanguagesIntoList(element, languages) { + let html = ''; + for (let i = 0; i < languages.length; i++) { + const culture = languages[i]; + html += ``; + } + element.innerHTML = html; +} + +function populateCountries(select) { + return ApiClient.getCountries().then(allCountries => { let html = ''; html += ""; - for (let i = 0; i < languages.length; i++) { - const culture = languages[i]; - html += ``; + for (let i = 0; i < allCountries.length; i++) { + const culture = allCountries[i]; + html += ``; } select.innerHTML = html; + }); +} + +function populateRefreshInterval(select) { + let html = ''; + html += ``; + html += [30, 60, 90].map(val => { + return ``; + }).join(''); + select.innerHTML = html; +} + +function renderMetadataReaders(page, plugins) { + let html = ''; + const elem = page.querySelector('.metadataReaders'); + + if (plugins.length < 1) { + elem.innerHTML = ''; + elem.classList.add('hide'); + return false; } - function populateLanguagesIntoList(element, languages) { - let html = ''; - for (let i = 0; i < languages.length; i++) { - const culture = languages[i]; - html += ``; - } - element.innerHTML = html; - } - - function populateCountries(select) { - return ApiClient.getCountries().then(allCountries => { - let html = ''; - html += ""; - for (let i = 0; i < allCountries.length; i++) { - const culture = allCountries[i]; - html += ``; - } - select.innerHTML = html; - }); - } - - function populateRefreshInterval(select) { - let html = ''; - html += ``; - html += [30, 60, 90].map(val => { - return ``; - }).join(''); - select.innerHTML = html; - } - - function renderMetadataReaders(page, plugins) { - let html = ''; - const elem = page.querySelector('.metadataReaders'); - - if (plugins.length < 1) { - elem.innerHTML = ''; - elem.classList.add('hide'); - return false; - } - - html += `

${globalize.translate('LabelMetadataReaders')}

`; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += `
`; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ``; - } else if (plugins.length > 1) { - html += ``; - } - html += '
'; + html += `

${globalize.translate('LabelMetadataReaders')}

`; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += `
`; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ``; + } else if (plugins.length > 1) { + html += ``; } html += '
'; - html += `
${globalize.translate('LabelMetadataReadersHelp')}
`; - if (plugins.length < 2) { - elem.classList.add('hide'); - } else { - elem.classList.remove('hide'); - } - elem.innerHTML = html; - return true; } - - function renderMetadataSavers(page, metadataSavers) { - let html = ''; - const elem = page.querySelector('.metadataSavers'); - if (!metadataSavers.length) { - elem.innerHTML = ''; - elem.classList.add('hide'); - return false; - } - html += `

${globalize.translate('LabelMetadataSavers')}

`; - html += '
'; - for (let i = 0; i < metadataSavers.length; i++) { - const plugin = metadataSavers[i]; - html += ``; - } - html += '
'; - html += `
${globalize.translate('LabelMetadataSaversHelp')}
`; - elem.innerHTML = html; + html += '
'; + html += `
${globalize.translate('LabelMetadataReadersHelp')}
`; + if (plugins.length < 2) { + elem.classList.add('hide'); + } else { elem.classList.remove('hide'); - return true; } + elem.innerHTML = html; + return true; +} - function getMetadataFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { - let html = ''; - let plugins = availableTypeOptions.MetadataFetchers; +function renderMetadataSavers(page, metadataSavers) { + let html = ''; + const elem = page.querySelector('.metadataSavers'); + if (!metadataSavers.length) { + elem.innerHTML = ''; + elem.classList.add('hide'); + return false; + } + html += `

${globalize.translate('LabelMetadataSavers')}

`; + html += '
'; + for (let i = 0; i < metadataSavers.length; i++) { + const plugin = metadataSavers[i]; + html += ``; + } + html += '
'; + html += `
${globalize.translate('LabelMetadataSaversHelp')}
`; + elem.innerHTML = html; + elem.classList.remove('hide'); + return true; +} - plugins = getOrderedPlugins(plugins, libraryOptionsForType.MetadataFetcherOrder || []); - if (!plugins.length) return html; +function getMetadataFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { + let html = ''; + let plugins = availableTypeOptions.MetadataFetchers; - html += '
'; - html += '

' + globalize.translate('LabelTypeMetadataDownloaders', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; - html += '
'; + plugins = getOrderedPlugins(plugins, libraryOptionsForType.MetadataFetcherOrder || []); + if (!plugins.length) return html; - plugins.forEach((plugin, index) => { - html += '
'; - const isChecked = libraryOptionsForType.MetadataFetchers ? libraryOptionsForType.MetadataFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (index > 0) { - html += ''; - } else if (plugins.length > 1) { - html += ''; - } - html += '
'; - }); + html += '
'; + html += '

' + globalize.translate('LabelTypeMetadataDownloaders', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; + html += '
'; + plugins.forEach((plugin, index) => { + html += '
'; + const isChecked = libraryOptionsForType.MetadataFetchers ? libraryOptionsForType.MetadataFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; html += '
'; - html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; - html += '
'; - return html; - } - - function getTypeOptions(allOptions, type) { - const allTypeOptions = allOptions.TypeOptions || []; - for (let i = 0; i < allTypeOptions.length; i++) { - const typeOptions = allTypeOptions[i]; - if (typeOptions.Type === type) return typeOptions; - } - return null; - } - - function renderMetadataFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.metadataFetchers'); - for (let i = 0; i < availableOptions.TypeOptions.length; i++) { - const availableTypeOptions = availableOptions.TypeOptions[i]; - html += getMetadataFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); - } - elem.innerHTML = html; - if (html) { - elem.classList.remove('hide'); - page.querySelector('.fldAutoRefreshInterval').classList.remove('hide'); - page.querySelector('.fldMetadataLanguage').classList.remove('hide'); - page.querySelector('.fldMetadataCountry').classList.remove('hide'); - } else { - elem.classList.add('hide'); - page.querySelector('.fldAutoRefreshInterval').classList.add('hide'); - page.querySelector('.fldMetadataLanguage').classList.add('hide'); - page.querySelector('.fldMetadataCountry').classList.add('hide'); - } - return true; - } - - function renderSubtitleFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.subtitleFetchers'); - - let plugins = availableOptions.SubtitleFetchers; - plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []); - if (!plugins.length) return html; - - html += `

${globalize.translate('LabelSubtitleDownloaders')}

`; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += `
`; - const isChecked = libraryOptions.DisabledSubtitleFetchers ? !libraryOptions.DisabledSubtitleFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ``; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ``; - } else if (plugins.length > 1) { - html += ``; - } - html += '
'; + if (index > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; } html += '
'; - html += `
${globalize.translate('SubtitleDownloadersHelp')}
`; - elem.innerHTML = html; + }); + + html += '
'; + html += '
' + globalize.translate('LabelMetadataDownloadersHelp') + '
'; + html += '
'; + return html; +} + +function getTypeOptions(allOptions, type) { + const allTypeOptions = allOptions.TypeOptions || []; + for (let i = 0; i < allTypeOptions.length; i++) { + const typeOptions = allTypeOptions[i]; + if (typeOptions.Type === type) return typeOptions; } + return null; +} - function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { - let html = ''; - let plugins = availableTypeOptions.ImageFetchers; +function renderMetadataFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.metadataFetchers'); + for (let i = 0; i < availableOptions.TypeOptions.length; i++) { + const availableTypeOptions = availableOptions.TypeOptions[i]; + html += getMetadataFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); + } + elem.innerHTML = html; + if (html) { + elem.classList.remove('hide'); + page.querySelector('.fldAutoRefreshInterval').classList.remove('hide'); + page.querySelector('.fldMetadataLanguage').classList.remove('hide'); + page.querySelector('.fldMetadataCountry').classList.remove('hide'); + } else { + elem.classList.add('hide'); + page.querySelector('.fldAutoRefreshInterval').classList.add('hide'); + page.querySelector('.fldMetadataLanguage').classList.add('hide'); + page.querySelector('.fldMetadataCountry').classList.add('hide'); + } + return true; +} - plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []); - if (!plugins.length) return html; +function renderSubtitleFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.subtitleFetchers'); - html += '
'; - html += '
'; - html += '

' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; - const supportedImageTypes = availableTypeOptions.SupportedImageTypes || []; - if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') { - html += ''; + let plugins = availableOptions.SubtitleFetchers; + plugins = getOrderedPlugins(plugins, libraryOptions.SubtitleFetcherOrder || []); + if (!plugins.length) return html; + + html += `

${globalize.translate('LabelSubtitleDownloaders')}

`; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += `
`; + const isChecked = libraryOptions.DisabledSubtitleFetchers ? !libraryOptions.DisabledSubtitleFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ``; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ``; + } else if (plugins.length > 1) { + html += ``; } html += '
'; - html += '
'; - for (let i = 0; i < plugins.length; i++) { - const plugin = plugins[i]; - html += '
'; - const isChecked = libraryOptionsForType.ImageFetchers ? libraryOptionsForType.ImageFetchers.includes(plugin.Name) : plugin.DefaultEnabled; - const checkedHtml = isChecked ? ' checked="checked"' : ''; - html += ''; - html += '
'; - html += '

'; - html += escapeHtml(plugin.Name); - html += '

'; - html += '
'; - if (i > 0) { - html += ''; - } else if (plugins.length > 1) { - html += ''; - } - html += '
'; + } + html += '
'; + html += `
${globalize.translate('SubtitleDownloadersHelp')}
`; + elem.innerHTML = html; +} + +function getImageFetchersForTypeHtml(availableTypeOptions, libraryOptionsForType) { + let html = ''; + let plugins = availableTypeOptions.ImageFetchers; + + plugins = getOrderedPlugins(plugins, libraryOptionsForType.ImageFetcherOrder || []); + if (!plugins.length) return html; + + html += '
'; + html += '
'; + html += '

' + globalize.translate('HeaderTypeImageFetchers', globalize.translate('TypeOptionPlural' + availableTypeOptions.Type)) + '

'; + const supportedImageTypes = availableTypeOptions.SupportedImageTypes || []; + if (supportedImageTypes.length > 1 || supportedImageTypes.length === 1 && supportedImageTypes[0] !== 'Primary') { + html += ''; + } + html += '
'; + html += '
'; + for (let i = 0; i < plugins.length; i++) { + const plugin = plugins[i]; + html += '
'; + const isChecked = libraryOptionsForType.ImageFetchers ? libraryOptionsForType.ImageFetchers.includes(plugin.Name) : plugin.DefaultEnabled; + const checkedHtml = isChecked ? ' checked="checked"' : ''; + html += ''; + html += '
'; + html += '

'; + html += escapeHtml(plugin.Name); + html += '

'; + html += '
'; + if (i > 0) { + html += ''; + } else if (plugins.length > 1) { + html += ''; } html += '
'; - html += '
' + globalize.translate('LabelImageFetchersHelp') + '
'; - html += '
'; - return html; } + html += '
'; + html += '
' + globalize.translate('LabelImageFetchersHelp') + '
'; + html += '
'; + return html; +} - function renderImageFetchers(page, availableOptions, libraryOptions) { - let html = ''; - const elem = page.querySelector('.imageFetchers'); - for (let i = 0; i < availableOptions.TypeOptions.length; i++) { - const availableTypeOptions = availableOptions.TypeOptions[i]; - html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); +function renderImageFetchers(page, availableOptions, libraryOptions) { + let html = ''; + const elem = page.querySelector('.imageFetchers'); + for (let i = 0; i < availableOptions.TypeOptions.length; i++) { + const availableTypeOptions = availableOptions.TypeOptions[i]; + html += getImageFetchersForTypeHtml(availableTypeOptions, getTypeOptions(libraryOptions, availableTypeOptions.Type) || {}); + } + elem.innerHTML = html; + if (html) { + elem.classList.remove('hide'); + page.querySelector('.chkSaveLocalContainer').classList.remove('hide'); + } else { + elem.classList.add('hide'); + page.querySelector('.chkSaveLocalContainer').classList.add('hide'); + } + return true; +} + +function populateMetadataSettings(parent, contentType) { + const isNewLibrary = parent.classList.contains('newlibrary'); + return ApiClient.getJSON(ApiClient.getUrl('Libraries/AvailableOptions', { + LibraryContentType: contentType, + IsNewLibrary: isNewLibrary + })).then(availableOptions => { + currentAvailableOptions = availableOptions; + parent.availableOptions = availableOptions; + renderMetadataSavers(parent, availableOptions.MetadataSavers); + renderMetadataReaders(parent, availableOptions.MetadataReaders); + renderMetadataFetchers(parent, availableOptions, {}); + renderSubtitleFetchers(parent, availableOptions, {}); + renderImageFetchers(parent, availableOptions, {}); + availableOptions.SubtitleFetchers.length ? parent.querySelector('.subtitleDownloadSettings').classList.remove('hide') : parent.querySelector('.subtitleDownloadSettings').classList.add('hide'); + }).catch(() => { + return Promise.resolve(); + }); +} + +function adjustSortableListElement(elem) { + const btnSortable = elem.querySelector('.btnSortable'); + const inner = btnSortable.querySelector('.material-icons'); + if (elem.previousSibling) { + btnSortable.title = globalize.translate('Up'); + btnSortable.classList.add('btnSortableMoveUp'); + btnSortable.classList.remove('btnSortableMoveDown'); + inner.classList.remove('keyboard_arrow_down'); + inner.classList.add('keyboard_arrow_up'); + } else { + btnSortable.title = globalize.translate('Down'); + btnSortable.classList.remove('btnSortableMoveUp'); + btnSortable.classList.add('btnSortableMoveDown'); + inner.classList.remove('keyboard_arrow_up'); + inner.classList.add('keyboard_arrow_down'); + } +} + +function showImageOptionsForType(type) { + import('../imageOptionsEditor/imageOptionsEditor').then(({ default: ImageOptionsEditor }) => { + let typeOptions = getTypeOptions(currentLibraryOptions, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + currentLibraryOptions.TypeOptions.push(typeOptions); } - elem.innerHTML = html; - if (html) { - elem.classList.remove('hide'); - page.querySelector('.chkSaveLocalContainer').classList.remove('hide'); - } else { - elem.classList.add('hide'); - page.querySelector('.chkSaveLocalContainer').classList.add('hide'); - } - return true; - } + const availableOptions = getTypeOptions(currentAvailableOptions || {}, type); + const imageOptionsEditor = new ImageOptionsEditor(); + imageOptionsEditor.show(type, typeOptions, availableOptions); + }); +} - function populateMetadataSettings(parent, contentType) { - const isNewLibrary = parent.classList.contains('newlibrary'); - return ApiClient.getJSON(ApiClient.getUrl('Libraries/AvailableOptions', { - LibraryContentType: contentType, - IsNewLibrary: isNewLibrary - })).then(availableOptions => { - currentAvailableOptions = availableOptions; - parent.availableOptions = availableOptions; - renderMetadataSavers(parent, availableOptions.MetadataSavers); - renderMetadataReaders(parent, availableOptions.MetadataReaders); - renderMetadataFetchers(parent, availableOptions, {}); - renderSubtitleFetchers(parent, availableOptions, {}); - renderImageFetchers(parent, availableOptions, {}); - availableOptions.SubtitleFetchers.length ? parent.querySelector('.subtitleDownloadSettings').classList.remove('hide') : parent.querySelector('.subtitleDownloadSettings').classList.add('hide'); - }).catch(() => { - return Promise.resolve(); - }); +function onImageFetchersContainerClick(e) { + const btnImageOptionsForType = dom.parentWithClass(e.target, 'btnImageOptionsForType'); + if (btnImageOptionsForType) { + showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, 'imageFetcher').getAttribute('data-type')); + return; } + onSortableContainerClick.call(this, e); +} - function adjustSortableListElement(elem) { - const btnSortable = elem.querySelector('.btnSortable'); - const inner = btnSortable.querySelector('.material-icons'); - if (elem.previousSibling) { - btnSortable.title = globalize.translate('Up'); - btnSortable.classList.add('btnSortableMoveUp'); - btnSortable.classList.remove('btnSortableMoveDown'); - inner.classList.remove('keyboard_arrow_down'); - inner.classList.add('keyboard_arrow_up'); - } else { - btnSortable.title = globalize.translate('Down'); - btnSortable.classList.remove('btnSortableMoveUp'); - btnSortable.classList.add('btnSortableMoveDown'); - inner.classList.remove('keyboard_arrow_up'); - inner.classList.add('keyboard_arrow_down'); - } - } - - function showImageOptionsForType(type) { - import('../imageOptionsEditor/imageOptionsEditor').then(({ default: ImageOptionsEditor }) => { - let typeOptions = getTypeOptions(currentLibraryOptions, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - currentLibraryOptions.TypeOptions.push(typeOptions); +function onSortableContainerClick(e) { + const btnSortable = dom.parentWithClass(e.target, 'btnSortable'); + if (btnSortable) { + const li = dom.parentWithClass(btnSortable, 'sortableOption'); + const list = dom.parentWithClass(li, 'paperList'); + if (btnSortable.classList.contains('btnSortableMoveDown')) { + const next = li.nextSibling; + if (next) { + li.parentNode.removeChild(li); + next.parentNode.insertBefore(li, next.nextSibling); } - const availableOptions = getTypeOptions(currentAvailableOptions || {}, type); - const imageOptionsEditor = new ImageOptionsEditor(); - imageOptionsEditor.show(type, typeOptions, availableOptions); - }); - } - - function onImageFetchersContainerClick(e) { - const btnImageOptionsForType = dom.parentWithClass(e.target, 'btnImageOptionsForType'); - if (btnImageOptionsForType) { - showImageOptionsForType(dom.parentWithClass(btnImageOptionsForType, 'imageFetcher').getAttribute('data-type')); - return; - } - onSortableContainerClick.call(this, e); - } - - function onSortableContainerClick(e) { - const btnSortable = dom.parentWithClass(e.target, 'btnSortable'); - if (btnSortable) { - const li = dom.parentWithClass(btnSortable, 'sortableOption'); - const list = dom.parentWithClass(li, 'paperList'); - if (btnSortable.classList.contains('btnSortableMoveDown')) { - const next = li.nextSibling; - if (next) { - li.parentNode.removeChild(li); - next.parentNode.insertBefore(li, next.nextSibling); - } - } else { - const prev = li.previousSibling; - if (prev) { - li.parentNode.removeChild(li); - prev.parentNode.insertBefore(li, prev); - } - } - Array.prototype.forEach.call(list.querySelectorAll('.sortableOption'), adjustSortableListElement); - } - } - - function bindEvents(parent) { - parent.querySelector('.metadataReaders').addEventListener('click', onSortableContainerClick); - parent.querySelector('.subtitleFetchers').addEventListener('click', onSortableContainerClick); - parent.querySelector('.metadataFetchers').addEventListener('click', onSortableContainerClick); - parent.querySelector('.imageFetchers').addEventListener('click', onImageFetchersContainerClick); - - parent.querySelector('#chkEnableEmbeddedTitles').addEventListener('change', (e) => { - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !e.currentTarget.checked); - }); - } - - export async function embed(parent, contentType, libraryOptions) { - currentLibraryOptions = { - TypeOptions: [] - }; - currentAvailableOptions = null; - const isNewLibrary = libraryOptions === null; - isNewLibrary && parent.classList.add('newlibrary'); - - parent.innerHTML = globalize.translateHtml(template); - populateRefreshInterval(parent.querySelector('#selectAutoRefreshInterval')); - const promises = [populateLanguages(parent), populateCountries(parent.querySelector('#selectCountry'))]; - Promise.all(promises).then(function() { - return setContentType(parent, contentType).then(function() { - libraryOptions && setLibraryOptions(parent, libraryOptions); - bindEvents(parent); - }); - }); - } - - export function setContentType(parent, contentType) { - if (contentType === 'homevideos' || contentType === 'photos') { - parent.querySelector('.chkEnablePhotosContainer').classList.remove('hide'); } else { - parent.querySelector('.chkEnablePhotosContainer').classList.add('hide'); - } - - if (contentType !== 'tvshows' && contentType !== 'movies' && contentType !== 'homevideos' && contentType !== 'musicvideos' && contentType !== 'mixed') { - parent.querySelector('.chapterSettingsSection').classList.add('hide'); - } else { - parent.querySelector('.chapterSettingsSection').classList.remove('hide'); - } - - if (contentType === 'tvshows') { - parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.remove('hide'); - parent.querySelector('.fldSeasonZeroDisplayName').classList.remove('hide'); - parent.querySelector('#txtSeasonZeroName').setAttribute('required', 'required'); - } else { - parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.add('hide'); - parent.querySelector('.fldSeasonZeroDisplayName').classList.add('hide'); - parent.querySelector('#txtSeasonZeroName').removeAttribute('required'); - } - - if (contentType === 'books' || contentType === 'boxsets' || contentType === 'playlists' || contentType === 'music') { - parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.add('hide'); - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.add('hide'); - } else { - parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.remove('hide'); - if (parent.querySelector('#chkEnableEmbeddedTitles').checked) { - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.remove('hide'); + const prev = li.previousSibling; + if (prev) { + li.parentNode.removeChild(li); + prev.parentNode.insertBefore(li, prev); } } - - if (contentType === 'tvshows') { - parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); - } else { - parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide'); - } - - if (contentType === 'tvshows' || contentType === 'movies' || contentType === 'musicvideos' || contentType === 'mixed') { - parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.remove('hide'); - } else { - parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide'); - } - - parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed'); - - return populateMetadataSettings(parent, contentType); + Array.prototype.forEach.call(list.querySelectorAll('.sortableOption'), adjustSortableListElement); } +} - function setSubtitleFetchersIntoOptions(parent, options) { - options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleFetcher'), elem => { - return !elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); +function bindEvents(parent) { + parent.querySelector('.metadataReaders').addEventListener('click', onSortableContainerClick); + parent.querySelector('.subtitleFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.metadataFetchers').addEventListener('click', onSortableContainerClick); + parent.querySelector('.imageFetchers').addEventListener('click', onImageFetchersContainerClick); + + parent.querySelector('#chkEnableEmbeddedTitles').addEventListener('change', (e) => { + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !e.currentTarget.checked); + }); +} + +export async function embed(parent, contentType, libraryOptions) { + currentLibraryOptions = { + TypeOptions: [] + }; + currentAvailableOptions = null; + const isNewLibrary = libraryOptions === null; + isNewLibrary && parent.classList.add('newlibrary'); + + parent.innerHTML = globalize.translateHtml(template); + populateRefreshInterval(parent.querySelector('#selectAutoRefreshInterval')); + const promises = [populateLanguages(parent), populateCountries(parent.querySelector('#selectCountry'))]; + Promise.all(promises).then(function() { + return setContentType(parent, contentType).then(function() { + libraryOptions && setLibraryOptions(parent, libraryOptions); + bindEvents(parent); }); + }); +} - options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll('.subtitleFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); +export function setContentType(parent, contentType) { + if (contentType === 'homevideos' || contentType === 'photos') { + parent.querySelector('.chkEnablePhotosContainer').classList.remove('hide'); + } else { + parent.querySelector('.chkEnablePhotosContainer').classList.add('hide'); } - function setMetadataFetchersIntoOptions(parent, options) { - const sections = parent.querySelectorAll('.metadataFetcher'); - for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - const type = section.getAttribute('data-type'); - let typeOptions = getTypeOptions(options, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - options.TypeOptions.push(typeOptions); - } - typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkMetadataFetcher'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }); + if (contentType !== 'tvshows' && contentType !== 'movies' && contentType !== 'homevideos' && contentType !== 'musicvideos' && contentType !== 'mixed') { + parent.querySelector('.chapterSettingsSection').classList.add('hide'); + } else { + parent.querySelector('.chapterSettingsSection').classList.remove('hide'); + } - typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.metadataFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); + if (contentType === 'tvshows') { + parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.remove('hide'); + parent.querySelector('.fldSeasonZeroDisplayName').classList.remove('hide'); + parent.querySelector('#txtSeasonZeroName').setAttribute('required', 'required'); + } else { + parent.querySelector('.chkAutomaticallyGroupSeriesContainer').classList.add('hide'); + parent.querySelector('.fldSeasonZeroDisplayName').classList.add('hide'); + parent.querySelector('#txtSeasonZeroName').removeAttribute('required'); + } + + if (contentType === 'books' || contentType === 'boxsets' || contentType === 'playlists' || contentType === 'music') { + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.add('hide'); + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.add('hide'); + } else { + parent.querySelector('.chkEnableEmbeddedTitlesContainer').classList.remove('hide'); + if (parent.querySelector('#chkEnableEmbeddedTitles').checked) { + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.remove('hide'); } } - function setImageFetchersIntoOptions(parent, options) { - const sections = parent.querySelectorAll('.imageFetcher'); - for (let i = 0; i < sections.length; i++) { - const section = sections[i]; - const type = section.getAttribute('data-type'); - let typeOptions = getTypeOptions(options, type); - if (!typeOptions) { - typeOptions = { - Type: type - }; - options.TypeOptions.push(typeOptions); - } - - typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkImageFetcher'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }); - - typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.imageFetcherItem'), elem => { - return elem.getAttribute('data-pluginname'); - }); - } + if (contentType === 'tvshows') { + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.remove('hide'); + } else { + parent.querySelector('.chkEnableEmbeddedEpisodeInfosContainer').classList.add('hide'); } - function setImageOptionsIntoOptions(options) { - const originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || []; - for (let i = 0; i < originalTypeOptions.length; i++) { - const originalTypeOption = originalTypeOptions[i]; - let typeOptions = getTypeOptions(options, originalTypeOption.Type); - - if (!typeOptions) { - typeOptions = { - Type: originalTypeOption.Type - }; - options.TypeOptions.push(typeOptions); - } - originalTypeOption.ImageOptions && (typeOptions.ImageOptions = originalTypeOption.ImageOptions); - } + if (contentType === 'tvshows' || contentType === 'movies' || contentType === 'musicvideos' || contentType === 'mixed') { + parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.remove('hide'); + } else { + parent.querySelector('.fldAllowEmbeddedSubtitlesContainer').classList.add('hide'); } - export function getLibraryOptions(parent) { - const options = { - EnableArchiveMediaFiles: false, - EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, - EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, - ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, - EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, - EnableInternetProviders: true, - SaveLocalMetadata: parent.querySelector('#chkSaveLocal').checked, - EnableAutomaticSeriesGrouping: parent.querySelector('.chkAutomaticallyGroupSeries').checked, - PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value, - MetadataCountryCode: parent.querySelector('#selectCountry').value, - SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value, - AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10), - EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, - EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked, - EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, - AllowEmbeddedSubtitles: parent.querySelector('#selectAllowEmbeddedSubtitles').value, - SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked, - SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked, - SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked, - RequirePerfectSubtitleMatch: parent.querySelector('#chkRequirePerfectMatch').checked, - AutomaticallyAddToCollection: parent.querySelector('#chkAutomaticallyAddToCollection').checked, - MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { - return elem.checked; - }), elem => { - return elem.getAttribute('data-pluginname'); - }), - TypeOptions: [] - }; + parent.querySelector('.chkAutomaticallyAddToCollectionContainer').classList.toggle('hide', contentType !== 'movies' && contentType !== 'mixed'); - options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll('.localReaderOption'), elem => { - return elem.getAttribute('data-pluginname'); - }); - options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + return populateMetadataSettings(parent, contentType); +} + +function setSubtitleFetchersIntoOptions(parent, options) { + options.DisabledSubtitleFetchers = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleFetcher'), elem => { + return !elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }); + + options.SubtitleFetcherOrder = Array.prototype.map.call(parent.querySelectorAll('.subtitleFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); + }); +} + +function setMetadataFetchersIntoOptions(parent, options) { + const sections = parent.querySelectorAll('.metadataFetcher'); + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const type = section.getAttribute('data-type'); + let typeOptions = getTypeOptions(options, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + options.TypeOptions.push(typeOptions); + } + typeOptions.MetadataFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkMetadataFetcher'), elem => { return elem.checked; }), elem => { - return elem.getAttribute('data-lang'); + return elem.getAttribute('data-pluginname'); }); - setSubtitleFetchersIntoOptions(parent, options); - setMetadataFetchersIntoOptions(parent, options); - setImageFetchersIntoOptions(parent, options); - setImageOptionsIntoOptions(options); - return options; + typeOptions.MetadataFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.metadataFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); + }); } +} - function getOrderedPlugins(plugins, configuredOrder) { - plugins = plugins.slice(0); - plugins.sort((a, b) => { - a = configuredOrder.indexOf(a.Name); - b = configuredOrder.indexOf(b.Name); - return a - b; +function setImageFetchersIntoOptions(parent, options) { + const sections = parent.querySelectorAll('.imageFetcher'); + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const type = section.getAttribute('data-type'); + let typeOptions = getTypeOptions(options, type); + if (!typeOptions) { + typeOptions = { + Type: type + }; + options.TypeOptions.push(typeOptions); + } + + typeOptions.ImageFetchers = Array.prototype.map.call(Array.prototype.filter.call(section.querySelectorAll('.chkImageFetcher'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }); + + typeOptions.ImageFetcherOrder = Array.prototype.map.call(section.querySelectorAll('.imageFetcherItem'), elem => { + return elem.getAttribute('data-pluginname'); }); - return plugins; } +} - export function setLibraryOptions(parent, options) { - currentLibraryOptions = options; - currentAvailableOptions = parent.availableOptions; - parent.querySelector('#selectLanguage').value = options.PreferredMetadataLanguage || ''; - parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; - parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; - parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; - parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; - parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; - parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; - parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; - parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; - parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping; - parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles; - parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !options.EnableEmbeddedTitles); - parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked = options.EnableEmbeddedExtrasTitles; - parent.querySelector('#chkEnableEmbeddedEpisodeInfos').value = options.EnableEmbeddedEpisodeInfos; - parent.querySelector('#selectAllowEmbeddedSubtitles').value = options.AllowEmbeddedSubtitles; - parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; - parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia; - parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches; - parent.querySelector('#chkRequirePerfectMatch').checked = options.RequirePerfectSubtitleMatch; - parent.querySelector('#chkAutomaticallyAddToCollection').checked = options.AutomaticallyAddToCollection; - Array.prototype.forEach.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { - elem.checked = options.MetadataSavers ? options.MetadataSavers.includes(elem.getAttribute('data-pluginname')) : elem.getAttribute('data-defaultenabled') === 'true'; - }); - Array.prototype.forEach.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { - elem.checked = !!options.SubtitleDownloadLanguages && options.SubtitleDownloadLanguages.includes(elem.getAttribute('data-lang')); - }); - renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); - renderMetadataFetchers(parent, parent.availableOptions, options); - renderImageFetchers(parent, parent.availableOptions, options); - renderSubtitleFetchers(parent, parent.availableOptions, options); +function setImageOptionsIntoOptions(options) { + const originalTypeOptions = (currentLibraryOptions || {}).TypeOptions || []; + for (let i = 0; i < originalTypeOptions.length; i++) { + const originalTypeOption = originalTypeOptions[i]; + let typeOptions = getTypeOptions(options, originalTypeOption.Type); + + if (!typeOptions) { + typeOptions = { + Type: originalTypeOption.Type + }; + options.TypeOptions.push(typeOptions); + } + originalTypeOption.ImageOptions && (typeOptions.ImageOptions = originalTypeOption.ImageOptions); } +} - let currentLibraryOptions; - let currentAvailableOptions; +export function getLibraryOptions(parent) { + const options = { + EnableArchiveMediaFiles: false, + EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, + EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, + ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, + EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, + EnableInternetProviders: true, + SaveLocalMetadata: parent.querySelector('#chkSaveLocal').checked, + EnableAutomaticSeriesGrouping: parent.querySelector('.chkAutomaticallyGroupSeries').checked, + PreferredMetadataLanguage: parent.querySelector('#selectLanguage').value, + MetadataCountryCode: parent.querySelector('#selectCountry').value, + SeasonZeroDisplayName: parent.querySelector('#txtSeasonZeroName').value, + AutomaticRefreshIntervalDays: parseInt(parent.querySelector('#selectAutoRefreshInterval').value, 10), + EnableEmbeddedTitles: parent.querySelector('#chkEnableEmbeddedTitles').checked, + EnableEmbeddedExtrasTitles: parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked, + EnableEmbeddedEpisodeInfos: parent.querySelector('#chkEnableEmbeddedEpisodeInfos').checked, + AllowEmbeddedSubtitles: parent.querySelector('#selectAllowEmbeddedSubtitles').value, + SkipSubtitlesIfEmbeddedSubtitlesPresent: parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked, + SkipSubtitlesIfAudioTrackMatches: parent.querySelector('#chkSkipIfAudioTrackPresent').checked, + SaveSubtitlesWithMedia: parent.querySelector('#chkSaveSubtitlesLocally').checked, + RequirePerfectSubtitleMatch: parent.querySelector('#chkRequirePerfectMatch').checked, + AutomaticallyAddToCollection: parent.querySelector('#chkAutomaticallyAddToCollection').checked, + MetadataSavers: Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-pluginname'); + }), + TypeOptions: [] + }; + + options.LocalMetadataReaderOrder = Array.prototype.map.call(parent.querySelectorAll('.localReaderOption'), elem => { + return elem.getAttribute('data-pluginname'); + }); + options.SubtitleDownloadLanguages = Array.prototype.map.call(Array.prototype.filter.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + return elem.checked; + }), elem => { + return elem.getAttribute('data-lang'); + }); + setSubtitleFetchersIntoOptions(parent, options); + setMetadataFetchersIntoOptions(parent, options); + setImageFetchersIntoOptions(parent, options); + setImageOptionsIntoOptions(options); + + return options; +} + +function getOrderedPlugins(plugins, configuredOrder) { + plugins = plugins.slice(0); + plugins.sort((a, b) => { + a = configuredOrder.indexOf(a.Name); + b = configuredOrder.indexOf(b.Name); + return a - b; + }); + return plugins; +} + +export function setLibraryOptions(parent, options) { + currentLibraryOptions = options; + currentAvailableOptions = parent.availableOptions; + parent.querySelector('#selectLanguage').value = options.PreferredMetadataLanguage || ''; + parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; + parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; + parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; + parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; + parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; + parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; + parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; + parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; + parent.querySelector('.chkAutomaticallyGroupSeries').checked = options.EnableAutomaticSeriesGrouping; + parent.querySelector('#chkEnableEmbeddedTitles').checked = options.EnableEmbeddedTitles; + parent.querySelector('.chkEnableEmbeddedExtrasTitlesContainer').classList.toggle('hide', !options.EnableEmbeddedTitles); + parent.querySelector('#chkEnableEmbeddedExtrasTitles').checked = options.EnableEmbeddedExtrasTitles; + parent.querySelector('#chkEnableEmbeddedEpisodeInfos').value = options.EnableEmbeddedEpisodeInfos; + parent.querySelector('#selectAllowEmbeddedSubtitles').value = options.AllowEmbeddedSubtitles; + parent.querySelector('#chkSkipIfGraphicalSubsPresent').checked = options.SkipSubtitlesIfEmbeddedSubtitlesPresent; + parent.querySelector('#chkSaveSubtitlesLocally').checked = options.SaveSubtitlesWithMedia; + parent.querySelector('#chkSkipIfAudioTrackPresent').checked = options.SkipSubtitlesIfAudioTrackMatches; + parent.querySelector('#chkRequirePerfectMatch').checked = options.RequirePerfectSubtitleMatch; + parent.querySelector('#chkAutomaticallyAddToCollection').checked = options.AutomaticallyAddToCollection; + Array.prototype.forEach.call(parent.querySelectorAll('.chkMetadataSaver'), elem => { + elem.checked = options.MetadataSavers ? options.MetadataSavers.includes(elem.getAttribute('data-pluginname')) : elem.getAttribute('data-defaultenabled') === 'true'; + }); + Array.prototype.forEach.call(parent.querySelectorAll('.chkSubtitleLanguage'), elem => { + elem.checked = !!options.SubtitleDownloadLanguages && options.SubtitleDownloadLanguages.includes(elem.getAttribute('data-lang')); + }); + renderMetadataReaders(parent, getOrderedPlugins(parent.availableOptions.MetadataReaders, options.LocalMetadataReaderOrder || [])); + renderMetadataFetchers(parent, parent.availableOptions, options); + renderImageFetchers(parent, parent.availableOptions, options); + renderSubtitleFetchers(parent, parent.availableOptions, options); +} + +let currentLibraryOptions; +let currentAvailableOptions; -/* eslint-enable indent */ export default { embed: embed, setContentType: setContentType, diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index b9f156e9a2..a6a588dc66 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for display list view. @@ -18,484 +17,483 @@ import '../../elements/emby-ratingbutton/emby-ratingbutton'; import '../../elements/emby-playstatebutton/emby-playstatebutton'; import ServerConnections from '../ServerConnections'; - function getIndex(item, options) { - if (options.index === 'disc') { - return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); - } - - const sortBy = (options.sortBy || '').toLowerCase(); - let code; - let name; - - if (sortBy.indexOf('sortname') === 0) { - if (item.Type === 'Episode') { - return ''; - } - - // SortName - name = (item.SortName || item.Name || '?')[0].toUpperCase(); - - code = name.charCodeAt(0); - if (code < 65 || code > 90) { - return '#'; - } - - return name.toUpperCase(); - } - if (sortBy.indexOf('officialrating') === 0) { - return item.OfficialRating || globalize.translate('Unrated'); - } - if (sortBy.indexOf('communityrating') === 0) { - if (item.CommunityRating == null) { - return globalize.translate('Unrated'); - } - - return Math.floor(item.CommunityRating); - } - if (sortBy.indexOf('criticrating') === 0) { - if (item.CriticRating == null) { - return globalize.translate('Unrated'); - } - - return Math.floor(item.CriticRating); - } - if (sortBy.indexOf('albumartist') === 0) { - // SortName - if (!item.AlbumArtist) { - return ''; - } - - name = item.AlbumArtist[0].toUpperCase(); - - code = name.charCodeAt(0); - if (code < 65 || code > 90) { - return '#'; - } - - return name.toUpperCase(); - } - return ''; +function getIndex(item, options) { + if (options.index === 'disc') { + return item.ParentIndexNumber == null ? '' : globalize.translate('ValueDiscNumber', item.ParentIndexNumber); } - function getImageUrl(item, size) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - let itemId; + const sortBy = (options.sortBy || '').toLowerCase(); + let code; + let name; - const options = { - fillWidth: size, - fillHeight: size, - type: 'Primary' - }; - - if (item.ImageTags && item.ImageTags.Primary) { - options.tag = item.ImageTags.Primary; - itemId = item.Id; - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - itemId = item.AlbumId; - } else if (item.SeriesId && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; - itemId = item.SeriesId; - } else if (item.ParentPrimaryImageTag) { - options.tag = item.ParentPrimaryImageTag; - itemId = item.ParentPrimaryImageItemId; + if (sortBy.indexOf('sortname') === 0) { + if (item.Type === 'Episode') { + return ''; } - if (itemId) { - return apiClient.getScaledImageUrl(itemId, options); + // SortName + name = (item.SortName || item.Name || '?')[0].toUpperCase(); + + code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; } - return null; + + return name.toUpperCase(); + } + if (sortBy.indexOf('officialrating') === 0) { + return item.OfficialRating || globalize.translate('Unrated'); + } + if (sortBy.indexOf('communityrating') === 0) { + if (item.CommunityRating == null) { + return globalize.translate('Unrated'); + } + + return Math.floor(item.CommunityRating); + } + if (sortBy.indexOf('criticrating') === 0) { + if (item.CriticRating == null) { + return globalize.translate('Unrated'); + } + + return Math.floor(item.CriticRating); + } + if (sortBy.indexOf('albumartist') === 0) { + // SortName + if (!item.AlbumArtist) { + return ''; + } + + name = item.AlbumArtist[0].toUpperCase(); + + code = name.charCodeAt(0); + if (code < 65 || code > 90) { + return '#'; + } + + return name.toUpperCase(); + } + return ''; +} + +function getImageUrl(item, size) { + const apiClient = ServerConnections.getApiClient(item.ServerId); + let itemId; + + const options = { + fillWidth: size, + fillHeight: size, + type: 'Primary' + }; + + if (item.ImageTags && item.ImageTags.Primary) { + options.tag = item.ImageTags.Primary; + itemId = item.Id; + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + itemId = item.AlbumId; + } else if (item.SeriesId && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + itemId = item.SeriesId; + } else if (item.ParentPrimaryImageTag) { + options.tag = item.ParentPrimaryImageTag; + itemId = item.ParentPrimaryImageItemId; } - function getChannelImageUrl(item, size) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - const options = { - fillWidth: size, - fillHeight: size, - type: 'Primary' - }; + if (itemId) { + return apiClient.getScaledImageUrl(itemId, options); + } + return null; +} - if (item.ChannelId && item.ChannelPrimaryImageTag) { - options.tag = item.ChannelPrimaryImageTag; - } +function getChannelImageUrl(item, size) { + const apiClient = ServerConnections.getApiClient(item.ServerId); + const options = { + fillWidth: size, + fillHeight: size, + type: 'Primary' + }; - if (item.ChannelId) { - return apiClient.getScaledImageUrl(item.ChannelId, options); - } + if (item.ChannelId && item.ChannelPrimaryImageTag) { + options.tag = item.ChannelPrimaryImageTag; } - function getTextLinesHtml(textlines, isLargeStyle) { - let html = ''; + if (item.ChannelId) { + return apiClient.getScaledImageUrl(item.ChannelId, options); + } +} - const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; +function getTextLinesHtml(textlines, isLargeStyle) { + let html = ''; - for (const [i, text] of textlines.entries()) { - if (!text) { - continue; - } + const largeTitleTagName = layoutManager.tv ? 'h2' : 'div'; - let elem; + for (const [i, text] of textlines.entries()) { + if (!text) { + continue; + } - if (i === 0) { - if (isLargeStyle) { - elem = document.createElement(largeTitleTagName); - } else { - elem = document.createElement('div'); - } + let elem; + + if (i === 0) { + if (isLargeStyle) { + elem = document.createElement(largeTitleTagName); } else { elem = document.createElement('div'); - elem.classList.add('secondary'); } - - elem.classList.add('listItemBodyText'); - - elem.innerHTML = '' + escapeHtml(text) + ''; - - html += elem.outerHTML; + } else { + elem = document.createElement('div'); + elem.classList.add('secondary'); } - return html; + elem.classList.add('listItemBodyText'); + + elem.innerHTML = '' + escapeHtml(text) + ''; + + html += elem.outerHTML; } - function getRightButtonsHtml(options) { + return html; +} + +function getRightButtonsHtml(options) { + let html = ''; + + for (let i = 0, length = options.rightButtons.length; i < length; i++) { + const button = options.rightButtons[i]; + + html += ``; + } + + return html; +} + +export function getListViewHtml(options) { + const items = options.items; + + let groupTitle = ''; + const action = options.action || 'link'; + + const isLargeStyle = options.imageSize === 'large'; + const enableOverview = options.enableOverview; + + const clickEntireItem = layoutManager.tv ? true : false; + const outerTagName = clickEntireItem ? 'button' : 'div'; + const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; + + let outerHtml = ''; + + const enableContentWrapper = options.enableOverview && !layoutManager.tv; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let html = ''; - for (let i = 0, length = options.rightButtons.length; i < length; i++) { - const button = options.rightButtons[i]; + if (options.showIndex) { + const itemGroupTitle = getIndex(item, options); - html += ``; + if (itemGroupTitle !== groupTitle) { + if (html) { + html += '
'; + } + + if (i === 0) { + html += '

'; + } else { + html += '

'; + } + html += escapeHtml(itemGroupTitle); + html += '

'; + + html += '
'; + + groupTitle = itemGroupTitle; + } } - return html; - } + let cssClass = 'listItem'; - export function getListViewHtml(options) { - const items = options.items; + if (options.border || (options.highlight !== false && !layoutManager.tv)) { + cssClass += ' listItem-border'; + } - let groupTitle = ''; - const action = options.action || 'link'; + if (clickEntireItem) { + cssClass += ' itemAction listItem-button'; + } - const isLargeStyle = options.imageSize === 'large'; - const enableOverview = options.enableOverview; + if (layoutManager.tv) { + cssClass += ' listItem-focusscale'; + } - const clickEntireItem = layoutManager.tv ? true : false; - const outerTagName = clickEntireItem ? 'button' : 'div'; - const enableSideMediaInfo = options.enableSideMediaInfo != null ? options.enableSideMediaInfo : true; + let downloadWidth = 80; - let outerHtml = ''; + if (isLargeStyle) { + cssClass += ' listItem-largeImage'; + downloadWidth = 500; + } - const enableContentWrapper = options.enableOverview && !layoutManager.tv; + const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; + 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}"`) : ''; - let html = ''; + if (enableContentWrapper) { + cssClass += ' listItem-withContentWrapper'; + } - if (options.showIndex) { - const itemGroupTitle = getIndex(item, options); + 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 (itemGroupTitle !== groupTitle) { - if (html) { - html += '
'; - } + if (enableContentWrapper) { + html += '
'; + } - if (i === 0) { - html += '

'; - } else { - html += '

'; - } - html += escapeHtml(itemGroupTitle); - html += '

'; + if (!clickEntireItem && options.dragHandle) { + html += ''; + } - html += '
'; + if (options.image !== false) { + const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth); + let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage'; - groupTitle = itemGroupTitle; - } + if (options.imageSource === 'channel') { + imageClass += ' listItemImage-channel'; } - let cssClass = 'listItem'; - - if (options.border || (options.highlight !== false && !layoutManager.tv)) { - cssClass += ' listItem-border'; + if (isLargeStyle && layoutManager.tv) { + imageClass += ' listItemImage-large-tv'; } - if (clickEntireItem) { - cssClass += ' itemAction listItem-button'; + const playOnImageClick = options.imagePlayButton && !layoutManager.tv; + + if (!clickEntireItem) { + imageClass += ' itemAction'; } - if (layoutManager.tv) { - cssClass += ' listItem-focusscale'; + const imageAction = playOnImageClick ? 'link' : action; + + if (imgUrl) { + html += '
'; + } else { + html += '
' + cardBuilder.getDefaultText(item, options); } - let downloadWidth = 80; - - if (isLargeStyle) { - cssClass += ' listItem-largeImage'; - downloadWidth = 500; + const mediaSourceCount = item.MediaSourceCount || 1; + if (mediaSourceCount > 1 && options.disableIndicators !== true) { + html += '
' + mediaSourceCount + '
'; } - const playlistItemId = item.PlaylistItemId ? (` data-playlistitemid="${item.PlaylistItemId}"`) : ''; + let indicatorsHtml = ''; + indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - 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'; + if (indicatorsHtml) { + html += `
${indicatorsHtml}
`; } - 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) { - html += '
'; + if (playOnImageClick) { + html += ''; } - if (!clickEntireItem && options.dragHandle) { - html += ''; - } - - if (options.image !== false) { - const imgUrl = options.imageSource === 'channel' ? getChannelImageUrl(item, downloadWidth) : getImageUrl(item, downloadWidth); - let imageClass = isLargeStyle ? 'listItemImage listItemImage-large' : 'listItemImage'; - - if (options.imageSource === 'channel') { - imageClass += ' listItemImage-channel'; - } - - if (isLargeStyle && layoutManager.tv) { - imageClass += ' listItemImage-large-tv'; - } - - const playOnImageClick = options.imagePlayButton && !layoutManager.tv; - - if (!clickEntireItem) { - imageClass += ' itemAction'; - } - - const imageAction = playOnImageClick ? 'link' : action; - - if (imgUrl) { - html += '
'; - } else { - html += '
' + cardBuilder.getDefaultText(item, options); - } - - const mediaSourceCount = item.MediaSourceCount || 1; - if (mediaSourceCount > 1 && options.disableIndicators !== true) { - html += '
' + mediaSourceCount + '
'; - } - - let indicatorsHtml = ''; - indicatorsHtml += indicators.getPlayedIndicatorHtml(item); - - if (indicatorsHtml) { - html += `
${indicatorsHtml}
`; - } - - if (playOnImageClick) { - html += ''; - } - - const progressHtml = indicators.getProgressBarHtml(item, { - containerClass: 'listItemProgressBar' - }); - - if (progressHtml) { - html += progressHtml; - } - html += '
'; - } - - if (options.showIndexNumberLeft) { - html += '
'; - html += (item.IndexNumber || ' '); - html += '
'; - } - - const textlines = []; - - if (options.showProgramDateTime) { - textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), { - - weekday: 'long', - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - })); - } - - if (options.showProgramTime) { - textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); - } - - if (options.showChannel && item.ChannelName) { - textlines.push(item.ChannelName); - } - - let parentTitle = null; - - if (options.showParentTitle) { - if (item.Type === 'Episode') { - parentTitle = item.SeriesName; - } else if (item.IsSeries || (item.EpisodeTitle && item.Name)) { - parentTitle = item.Name; - } - } - - let displayName = itemHelper.getDisplayName(item, { - includeParentInfo: options.includeParentInfoInTitle + const progressHtml = indicators.getProgressBarHtml(item, { + containerClass: 'listItemProgressBar' }); - if (options.showIndexNumber && item.IndexNumber != null) { - displayName = `${item.IndexNumber}. ${displayName}`; + if (progressHtml) { + html += progressHtml; + } + html += '
'; + } + + if (options.showIndexNumberLeft) { + html += '
'; + html += (item.IndexNumber || ' '); + html += '
'; + } + + const textlines = []; + + if (options.showProgramDateTime) { + textlines.push(datetime.toLocaleString(datetime.parseISO8601Date(item.StartDate), { + + weekday: 'long', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: '2-digit' + })); + } + + if (options.showProgramTime) { + textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); + } + + if (options.showChannel && item.ChannelName) { + textlines.push(item.ChannelName); + } + + let parentTitle = null; + + if (options.showParentTitle) { + if (item.Type === 'Episode') { + parentTitle = item.SeriesName; + } else if (item.IsSeries || (item.EpisodeTitle && item.Name)) { + parentTitle = item.Name; + } + } + + let displayName = itemHelper.getDisplayName(item, { + includeParentInfo: options.includeParentInfoInTitle + }); + + if (options.showIndexNumber && item.IndexNumber != null) { + displayName = `${item.IndexNumber}. ${displayName}`; + } + + if (options.showParentTitle && options.parentTitleWithTitle) { + if (displayName) { + if (parentTitle) { + parentTitle += ' - '; + } + parentTitle = (parentTitle || '') + displayName; } - if (options.showParentTitle && options.parentTitleWithTitle) { - if (displayName) { - if (parentTitle) { - parentTitle += ' - '; - } - parentTitle = (parentTitle || '') + displayName; + textlines.push(parentTitle || ''); + } else if (options.showParentTitle) { + textlines.push(parentTitle || ''); + } + + if (displayName && !options.parentTitleWithTitle) { + textlines.push(displayName); + } + + if (item.IsFolder) { + if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { + textlines.push(item.AlbumArtist); + } + } else { + if (options.artist) { + const artistItems = item.ArtistItems; + if (artistItems && item.Type !== 'MusicAlbum') { + textlines.push(artistItems.map(a => { + return a.Name; + }).join(', ')); + } + } + } + + if (item.Type === 'TvChannel' && item.CurrentProgram) { + textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); + } + + cssClass = 'listItemBody'; + if (!clickEntireItem) { + cssClass += ' itemAction'; + } + + if (options.image === false) { + cssClass += ' listItemBody-noleftpadding'; + } + + html += `
`; + + html += getTextLinesHtml(textlines, isLargeStyle); + + if (options.mediaInfo !== false && !enableSideMediaInfo) { + const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; + + html += `
`; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { + episodeTitle: false, + originalAirDate: false, + subtitles: false + + }); + html += '
'; + } + + if (enableOverview && item.Overview) { + html += '
'; + html += '' + item.Overview + ''; + html += '
'; + } + + html += '
'; + + if (options.mediaInfo !== false && enableSideMediaInfo) { + html += '
'; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { + + year: false, + container: false, + episodeTitle: false, + criticRating: false, + officialRating: false, + endsAt: false + + }); + html += '
'; + } + + if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) { + html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside'); + } + + html += '
'; + + if (!clickEntireItem) { + if (options.addToListButton) { + html += ''; + } + + if (options.infoButton) { + html += ''; + } + + if (options.rightButtons) { + html += getRightButtonsHtml(options); + } + + if (options.enableUserDataButtons !== false) { + const userData = item.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + + if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) { + html += ''; } - textlines.push(parentTitle || ''); - } else if (options.showParentTitle) { - textlines.push(parentTitle || ''); - } - - if (displayName && !options.parentTitleWithTitle) { - textlines.push(displayName); - } - - if (item.IsFolder) { - if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { - textlines.push(item.AlbumArtist); - } - } else { - if (options.artist) { - const artistItems = item.ArtistItems; - if (artistItems && item.Type !== 'MusicAlbum') { - textlines.push(artistItems.map(a => { - return a.Name; - }).join(', ')); - } + if (itemHelper.canRate(item) && options.enableRatingButton !== false) { + html += ''; } } - if (item.Type === 'TvChannel' && item.CurrentProgram) { - textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); + if (options.moreButton !== false) { + html += ''; } + } + html += '
'; - cssClass = 'listItemBody'; - if (!clickEntireItem) { - cssClass += ' itemAction'; - } - - if (options.image === false) { - cssClass += ' listItemBody-noleftpadding'; - } - - html += `
`; - - html += getTextLinesHtml(textlines, isLargeStyle); - - if (options.mediaInfo !== false && !enableSideMediaInfo) { - const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; - - html += `
`; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { - episodeTitle: false, - originalAirDate: false, - subtitles: false - - }); - html += '
'; - } + if (enableContentWrapper) { + html += '
'; if (enableOverview && item.Overview) { - html += '
'; + html += '
'; html += '' + item.Overview + ''; html += '
'; } - - html += '
'; - - if (options.mediaInfo !== false && enableSideMediaInfo) { - html += '
'; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { - - year: false, - container: false, - episodeTitle: false, - criticRating: false, - officialRating: false, - endsAt: false - - }); - html += '
'; - } - - if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) { - html += indicators.getTimerIndicator(item).replace('indicatorIcon', 'indicatorIcon listItemAside'); - } - - html += '
'; - - if (!clickEntireItem) { - if (options.addToListButton) { - html += ''; - } - - if (options.infoButton) { - html += ''; - } - - if (options.rightButtons) { - html += getRightButtonsHtml(options); - } - - if (options.enableUserDataButtons !== false) { - const userData = item.UserData || {}; - const likes = userData.Likes == null ? '' : userData.Likes; - - if (itemHelper.canMarkPlayed(item) && options.enablePlayedButton !== false) { - html += ''; - } - - if (itemHelper.canRate(item) && options.enableRatingButton !== false) { - html += ''; - } - } - - if (options.moreButton !== false) { - html += ''; - } - } - html += '
'; - - if (enableContentWrapper) { - html += '
'; - - if (enableOverview && item.Overview) { - html += '
'; - html += '' + item.Overview + ''; - html += '
'; - } - } - - html += ``; - - outerHtml += html; } - return outerHtml; + html += ``; + + outerHtml += html; } -/* eslint-enable indent */ + return outerHtml; +} + export default { getListViewHtml: getListViewHtml }; diff --git a/src/components/maintabsmanager.js b/src/components/maintabsmanager.js index 76d0090ba8..57cb31f759 100644 --- a/src/components/maintabsmanager.js +++ b/src/components/maintabsmanager.js @@ -4,200 +4,196 @@ import Events from '../utils/events.ts'; import '../elements/emby-tabs/emby-tabs'; import '../elements/emby-button/emby-button'; -/* eslint-disable indent */ +let tabOwnerView; +const queryScope = document.querySelector('.skinHeader'); +let headerTabsContainer; +let tabsElem; - let tabOwnerView; - const queryScope = document.querySelector('.skinHeader'); - let headerTabsContainer; - let tabsElem; - - function ensureElements() { - if (!headerTabsContainer) { - headerTabsContainer = queryScope.querySelector('.headerTabs'); - } +function ensureElements() { + if (!headerTabsContainer) { + headerTabsContainer = queryScope.querySelector('.headerTabs'); } +} - function onViewTabsReady() { - this.selectedIndex(this.readySelectedIndex); - this.readySelectedIndex = null; - } +function onViewTabsReady() { + this.selectedIndex(this.readySelectedIndex); + this.readySelectedIndex = null; +} - function allowSwipe(target) { - function allowSwipeOn(elem) { - if (dom.parentWithTag(elem, 'input')) { - return false; - } - - const classList = elem.classList; - if (classList) { - return !classList.contains('scrollX') && !classList.contains('animatedScrollX'); - } - - return true; +function allowSwipe(target) { + function allowSwipeOn(elem) { + if (dom.parentWithTag(elem, 'input')) { + return false; } - let parent = target; - while (parent != null) { - if (!allowSwipeOn(parent)) { - return false; - } - parent = parent.parentNode; + const classList = elem.classList; + if (classList) { + return !classList.contains('scrollX') && !classList.contains('animatedScrollX'); } return true; } - function configureSwipeTabs(view, currentElement) { - if (!browser.touch) { - return; + let parent = target; + while (parent != null) { + if (!allowSwipeOn(parent)) { + return false; } - - // implement without hammer - const onSwipeLeft = function (e, target) { - if (allowSwipe(target) && view.contains(target)) { - currentElement.selectNext(); - } - }; - - const onSwipeRight = function (e, target) { - if (allowSwipe(target) && view.contains(target)) { - currentElement.selectPrevious(); - } - }; - - import('../scripts/touchHelper').then(({ default: TouchHelper }) => { - const touchHelper = new TouchHelper(view.parentNode.parentNode); - - Events.on(touchHelper, 'swipeleft', onSwipeLeft); - Events.on(touchHelper, 'swiperight', onSwipeRight); - - view.addEventListener('viewdestroy', function () { - touchHelper.destroy(); - }); - }); + parent = parent.parentNode; } - export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { - ensureElements(); + return true; +} - if (!view) { - if (tabOwnerView) { - document.body.classList.remove('withSectionTabs'); +function configureSwipeTabs(view, currentElement) { + if (!browser.touch) { + return; + } - headerTabsContainer.innerHTML = ''; - headerTabsContainer.classList.add('hide'); - - tabOwnerView = null; - } - return { - tabsContainer: headerTabsContainer, - replaced: false - }; + // implement without hammer + const onSwipeLeft = function (e, target) { + if (allowSwipe(target) && view.contains(target)) { + currentElement.selectNext(); } + }; - const tabsContainerElem = headerTabsContainer; - - if (!tabOwnerView) { - tabsContainerElem.classList.remove('hide'); + const onSwipeRight = function (e, target) { + if (allowSwipe(target) && view.contains(target)) { + currentElement.selectPrevious(); } + }; - if (tabOwnerView !== view) { - let index = 0; + import('../scripts/touchHelper').then(({ default: TouchHelper }) => { + const touchHelper = new TouchHelper(view.parentNode.parentNode); - const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); - const tabsHtml = '
' + getTabsFn().map(function (t) { - let tabClass = 'emby-tab-button'; + Events.on(touchHelper, 'swipeleft', onSwipeLeft); + Events.on(touchHelper, 'swiperight', onSwipeRight); - if (t.enabled === false) { - tabClass += ' hide'; - } + view.addEventListener('viewdestroy', function () { + touchHelper.destroy(); + }); + }); +} - let tabHtml; +export function setTabs(view, selectedIndex, getTabsFn, getTabContainersFn, onBeforeTabChange, onTabChange, setSelectedIndex) { + ensureElements(); - if (t.cssClass) { - tabClass += ' ' + t.cssClass; - } + if (!view) { + if (tabOwnerView) { + document.body.classList.remove('withSectionTabs'); - if (t.href) { - tabHtml = '
' + t.name + '
'; - } else { - tabHtml = ''; - } + headerTabsContainer.innerHTML = ''; + headerTabsContainer.classList.add('hide'); - index++; - return tabHtml; - }).join('') + '
'; - - tabsContainerElem.innerHTML = tabsHtml; - window.CustomElements.upgradeSubtree(tabsContainerElem); - - document.body.classList.add('withSectionTabs'); - tabOwnerView = view; - - tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]'); - - configureSwipeTabs(view, tabsElem); - - if (getTabContainersFn) { - tabsElem.addEventListener('beforetabchange', function (e) { - const tabContainers = getTabContainersFn(); - if (e.detail.previousIndex != null) { - const previousPanel = tabContainers[e.detail.previousIndex]; - if (previousPanel) { - previousPanel.classList.remove('is-active'); - } - } - - const newPanel = tabContainers[e.detail.selectedTabIndex]; - - if (newPanel) { - newPanel.classList.add('is-active'); - } - }); - } - - if (onBeforeTabChange) { - tabsElem.addEventListener('beforetabchange', onBeforeTabChange); - } - if (onTabChange) { - tabsElem.addEventListener('tabchange', onTabChange); - } - - if (setSelectedIndex !== false) { - if (tabsElem.selectedIndex) { - tabsElem.selectedIndex(selectedIndex); - } else { - tabsElem.readySelectedIndex = selectedIndex; - tabsElem.addEventListener('ready', onViewTabsReady); - } - } - - return { - tabsContainer: tabsContainerElem, - tabs: tabsElem, - replaced: true - }; + tabOwnerView = null; } - - tabsElem.selectedIndex(selectedIndex); - return { - tabsContainer: tabsContainerElem, - tabs: tabsElem, + tabsContainer: headerTabsContainer, replaced: false }; } - export function selectedTabIndex(index) { - if (index != null) { - tabsElem.selectedIndex(index); - } else { - tabsElem.triggerTabChange(); + const tabsContainerElem = headerTabsContainer; + + if (!tabOwnerView) { + tabsContainerElem.classList.remove('hide'); + } + + if (tabOwnerView !== view) { + let index = 0; + + const indexAttribute = selectedIndex == null ? '' : (' data-index="' + selectedIndex + '"'); + const tabsHtml = '
' + getTabsFn().map(function (t) { + let tabClass = 'emby-tab-button'; + + if (t.enabled === false) { + tabClass += ' hide'; + } + + let tabHtml; + + if (t.cssClass) { + tabClass += ' ' + t.cssClass; + } + + if (t.href) { + tabHtml = '
' + t.name + '
'; + } else { + tabHtml = ''; + } + + index++; + return tabHtml; + }).join('') + '
'; + + tabsContainerElem.innerHTML = tabsHtml; + window.CustomElements.upgradeSubtree(tabsContainerElem); + + document.body.classList.add('withSectionTabs'); + tabOwnerView = view; + + tabsElem = tabsContainerElem.querySelector('[is="emby-tabs"]'); + + configureSwipeTabs(view, tabsElem); + + if (getTabContainersFn) { + tabsElem.addEventListener('beforetabchange', function (e) { + const tabContainers = getTabContainersFn(); + if (e.detail.previousIndex != null) { + const previousPanel = tabContainers[e.detail.previousIndex]; + if (previousPanel) { + previousPanel.classList.remove('is-active'); + } + } + + const newPanel = tabContainers[e.detail.selectedTabIndex]; + + if (newPanel) { + newPanel.classList.add('is-active'); + } + }); } + + if (onBeforeTabChange) { + tabsElem.addEventListener('beforetabchange', onBeforeTabChange); + } + if (onTabChange) { + tabsElem.addEventListener('tabchange', onTabChange); + } + + if (setSelectedIndex !== false) { + if (tabsElem.selectedIndex) { + tabsElem.selectedIndex(selectedIndex); + } else { + tabsElem.readySelectedIndex = selectedIndex; + tabsElem.addEventListener('ready', onViewTabsReady); + } + } + + return { + tabsContainer: tabsContainerElem, + tabs: tabsElem, + replaced: true + }; } - export function getTabsElement() { - return document.querySelector('.tabs-viewmenubar'); - } + tabsElem.selectedIndex(selectedIndex); -/* eslint-enable indent */ + return { + tabsContainer: tabsContainerElem, + tabs: tabsElem, + replaced: false + }; +} + +export function selectedTabIndex(index) { + if (index != null) { + tabsElem.selectedIndex(index); + } else { + tabsElem.triggerTabChange(); + } +} + +export function getTabsElement() { + return document.querySelector('.tabs-viewmenubar'); +} diff --git a/src/components/mediaLibraryCreator/mediaLibraryCreator.js b/src/components/mediaLibraryCreator/mediaLibraryCreator.js index 1592b27c2e..8fe961ce5a 100644 --- a/src/components/mediaLibraryCreator/mediaLibraryCreator.js +++ b/src/components/mediaLibraryCreator/mediaLibraryCreator.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for media library creator. @@ -25,167 +24,169 @@ import toast from '../toast/toast'; import alert from '../alert'; import template from './mediaLibraryCreator.template.html'; - function onAddLibrary(e) { - if (isCreating) { - return false; - } +function onAddLibrary(e) { + if (isCreating) { + return false; + } - if (pathInfos.length == 0) { - alert({ - text: globalize.translate('PleaseAddAtLeastOneFolder'), - type: 'error' - }); - - return false; - } - - isCreating = true; - loading.show(); - const dlg = dom.parentWithClass(this, 'dlg-librarycreator'); - const name = $('#txtValue', dlg).val(); - let type = $('#selectCollectionType', dlg).val(); - - if (type == 'mixed') { - type = null; - } - - const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); - libraryOptions.PathInfos = pathInfos; - ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => { - hasChanges = true; - isCreating = false; - loading.hide(); - dialogHelper.close(dlg); - }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - - isCreating = false; - loading.hide(); + if (pathInfos.length == 0) { + alert({ + text: globalize.translate('PleaseAddAtLeastOneFolder'), + type: 'error' }); - e.preventDefault(); + + return false; } - function getCollectionTypeOptionsHtml(collectionTypeOptions) { - return collectionTypeOptions.map(i => { - return ``; - }).join(''); + isCreating = true; + loading.show(); + const dlg = dom.parentWithClass(this, 'dlg-librarycreator'); + const name = $('#txtValue', dlg).val(); + let type = $('#selectCollectionType', dlg).val(); + + if (type == 'mixed') { + type = null; } - function initEditor(page, collectionTypeOptions) { - $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () { - const value = this.value; - const dlg = $(this).parents('.dialog')[0]; - libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value); + const libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); + libraryOptions.PathInfos = pathInfos; + ApiClient.addVirtualFolder(name, type, currentOptions.refresh, libraryOptions).then(() => { + hasChanges = true; + isCreating = false; + loading.hide(); + dialogHelper.close(dlg); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - if (value) { - dlg.querySelector('.libraryOptions').classList.remove('hide'); - } else { - dlg.querySelector('.libraryOptions').classList.add('hide'); - } + isCreating = false; + loading.hide(); + }); + e.preventDefault(); +} - if (value != 'mixed') { - const index = this.selectedIndex; +function getCollectionTypeOptionsHtml(collectionTypeOptions) { + return collectionTypeOptions.map(i => { + return ``; + }).join(''); +} - if (index != -1) { - const name = this.options[index].innerHTML.replace('*', '').replace('&', '&'); - $('#txtValue', dlg).val(name); - } - } +function initEditor(page, collectionTypeOptions) { + $('#selectCollectionType', page).html(getCollectionTypeOptionsHtml(collectionTypeOptions)).val('').on('change', function () { + const value = this.value; + const dlg = $(this).parents('.dialog')[0]; + libraryoptionseditor.setContentType(dlg.querySelector('.libraryOptions'), value); - const folderOption = collectionTypeOptions.find(i => i.value === value); - $('.collectionTypeFieldDescription', dlg).html(folderOption?.message || ''); - }); - page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); - page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary); - page.querySelector('.folderList').addEventListener('click', onRemoveClick); - } - - function onAddButtonClick() { - const page = dom.parentWithClass(this, 'dlg-librarycreator'); - - import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - enableNetworkSharePath: true, - callback: function (path, networkSharePath) { - if (path) { - addMediaLocation(page, path, networkSharePath); - } - - picker.close(); - } - }); - }); - } - - function getFolderHtml(pathInfo, index) { - let html = ''; - html += '
'; - html += `
`; - html += `
${escapeHtml(pathInfo.Path)}
`; - - if (pathInfo.NetworkPath) { - html += `
${escapeHtml(pathInfo.NetworkPath)}
`; - } - - html += '
'; - html += ``; - html += '
'; - return html; - } - - function renderPaths(page) { - const foldersHtml = pathInfos.map(getFolderHtml).join(''); - const folderList = page.querySelector('.folderList'); - folderList.innerHTML = foldersHtml; - - if (foldersHtml) { - folderList.classList.remove('hide'); + if (value) { + dlg.querySelector('.libraryOptions').classList.remove('hide'); } else { - folderList.classList.add('hide'); + dlg.querySelector('.libraryOptions').classList.add('hide'); } - } - function addMediaLocation(page, path, networkSharePath) { - const pathLower = path.toLowerCase(); - const pathFilter = pathInfos.filter(p => { - return p.Path.toLowerCase() == pathLower; - }); + if (value != 'mixed') { + const index = this.selectedIndex; - if (!pathFilter.length) { - const pathInfo = { - Path: path - }; - - if (networkSharePath) { - pathInfo.NetworkPath = networkSharePath; + if (index != -1) { + const name = this.options[index].innerHTML + .replaceAll('*', '') + .replaceAll('&', '&'); + $('#txtValue', dlg).val(name); } - - pathInfos.push(pathInfo); - renderPaths(page); } - } - function onRemoveClick(e) { - const button = dom.parentWithClass(e.target, 'btnRemovePath'); - const index = parseInt(button.getAttribute('data-index'), 10); - const location = pathInfos[index].Path; - const locationLower = location.toLowerCase(); - pathInfos = pathInfos.filter(p => { - return p.Path.toLowerCase() != locationLower; + const folderOption = collectionTypeOptions.find(i => i.value === value); + $('.collectionTypeFieldDescription', dlg).html(folderOption?.message || ''); + }); + page.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + page.querySelector('.addLibraryForm').addEventListener('submit', onAddLibrary); + page.querySelector('.folderList').addEventListener('click', onRemoveClick); +} + +function onAddButtonClick() { + const page = dom.parentWithClass(this, 'dlg-librarycreator'); + + import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + enableNetworkSharePath: true, + callback: function (path, networkSharePath) { + if (path) { + addMediaLocation(page, path, networkSharePath); + } + + picker.close(); + } }); - renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); + }); +} + +function getFolderHtml(pathInfo, index) { + let html = ''; + html += '
'; + html += `
`; + html += `
${escapeHtml(pathInfo.Path)}
`; + + if (pathInfo.NetworkPath) { + html += `
${escapeHtml(pathInfo.NetworkPath)}
`; } - function onDialogClosed() { - currentResolve(hasChanges); - } + html += '
'; + html += ``; + html += '
'; + return html; +} - function initLibraryOptions(dlg) { - libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => { - $('#selectCollectionType', dlg).trigger('change'); - }); +function renderPaths(page) { + const foldersHtml = pathInfos.map(getFolderHtml).join(''); + const folderList = page.querySelector('.folderList'); + folderList.innerHTML = foldersHtml; + + if (foldersHtml) { + folderList.classList.remove('hide'); + } else { + folderList.classList.add('hide'); } +} + +function addMediaLocation(page, path, networkSharePath) { + const pathLower = path.toLowerCase(); + const pathFilter = pathInfos.filter(p => { + return p.Path.toLowerCase() == pathLower; + }); + + if (!pathFilter.length) { + const pathInfo = { + Path: path + }; + + if (networkSharePath) { + pathInfo.NetworkPath = networkSharePath; + } + + pathInfos.push(pathInfo); + renderPaths(page); + } +} + +function onRemoveClick(e) { + const button = dom.parentWithClass(e.target, 'btnRemovePath'); + const index = parseInt(button.getAttribute('data-index'), 10); + const location = pathInfos[index].Path; + const locationLower = location.toLowerCase(); + pathInfos = pathInfos.filter(p => { + return p.Path.toLowerCase() != locationLower; + }); + renderPaths(dom.parentWithClass(button, 'dlg-librarycreator')); +} + +function onDialogClosed() { + currentResolve(hasChanges); +} + +function initLibraryOptions(dlg) { + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions')).then(() => { + $('#selectCollectionType', dlg).trigger('change'); + }); +} export class showEditor { constructor(options) { @@ -217,11 +218,10 @@ export class showEditor { } } - let pathInfos = []; - let currentResolve; - let currentOptions; - let hasChanges = false; - let isCreating = false; +let pathInfos = []; +let currentResolve; +let currentOptions; +let hasChanges = false; +let isCreating = false; -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js index c9f931297d..b1389552a8 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ /** * Module for media library editor. @@ -23,180 +22,180 @@ import toast from '../toast/toast'; import confirm from '../confirm/confirm'; import template from './mediaLibraryEditor.template.html'; - function onEditLibrary() { - if (isCreating) { - return false; - } - - isCreating = true; - loading.show(); - const dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); - let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); - libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions); - ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => { - hasChanges = true; - isCreating = false; - loading.hide(); - dialogHelper.close(dlg); - }, () => { - isCreating = false; - loading.hide(); - }); +function onEditLibrary() { + if (isCreating) { return false; } - function addMediaLocation(page, path, networkSharePath) { - const virtualFolder = currentOptions.library; + isCreating = true; + loading.show(); + const dlg = dom.parentWithClass(this, 'dlg-libraryeditor'); + let libraryOptions = libraryoptionseditor.getLibraryOptions(dlg.querySelector('.libraryOptions')); + libraryOptions = Object.assign(currentOptions.library.LibraryOptions || {}, libraryOptions); + ApiClient.updateVirtualFolderOptions(currentOptions.library.ItemId, libraryOptions).then(() => { + hasChanges = true; + isCreating = false; + loading.hide(); + dialogHelper.close(dlg); + }, () => { + isCreating = false; + loading.hide(); + }); + return false; +} + +function addMediaLocation(page, path, networkSharePath) { + const virtualFolder = currentOptions.library; + const refreshAfterChange = currentOptions.refresh; + ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => { + hasChanges = true; + refreshLibraryFromServer(page); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + }); +} + +function updateMediaLocation(page, path, networkSharePath) { + const virtualFolder = currentOptions.library; + ApiClient.updateMediaPath(virtualFolder.Name, { + Path: path, + NetworkPath: networkSharePath + }).then(() => { + hasChanges = true; + refreshLibraryFromServer(page); + }, () => { + toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + }); +} + +function onRemoveClick(btnRemovePath, location) { + const button = btnRemovePath; + const virtualFolder = currentOptions.library; + + confirm({ + title: globalize.translate('HeaderRemoveMediaLocation'), + text: globalize.translate('MessageConfirmRemoveMediaLocation'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(() => { const refreshAfterChange = currentOptions.refresh; - ApiClient.addMediaPath(virtualFolder.Name, path, networkSharePath, refreshAfterChange).then(() => { + ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => { hasChanges = true; - refreshLibraryFromServer(page); + refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); + toast(globalize.translate('ErrorDefault')); + }); + }); +} + +function onListItemClick(e) { + const listItem = dom.parentWithClass(e.target, 'listItem'); + + if (listItem) { + const index = parseInt(listItem.getAttribute('data-index'), 10); + const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; + const pathInfo = index == null ? {} : pathInfos[index] || {}; + const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); + const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); + + if (btnRemovePath) { + onRemoveClick(btnRemovePath, originalPath); + return; + } + + showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath); + } +} + +function getFolderHtml(pathInfo, index) { + let html = ''; + html += `
`; + html += `
`; + html += '

'; + html += escapeHtml(pathInfo.Path); + html += '

'; + + if (pathInfo.NetworkPath) { + html += `
${escapeHtml(pathInfo.NetworkPath)}
`; + } + + html += '
'; + html += ``; + html += '
'; + return html; +} + +function refreshLibraryFromServer(page) { + ApiClient.getVirtualFolders().then(result => { + const library = result.filter(f => { + return f.Name === currentOptions.library.Name; + })[0]; + + if (library) { + currentOptions.library = library; + renderLibrary(page, currentOptions); + } + }); +} + +function renderLibrary(page, options) { + let pathInfos = (options.library.LibraryOptions || {}).PathInfos || []; + + if (!pathInfos.length) { + pathInfos = options.library.Locations.map(p => { + return { + Path: p + }; }); } - function updateMediaLocation(page, path, networkSharePath) { - const virtualFolder = currentOptions.library; - ApiClient.updateMediaPath(virtualFolder.Name, { - Path: path, - NetworkPath: networkSharePath - }).then(() => { - hasChanges = true; - refreshLibraryFromServer(page); - }, () => { - toast(globalize.translate('ErrorAddingMediaPathToVirtualFolder')); - }); + if (options.library.CollectionType === 'boxsets') { + page.querySelector('.folders').classList.add('hide'); + } else { + page.querySelector('.folders').classList.remove('hide'); } - function onRemoveClick(btnRemovePath, location) { - const button = btnRemovePath; - const virtualFolder = currentOptions.library; + page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join(''); +} - confirm({ - title: globalize.translate('HeaderRemoveMediaLocation'), - text: globalize.translate('MessageConfirmRemoveMediaLocation'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(() => { - const refreshAfterChange = currentOptions.refresh; - ApiClient.removeMediaPath(virtualFolder.Name, location, refreshAfterChange).then(() => { - hasChanges = true; - refreshLibraryFromServer(dom.parentWithClass(button, 'dlg-libraryeditor')); - }, () => { - toast(globalize.translate('ErrorDefault')); - }); - }); - } +function onAddButtonClick() { + showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor')); +} - function onListItemClick(e) { - const listItem = dom.parentWithClass(e.target, 'listItem'); - - if (listItem) { - const index = parseInt(listItem.getAttribute('data-index'), 10); - const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; - const pathInfo = index == null ? {} : pathInfos[index] || {}; - const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); - const btnRemovePath = dom.parentWithClass(e.target, 'btnRemovePath'); - - if (btnRemovePath) { - onRemoveClick(btnRemovePath, originalPath); - return; - } - - showDirectoryBrowser(dom.parentWithClass(listItem, 'dlg-libraryeditor'), originalPath, pathInfo.NetworkPath); - } - } - - function getFolderHtml(pathInfo, index) { - let html = ''; - html += `
`; - html += `
`; - html += '

'; - html += escapeHtml(pathInfo.Path); - html += '

'; - - if (pathInfo.NetworkPath) { - html += `
${escapeHtml(pathInfo.NetworkPath)}
`; - } - - html += '
'; - html += ``; - html += '
'; - return html; - } - - function refreshLibraryFromServer(page) { - ApiClient.getVirtualFolders().then(result => { - const library = result.filter(f => { - return f.Name === currentOptions.library.Name; - })[0]; - - if (library) { - currentOptions.library = library; - renderLibrary(page, currentOptions); - } - }); - } - - function renderLibrary(page, options) { - let pathInfos = (options.library.LibraryOptions || {}).PathInfos || []; - - if (!pathInfos.length) { - pathInfos = options.library.Locations.map(p => { - return { - Path: p - }; - }); - } - - if (options.library.CollectionType === 'boxsets') { - page.querySelector('.folders').classList.add('hide'); - } else { - page.querySelector('.folders').classList.remove('hide'); - } - - page.querySelector('.folderList').innerHTML = pathInfos.map(getFolderHtml).join(''); - } - - function onAddButtonClick() { - showDirectoryBrowser(dom.parentWithClass(this, 'dlg-libraryeditor')); - } - - function showDirectoryBrowser(context, originalPath, networkPath) { - import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - enableNetworkSharePath: true, - pathReadOnly: originalPath != null, - path: originalPath, - networkSharePath: networkPath, - callback: function (path, networkSharePath) { - if (path) { - if (originalPath) { - updateMediaLocation(context, originalPath, networkSharePath); - } else { - addMediaLocation(context, path, networkSharePath); - } +function showDirectoryBrowser(context, originalPath, networkPath) { + import('../directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + enableNetworkSharePath: true, + pathReadOnly: originalPath != null, + path: originalPath, + networkSharePath: networkPath, + callback: function (path, networkSharePath) { + if (path) { + if (originalPath) { + updateMediaLocation(context, originalPath, networkSharePath); + } else { + addMediaLocation(context, path, networkSharePath); } - - picker.close(); } - }); + + picker.close(); + } }); - } + }); +} - function initEditor(dlg, options) { - renderLibrary(dlg, options); - dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); - dlg.querySelector('.folderList').addEventListener('click', onListItemClick); - dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); - libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions); - } +function initEditor(dlg, options) { + renderLibrary(dlg, options); + dlg.querySelector('.btnAddFolder').addEventListener('click', onAddButtonClick); + dlg.querySelector('.folderList').addEventListener('click', onListItemClick); + dlg.querySelector('.btnSubmit').addEventListener('click', onEditLibrary); + libraryoptionseditor.embed(dlg.querySelector('.libraryOptions'), options.library.CollectionType, options.library.LibraryOptions); +} - function onDialogClosed() { - currentDeferred.resolveWith(null, [hasChanges]); - } +function onDialogClosed() { + currentDeferred.resolveWith(null, [hasChanges]); +} export class showEditor { constructor(options) { @@ -227,10 +226,9 @@ export class showEditor { } } - let currentDeferred; - let currentOptions; - let hasChanges = false; - let isCreating = false; +let currentDeferred; +let currentOptions; +let hasChanges = false; +let isCreating = false; -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index 530e102f14..9f5c366083 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -10,574 +10,571 @@ import '../guide/programs.scss'; import '../../elements/emby-button/emby-button'; import * as userSettings from '../../scripts/settings/userSettings'; -/* eslint-disable indent */ - function getTimerIndicator(item) { - let status; +function getTimerIndicator(item) { + let status; - if (item.Type === 'SeriesTimer') { + if (item.Type === 'SeriesTimer') { + return ''; + } else if (item.TimerId || item.SeriesTimerId) { + status = item.Status || 'Cancelled'; + } else if (item.Type === 'Timer') { + status = item.Status; + } else { + return ''; + } + + if (item.SeriesTimerId) { + if (status !== 'Cancelled') { return ''; - } else if (item.TimerId || item.SeriesTimerId) { - status = item.Status || 'Cancelled'; - } else if (item.Type === 'Timer') { - status = item.Status; + } + + return ''; + } + + return ''; +} + +function getProgramInfoHtml(item, options) { + let html = ''; + + const miscInfo = []; + let text; + let date; + + if (item.StartDate && options.programTime !== false) { + try { + text = ''; + + date = datetime.parseISO8601Date(item.StartDate); + + if (options.startDate !== false) { + text += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }); + } + + text += ` ${datetime.getDisplayTime(date)}`; + + if (item.EndDate) { + date = datetime.parseISO8601Date(item.EndDate); + text += ` - ${datetime.getDisplayTime(date)}`; + } + + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.StartDate); + } + } + + if (item.ChannelNumber) { + miscInfo.push(`CH ${item.ChannelNumber}`); + } + + if (item.ChannelName) { + if (options.interactive && item.ChannelId) { + miscInfo.push({ + html: `${escapeHtml(item.ChannelName)}` + }); } else { - return ''; + miscInfo.push(escapeHtml(item.ChannelName)); } - - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return ''; - } - - return ''; - } - - return ''; } - function getProgramInfoHtml(item, options) { - let html = ''; - - const miscInfo = []; - let text; - let date; - - if (item.StartDate && options.programTime !== false) { - try { - text = ''; - - date = datetime.parseISO8601Date(item.StartDate); - - if (options.startDate !== false) { - text += datetime.toLocaleDateString(date, { weekday: 'short', month: 'short', day: 'numeric' }); - } - - text += ` ${datetime.getDisplayTime(date)}`; - - if (item.EndDate) { - date = datetime.parseISO8601Date(item.EndDate); - text += ` - ${datetime.getDisplayTime(date)}`; - } - - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.StartDate); - } + if (options.timerIndicator !== false) { + const timerHtml = getTimerIndicator(item); + if (timerHtml) { + miscInfo.push({ + html: timerHtml + }); } - - if (item.ChannelNumber) { - miscInfo.push(`CH ${item.ChannelNumber}`); - } - - if (item.ChannelName) { - if (options.interactive && item.ChannelId) { - miscInfo.push({ - html: `${escapeHtml(item.ChannelName)}` - }); - } else { - miscInfo.push(escapeHtml(item.ChannelName)); - } - } - - if (options.timerIndicator !== false) { - const timerHtml = getTimerIndicator(item); - if (timerHtml) { - miscInfo.push({ - html: timerHtml - }); - } - } - - html += miscInfo.map(m => { - return getMediaInfoItem(m); - }).join(''); - - return html; } - export function getMediaInfoHtml(item, options = {}) { - let html = ''; + html += miscInfo.map(m => { + return getMediaInfoItem(m); + }).join(''); - const miscInfo = []; - let text; - let date; - let count; + return html; +} - const showFolderRuntime = item.Type === 'MusicAlbum' || item.MediaType === 'MusicArtist' || item.Type === 'Playlist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; +export function getMediaInfoHtml(item, options = {}) { + let html = ''; - if (showFolderRuntime) { - count = item.SongCount || item.ChildCount; + const miscInfo = []; + let text; + let date; + let count; - if (count) { - miscInfo.push(globalize.translate('TrackCount', count)); - } + const showFolderRuntime = item.Type === 'MusicAlbum' || item.MediaType === 'MusicArtist' || item.Type === 'Playlist' || item.MediaType === 'Playlist' || item.MediaType === 'MusicGenre'; - if (item.RunTimeTicks) { - miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); - } - } else if (item.Type === 'PhotoAlbum' || item.Type === 'BoxSet') { - count = item.ChildCount; + if (showFolderRuntime) { + count = item.SongCount || item.ChildCount; - if (count) { - miscInfo.push(globalize.translate('ItemCount', count)); - } + if (count) { + miscInfo.push(globalize.translate('TrackCount', count)); } - if ((item.Type === 'Episode' || item.MediaType === 'Photo') + if (item.RunTimeTicks) { + miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); + } + } else if (item.Type === 'PhotoAlbum' || item.Type === 'BoxSet') { + count = item.ChildCount; + + if (count) { + miscInfo.push(globalize.translate('ItemCount', count)); + } + } + + if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false && item.PremiereDate - ) { - try { - //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog - date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); + ) { + try { + //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog + date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); - text = datetime.toLocaleDateString(date); + text = datetime.toLocaleDateString(date); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.PremiereDate); + } + } + + if (item.Type === 'SeriesTimer') { + if (item.RecordAnyTime) { + miscInfo.push(globalize.translate('Anytime')); + } else { + miscInfo.push(datetime.getDisplayTime(item.StartDate)); + } + + if (item.RecordAnyChannel) { + miscInfo.push(globalize.translate('AllChannels')); + } else { + miscInfo.push(item.ChannelName || globalize.translate('OneChannel')); + } + } + + if (item.StartDate && item.Type !== 'Program' && item.Type !== 'SeriesTimer' && item.Type !== 'Timer') { + try { + date = datetime.parseISO8601Date(item.StartDate); + + text = datetime.toLocaleDateString(date); + miscInfo.push(text); + + if (item.Type !== 'Recording') { + text = datetime.getDisplayTime(date); + miscInfo.push(text); + } + } catch (e) { + console.error('error parsing date:', item.StartDate); + } + } + + if (options.year !== false && item.ProductionYear && item.Type === 'Series') { + if (item.Status === 'Continuing') { + miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, { useGrouping: false }))); + } else if (item.ProductionYear) { + text = datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); + + if (item.EndDate) { + try { + const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); + + if (endYear !== item.ProductionYear) { + text += `-${endYear}`; + } + } catch (e) { + console.error('error parsing date:', item.EndDate); + } + } + + miscInfo.push(text); + } + } + + if (item.Type === 'Program' || item.Type === 'Timer') { + let program = item; + if (item.Type === 'Timer') { + program = item.ProgramInfo; + } + + if (options.programIndicator !== false) { + if (program.IsLive && userSettings.get('guide-indicator-live') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Live')}
` + }); + } else if (program.IsPremiere && userSettings.get('guide-indicator-premiere') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Premiere')}
` + }); + } else if (program.IsSeries && !program.IsRepeat && userSettings.get('guide-indicator-new') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('New')}
` + }); + } else if (program.IsSeries && program.IsRepeat && userSettings.get('guide-indicator-repeat') === 'true') { + miscInfo.push({ + html: `
${globalize.translate('Repeat')}
` + }); + } + } + + if ((program.IsSeries || program.EpisodeTitle) && options.episodeTitle !== false) { + text = itemHelper.getDisplayName(program, { + includeIndexNumber: options.episodeTitleIndexNumber + }); + + if (text) { + miscInfo.push(escapeHtml(text)); + } + } else if (program.IsMovie && program.ProductionYear && options.originalAirDate !== false) { + miscInfo.push(program.ProductionYear); + } else if (program.PremiereDate && options.originalAirDate !== false) { + try { + date = datetime.parseISO8601Date(program.PremiereDate); + text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', program.PremiereDate); + } + } else if (program.ProductionYear && options.year !== false ) { + miscInfo.push(program.ProductionYear); + } + } + + if (options.year !== false && item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' + && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season' + ) { + if (item.ProductionYear) { + miscInfo.push(item.ProductionYear); + } else if (item.PremiereDate) { + try { + text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); miscInfo.push(text); } catch (e) { console.error('error parsing date:', item.PremiereDate); } } - - if (item.Type === 'SeriesTimer') { - if (item.RecordAnyTime) { - miscInfo.push(globalize.translate('Anytime')); - } else { - miscInfo.push(datetime.getDisplayTime(item.StartDate)); - } - - if (item.RecordAnyChannel) { - miscInfo.push(globalize.translate('AllChannels')); - } else { - miscInfo.push(item.ChannelName || globalize.translate('OneChannel')); - } - } - - if (item.StartDate && item.Type !== 'Program' && item.Type !== 'SeriesTimer' && item.Type !== 'Timer') { - try { - date = datetime.parseISO8601Date(item.StartDate); - - text = datetime.toLocaleDateString(date); - miscInfo.push(text); - - if (item.Type !== 'Recording') { - text = datetime.getDisplayTime(date); - miscInfo.push(text); - } - } catch (e) { - console.error('error parsing date:', item.StartDate); - } - } - - if (options.year !== false && item.ProductionYear && item.Type === 'Series') { - if (item.Status === 'Continuing') { - miscInfo.push(globalize.translate('SeriesYearToPresent', datetime.toLocaleString(item.ProductionYear, { useGrouping: false }))); - } else if (item.ProductionYear) { - text = datetime.toLocaleString(item.ProductionYear, { useGrouping: false }); - - if (item.EndDate) { - try { - const endYear = datetime.toLocaleString(datetime.parseISO8601Date(item.EndDate).getFullYear(), { useGrouping: false }); - - if (endYear !== item.ProductionYear) { - text += `-${endYear}`; - } - } catch (e) { - console.error('error parsing date:', item.EndDate); - } - } - - miscInfo.push(text); - } - } - - if (item.Type === 'Program' || item.Type === 'Timer') { - let program = item; - if (item.Type === 'Timer') { - program = item.ProgramInfo; - } - - if (options.programIndicator !== false) { - if (program.IsLive && userSettings.get('guide-indicator-live') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Live')}
` - }); - } else if (program.IsPremiere && userSettings.get('guide-indicator-premiere') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Premiere')}
` - }); - } else if (program.IsSeries && !program.IsRepeat && userSettings.get('guide-indicator-new') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('New')}
` - }); - } else if (program.IsSeries && program.IsRepeat && userSettings.get('guide-indicator-repeat') === 'true') { - miscInfo.push({ - html: `
${globalize.translate('Repeat')}
` - }); - } - } - - if ((program.IsSeries || program.EpisodeTitle) && options.episodeTitle !== false) { - text = itemHelper.getDisplayName(program, { - includeIndexNumber: options.episodeTitleIndexNumber - }); - - if (text) { - miscInfo.push(escapeHtml(text)); - } - } else if (program.IsMovie && program.ProductionYear && options.originalAirDate !== false) { - miscInfo.push(program.ProductionYear); - } else if (program.PremiereDate && options.originalAirDate !== false) { - try { - date = datetime.parseISO8601Date(program.PremiereDate); - text = globalize.translate('OriginalAirDateValue', datetime.toLocaleDateString(date)); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', program.PremiereDate); - } - } else if (program.ProductionYear && options.year !== false ) { - miscInfo.push(program.ProductionYear); - } - } - - if (options.year !== false && item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' - && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season' - ) { - if (item.ProductionYear) { - miscInfo.push(item.ProductionYear); - } else if (item.PremiereDate) { - try { - text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), { useGrouping: false }); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.PremiereDate); - } - } - } - - if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Timer' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { - if (item.Type === 'Audio') { - miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); - } else { - miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); - } - } - - if (options.officialRating !== false && item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { - miscInfo.push({ - text: item.OfficialRating, - cssClass: 'mediaInfoOfficialRating' - }); - } - - if (item.Video3DFormat) { - miscInfo.push('3D'); - } - - if (item.MediaType === 'Photo' && item.Width && item.Height) { - miscInfo.push(`${item.Width}x${item.Height}`); - } - - if (options.container !== false && item.Type === 'Audio' && item.Container) { - miscInfo.push(item.Container); - } - - html += miscInfo.map(m => { - return getMediaInfoItem(m); - }).join(''); - - if (options.starRating !== false) { - html += getStarIconsHtml(item); - } - - if (item.HasSubtitles && options.subtitles !== false) { - html += '
CC
'; - } - - if (item.CriticRating && options.criticRating !== false) { - if (item.CriticRating >= 60) { - html += `
${item.CriticRating}
`; - } else { - html += `
${item.CriticRating}
`; - } - } - - if (options.endsAt !== false) { - const endsAt = getEndsAt(item); - if (endsAt) { - html += getMediaInfoItem(endsAt, 'endsAt'); - } - } - - html += indicators.getMissingIndicator(item); - - return html; } - export function getEndsAt(item) { - if (item.MediaType === 'Video' && item.RunTimeTicks && !item.StartDate) { - let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); - endDate = new Date(endDate); - - const displayTime = datetime.getDisplayTime(endDate); - return globalize.translate('EndsAtValue', displayTime); + if (item.RunTimeTicks && item.Type !== 'Series' && item.Type !== 'Program' && item.Type !== 'Timer' && item.Type !== 'Book' && !showFolderRuntime && options.runtime !== false) { + if (item.Type === 'Audio') { + miscInfo.push(datetime.getDisplayRunningTime(item.RunTimeTicks)); + } else { + miscInfo.push(datetime.getDisplayDuration(item.RunTimeTicks)); } - - return null; } - export function getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, includeText) { - let endDate = new Date().getTime() + (1 / playbackRate) * ((runtimeTicks - (positionTicks || 0)) / 10000); + if (options.officialRating !== false && item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { + miscInfo.push({ + text: item.OfficialRating, + cssClass: 'mediaInfoOfficialRating' + }); + } + + if (item.Video3DFormat) { + miscInfo.push('3D'); + } + + if (item.MediaType === 'Photo' && item.Width && item.Height) { + miscInfo.push(`${item.Width}x${item.Height}`); + } + + if (options.container !== false && item.Type === 'Audio' && item.Container) { + miscInfo.push(item.Container); + } + + html += miscInfo.map(m => { + return getMediaInfoItem(m); + }).join(''); + + if (options.starRating !== false) { + html += getStarIconsHtml(item); + } + + if (item.HasSubtitles && options.subtitles !== false) { + html += '
CC
'; + } + + if (item.CriticRating && options.criticRating !== false) { + if (item.CriticRating >= 60) { + html += `
${item.CriticRating}
`; + } else { + html += `
${item.CriticRating}
`; + } + } + + if (options.endsAt !== false) { + const endsAt = getEndsAt(item); + if (endsAt) { + html += getMediaInfoItem(endsAt, 'endsAt'); + } + } + + html += indicators.getMissingIndicator(item); + + return html; +} + +export function getEndsAt(item) { + if (item.MediaType === 'Video' && item.RunTimeTicks && !item.StartDate) { + let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); endDate = new Date(endDate); const displayTime = datetime.getDisplayTime(endDate); - - if (includeText === false) { - return displayTime; - } return globalize.translate('EndsAtValue', displayTime); } - function getMediaInfoItem(m, cssClass) { - cssClass = cssClass ? (`${cssClass} mediaInfoItem`) : 'mediaInfoItem'; - let mediaInfoText = m; + return null; +} - if (typeof (m) !== 'string' && typeof (m) !== 'number') { - if (m.html) { - return m.html; - } - mediaInfoText = m.text; - cssClass += ` ${m.cssClass}`; +export function getEndsAtFromPosition(runtimeTicks, positionTicks, playbackRate, includeText) { + let endDate = new Date().getTime() + (1 / playbackRate) * ((runtimeTicks - (positionTicks || 0)) / 10000); + endDate = new Date(endDate); + + const displayTime = datetime.getDisplayTime(endDate); + + if (includeText === false) { + return displayTime; + } + return globalize.translate('EndsAtValue', displayTime); +} + +function getMediaInfoItem(m, cssClass) { + cssClass = cssClass ? (`${cssClass} mediaInfoItem`) : 'mediaInfoItem'; + let mediaInfoText = m; + + if (typeof (m) !== 'string' && typeof (m) !== 'number') { + if (m.html) { + return m.html; } - return `
${mediaInfoText}
`; + mediaInfoText = m.text; + cssClass += ` ${m.cssClass}`; + } + return `
${mediaInfoText}
`; +} + +function getStarIconsHtml(item) { + let html = ''; + + if (item.CommunityRating) { + html += '
'; + + html += ''; + html += item.CommunityRating.toFixed(1); + html += '
'; } - function getStarIconsHtml(item) { - let html = ''; + return html; +} - if (item.CommunityRating) { - html += '
'; - - html += ''; - html += item.CommunityRating.toFixed(1); - html += '
'; +function dynamicEndTime(elem, item) { + const interval = setInterval(() => { + if (!document.body.contains(elem)) { + clearInterval(interval); + return; } - return html; - } + elem.innerHTML = getEndsAt(item); + }, 60000); +} - function dynamicEndTime(elem, item) { - const interval = setInterval(() => { - if (!document.body.contains(elem)) { - clearInterval(interval); - return; - } +export function fillPrimaryMediaInfo(elem, item, options) { + const html = getPrimaryMediaInfoHtml(item, options); - elem.innerHTML = getEndsAt(item); - }, 60000); - } + elem.innerHTML = html; + afterFill(elem, item, options); +} - export function fillPrimaryMediaInfo(elem, item, options) { - const html = getPrimaryMediaInfoHtml(item, options); +export function fillSecondaryMediaInfo(elem, item, options) { + const html = getSecondaryMediaInfoHtml(item, options); - elem.innerHTML = html; - afterFill(elem, item, options); - } + elem.innerHTML = html; + afterFill(elem, item, options); +} - export function fillSecondaryMediaInfo(elem, item, options) { - const html = getSecondaryMediaInfoHtml(item, options); - - elem.innerHTML = html; - afterFill(elem, item, options); - } - - function afterFill(elem, item, options) { - if (options.endsAt !== false) { - const endsAtElem = elem.querySelector('.endsAt'); - if (endsAtElem) { - dynamicEndTime(endsAtElem, item); - } - } - - const lnkChannel = elem.querySelector('.lnkChannel'); - if (lnkChannel) { - lnkChannel.addEventListener('click', onChannelLinkClick); +function afterFill(elem, item, options) { + if (options.endsAt !== false) { + const endsAtElem = elem.querySelector('.endsAt'); + if (endsAtElem) { + dynamicEndTime(endsAtElem, item); } } - function onChannelLinkClick(e) { - const channelId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); + const lnkChannel = elem.querySelector('.lnkChannel'); + if (lnkChannel) { + lnkChannel.addEventListener('click', onChannelLinkClick); + } +} - appRouter.showItem(channelId, serverId); +function onChannelLinkClick(e) { + const channelId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); - e.preventDefault(); - return false; + appRouter.showItem(channelId, serverId); + + e.preventDefault(); + return false; +} + +export function getPrimaryMediaInfoHtml(item, options = {}) { + if (options.interactive === undefined) { + options.interactive = false; } - export function getPrimaryMediaInfoHtml(item, options = {}) { - if (options.interactive === undefined) { - options.interactive = false; - } + return getMediaInfoHtml(item, options); +} - return getMediaInfoHtml(item, options); +export function getSecondaryMediaInfoHtml(item, options) { + options = options || {}; + if (options.interactive == null) { + options.interactive = false; + } + if (item.Type === 'Program') { + return getProgramInfoHtml(item, options); } - export function getSecondaryMediaInfoHtml(item, options) { - options = options || {}; - if (options.interactive == null) { - options.interactive = false; - } - if (item.Type === 'Program') { - return getProgramInfoHtml(item, options); - } + return ''; +} - return ''; +export function getResolutionText(i) { + const width = i.Width; + const height = i.Height; + + if (width && height) { + if (width >= 3800 || height >= 2000) { + return '4K'; + } + if (width >= 2500 || height >= 1400) { + if (i.IsInterlaced) { + return '1440i'; + } + return '1440p'; + } + if (width >= 1800 || height >= 1000) { + if (i.IsInterlaced) { + return '1080i'; + } + return '1080p'; + } + if (width >= 1200 || height >= 700) { + if (i.IsInterlaced) { + return '720i'; + } + return '720p'; + } + if (width >= 700 || height >= 400) { + if (i.IsInterlaced) { + return '480i'; + } + return '480p'; + } } + return null; +} - export function getResolutionText(i) { - const width = i.Width; - const height = i.Height; - - if (width && height) { - if (width >= 3800 || height >= 2000) { - return '4K'; - } - if (width >= 2500 || height >= 1400) { - if (i.IsInterlaced) { - return '1440i'; - } - return '1440p'; - } - if (width >= 1800 || height >= 1000) { - if (i.IsInterlaced) { - return '1080i'; - } - return '1080p'; - } - if (width >= 1200 || height >= 700) { - if (i.IsInterlaced) { - return '720i'; - } - return '720p'; - } - if (width >= 700 || height >= 400) { - if (i.IsInterlaced) { - return '480i'; - } - return '480p'; - } - } +function getAudioStreamForDisplay(item) { + if (!item.MediaSources) { return null; } - function getAudioStreamForDisplay(item) { - if (!item.MediaSources) { - return null; - } - - const mediaSource = item.MediaSources[0]; - if (!mediaSource) { - return null; - } - - return (mediaSource.MediaStreams || []).filter(i => { - return i.Type === 'Audio' && (i.Index === mediaSource.DefaultAudioStreamIndex || mediaSource.DefaultAudioStreamIndex == null); - })[0]; + const mediaSource = item.MediaSources[0]; + if (!mediaSource) { + return null; } - export function getMediaInfoStats(item) { - const list = []; + return (mediaSource.MediaStreams || []).filter(i => { + return i.Type === 'Audio' && (i.Index === mediaSource.DefaultAudioStreamIndex || mediaSource.DefaultAudioStreamIndex == null); + })[0]; +} - const mediaSource = (item.MediaSources || [])[0] || {}; +export function getMediaInfoStats(item) { + const list = []; - const videoStream = (mediaSource.MediaStreams || []).filter(i => { - return i.Type === 'Video'; - })[0] || {}; - const audioStream = getAudioStreamForDisplay(item) || {}; + const mediaSource = (item.MediaSources || [])[0] || {}; - if (item.VideoType === 'Dvd') { - list.push({ - type: 'mediainfo', - text: 'Dvd' - }); - } + const videoStream = (mediaSource.MediaStreams || []).filter(i => { + return i.Type === 'Video'; + })[0] || {}; + const audioStream = getAudioStreamForDisplay(item) || {}; - if (item.VideoType === 'BluRay') { - list.push({ - type: 'mediainfo', - text: 'BluRay' - }); - } - - const resolutionText = getResolutionText(videoStream); - if (resolutionText) { - list.push({ - type: 'mediainfo', - text: resolutionText - }); - } - - if (videoStream.Codec) { - list.push({ - type: 'mediainfo', - text: videoStream.Codec - }); - } - - const channels = audioStream.Channels; - let channelText; - - if (channels === 8) { - channelText = '7.1'; - } else if (channels === 7) { - channelText = '6.1'; - } else if (channels === 6) { - channelText = '5.1'; - } else if (channels === 2) { - channelText = '2.0'; - } - - if (channelText) { - list.push({ - type: 'mediainfo', - text: channelText - }); - } - - const audioCodec = (audioStream.Codec || '').toLowerCase(); - - if ((audioCodec === 'dca' || audioCodec === 'dts') && audioStream.Profile) { - list.push({ - type: 'mediainfo', - text: audioStream.Profile - }); - } else if (audioStream.Codec) { - list.push({ - type: 'mediainfo', - text: audioStream.Codec - }); - } - - if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) { - const dateCreated = datetime.parseISO8601Date(item.DateCreated); - - list.push({ - type: 'added', - text: globalize.translate('AddedOnValue', `${datetime.toLocaleDateString(dateCreated)} ${datetime.getDisplayTime(dateCreated)}`) - }); - } - - return list; + if (item.VideoType === 'Dvd') { + list.push({ + type: 'mediainfo', + text: 'Dvd' + }); } -/* eslint-enable indent */ + if (item.VideoType === 'BluRay') { + list.push({ + type: 'mediainfo', + text: 'BluRay' + }); + } + + const resolutionText = getResolutionText(videoStream); + if (resolutionText) { + list.push({ + type: 'mediainfo', + text: resolutionText + }); + } + + if (videoStream.Codec) { + list.push({ + type: 'mediainfo', + text: videoStream.Codec + }); + } + + const channels = audioStream.Channels; + let channelText; + + if (channels === 8) { + channelText = '7.1'; + } else if (channels === 7) { + channelText = '6.1'; + } else if (channels === 6) { + channelText = '5.1'; + } else if (channels === 2) { + channelText = '2.0'; + } + + if (channelText) { + list.push({ + type: 'mediainfo', + text: channelText + }); + } + + const audioCodec = (audioStream.Codec || '').toLowerCase(); + + if ((audioCodec === 'dca' || audioCodec === 'dts') && audioStream.Profile) { + list.push({ + type: 'mediainfo', + text: audioStream.Profile + }); + } else if (audioStream.Codec) { + list.push({ + type: 'mediainfo', + text: audioStream.Codec + }); + } + + if (item.DateCreated && itemHelper.enableDateAddedDisplay(item)) { + const dateCreated = datetime.parseISO8601Date(item.DateCreated); + + list.push({ + type: 'added', + text: globalize.translate('AddedOnValue', `${datetime.toLocaleDateString(dateCreated)} ${datetime.getDisplayTime(dateCreated)}`) + }); + } + + return list; +} export default { getMediaInfoHtml: getPrimaryMediaInfoHtml, diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index a9596c3786..3aeaee63a3 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -23,1094 +23,1091 @@ import toast from '../toast/toast'; import { appRouter } from '../appRouter'; import template from './metadataEditor.template.html'; -/* eslint-disable indent */ +let currentContext; +let metadataEditorInfo; +let currentItem; - let currentContext; - let metadataEditorInfo; - let currentItem; +function isDialog() { + return currentContext.classList.contains('dialog'); +} - function isDialog() { - return currentContext.classList.contains('dialog'); +function closeDialog() { + if (isDialog()) { + dialogHelper.close(currentContext); + } +} + +function submitUpdatedItem(form, item) { + function afterContentTypeUpdated() { + toast(globalize.translate('MessageItemSaved')); + + loading.hide(); + closeDialog(); } - function closeDialog() { - if (isDialog()) { - dialogHelper.close(currentContext); - } - } + const apiClient = getApiClient(); - function submitUpdatedItem(form, item) { - function afterContentTypeUpdated() { - toast(globalize.translate('MessageItemSaved')); + apiClient.updateItem(item).then(function () { + const newContentType = form.querySelector('#selectContentType').value || ''; - loading.hide(); - closeDialog(); - } + if ((metadataEditorInfo.ContentType || '') !== newContentType) { + apiClient.ajax({ - const apiClient = getApiClient(); + url: apiClient.getUrl('Items/' + item.Id + '/ContentType', { + ContentType: newContentType + }), - apiClient.updateItem(item).then(function () { - const newContentType = form.querySelector('#selectContentType').value || ''; + type: 'POST' - if ((metadataEditorInfo.ContentType || '') !== newContentType) { - apiClient.ajax({ - - url: apiClient.getUrl('Items/' + item.Id + '/ContentType', { - ContentType: newContentType - }), - - type: 'POST' - - }).then(function () { - afterContentTypeUpdated(); - }); - } else { + }).then(function () { afterContentTypeUpdated(); - } - }); - } - - function getSelectedAirDays(form) { - const checkedItems = form.querySelectorAll('.chkAirDay:checked') || []; - return Array.prototype.map.call(checkedItems, function (c) { - return c.getAttribute('data-day'); - }); - } - - function getAlbumArtists(form) { - return form.querySelector('#txtAlbumArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { - Name: a - }; - }); - } - - function getArtists(form) { - return form.querySelector('#txtArtist').value.trim().split(';').filter(function (s) { - return s.length > 0; - }).map(function (a) { - return { - Name: a - }; - }); - } - - function getDateValue(form, element, property) { - let val = form.querySelector(element).value; - - if (!val) { - return null; + }); + } else { + afterContentTypeUpdated(); } + }); +} - if (currentItem[property]) { - const date = datetime.parseISO8601Date(currentItem[property], true); +function getSelectedAirDays(form) { + const checkedItems = form.querySelectorAll('.chkAirDay:checked') || []; + return Array.prototype.map.call(checkedItems, function (c) { + return c.getAttribute('data-day'); + }); +} - const parts = date.toISOString().split('T'); - - // If the date is the same, preserve the time - if (parts[0].indexOf(val) === 0) { - const iso = parts[1]; - - val += 'T' + iso; - } - } - - return val; - } - - function onSubmit(e) { - loading.show(); - - const form = this; - - const item = { - Id: currentItem.Id, - Name: form.querySelector('#txtName').value, - OriginalTitle: form.querySelector('#txtOriginalName').value, - ForcedSortName: form.querySelector('#txtSortName').value, - CommunityRating: form.querySelector('#txtCommunityRating').value, - CriticRating: form.querySelector('#txtCriticRating').value, - IndexNumber: form.querySelector('#txtIndexNumber').value || null, - AirsBeforeSeasonNumber: form.querySelector('#txtAirsBeforeSeason').value, - AirsAfterSeasonNumber: form.querySelector('#txtAirsAfterSeason').value, - AirsBeforeEpisodeNumber: form.querySelector('#txtAirsBeforeEpisode').value, - ParentIndexNumber: form.querySelector('#txtParentIndexNumber').value || null, - DisplayOrder: form.querySelector('#selectDisplayOrder').value, - Album: form.querySelector('#txtAlbum').value, - AlbumArtists: getAlbumArtists(form), - ArtistItems: getArtists(form), - Overview: form.querySelector('#txtOverview').value, - Status: form.querySelector('#selectStatus').value, - AirDays: getSelectedAirDays(form), - AirTime: form.querySelector('#txtAirTime').value, - Genres: getListValues(form.querySelector('#listGenres')), - Tags: getListValues(form.querySelector('#listTags')), - Studios: getListValues(form.querySelector('#listStudios')).map(function (element) { - return { Name: element }; - }), - - PremiereDate: getDateValue(form, '#txtPremiereDate', 'PremiereDate'), - DateCreated: getDateValue(form, '#txtDateAdded', 'DateCreated'), - EndDate: getDateValue(form, '#txtEndDate', 'EndDate'), - ProductionYear: form.querySelector('#txtProductionYear').value, - AspectRatio: form.querySelector('#txtOriginalAspectRatio').value, - Video3DFormat: form.querySelector('#select3dFormat').value, - - OfficialRating: form.querySelector('#selectOfficialRating').value, - CustomRating: form.querySelector('#selectCustomRating').value, - People: currentItem.People, - LockData: form.querySelector('#chkLockData').checked, - LockedFields: Array.prototype.filter.call(form.querySelectorAll('.selectLockedField'), function (c) { - return !c.checked; - }).map(function (c) { - return c.getAttribute('data-value'); - }) +function getAlbumArtists(form) { + return form.querySelector('#txtAlbumArtist').value.trim().split(';').filter(function (s) { + return s.length > 0; + }).map(function (a) { + return { + Name: a }; + }); +} - item.ProviderIds = Object.assign({}, currentItem.ProviderIds); +function getArtists(form) { + return form.querySelector('#txtArtist').value.trim().split(';').filter(function (s) { + return s.length > 0; + }).map(function (a) { + return { + Name: a + }; + }); +} - const idElements = form.querySelectorAll('.txtExternalId'); - Array.prototype.map.call(idElements, function (idElem) { - const providerKey = idElem.getAttribute('data-providerkey'); - item.ProviderIds[providerKey] = idElem.value; - }); +function getDateValue(form, element, property) { + let val = form.querySelector(element).value; - item.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; - item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; - - if (currentItem.Type === 'Person') { - const placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; - - item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; - } - - if (currentItem.Type === 'Series') { - // 600000000 - const seriesRuntime = form.querySelector('#txtSeriesRuntime').value; - item.RunTimeTicks = seriesRuntime ? (seriesRuntime * 600000000) : null; - } - - const tagline = form.querySelector('#txtTagline').value; - item.Taglines = tagline ? [tagline] : []; - - submitUpdatedItem(form, item); - - e.preventDefault(); - e.stopPropagation(); - - // Disable default form submission - return false; + if (!val) { + return null; } - function getListValues(list) { - return Array.prototype.map.call(list.querySelectorAll('.textValue'), function (el) { - return el.textContent; - }); - } + if (currentItem[property]) { + const date = datetime.parseISO8601Date(currentItem[property], true); - function addElementToList(source, sortCallback) { - import('../prompt/prompt').then(({ default: prompt }) => { - prompt({ - label: 'Value:' - }).then(function (text) { - const list = dom.parentWithClass(source, 'editableListviewContainer').querySelector('.paperList'); - const items = getListValues(list); - items.push(text); - populateListView(list, items, sortCallback); - }); - }); - } + const parts = date.toISOString().split('T'); - function removeElementFromList(source) { - const el = dom.parentWithClass(source, 'listItem'); - el.parentNode.removeChild(el); - } + // If the date is the same, preserve the time + if (parts[0].indexOf(val) === 0) { + const iso = parts[1]; - function editPerson(context, person, index) { - import('./personEditor').then(({ default: personEditor }) => { - personEditor.show(person).then(function (updatedPerson) { - const isNew = index === -1; - - if (isNew) { - currentItem.People.push(updatedPerson); - } - - populatePeople(context, currentItem.People); - }); - }); - } - - function afterDeleted(context, item) { - const parentId = item.ParentId || item.SeasonId || item.SeriesId; - - if (parentId) { - reload(context, parentId, item.ServerId); - } else { - appRouter.goHome(); + val += 'T' + iso; } } - function showMoreMenu(context, button, user) { - import('../itemContextMenu').then(({ default: itemContextMenu }) => { - const item = currentItem; + return val; +} - itemContextMenu.show({ - item: item, - positionTo: button, - edit: false, - editImages: true, - editSubtitles: true, - sync: false, - share: false, - play: false, - queue: false, - user: user - }).then(function (result) { - if (result.deleted) { - afterDeleted(context, item); - } else if (result.updated) { - reload(context, item.Id, item.ServerId); - } - }); - }); +function onSubmit(e) { + loading.show(); + + const form = this; + + const item = { + Id: currentItem.Id, + Name: form.querySelector('#txtName').value, + OriginalTitle: form.querySelector('#txtOriginalName').value, + ForcedSortName: form.querySelector('#txtSortName').value, + CommunityRating: form.querySelector('#txtCommunityRating').value, + CriticRating: form.querySelector('#txtCriticRating').value, + IndexNumber: form.querySelector('#txtIndexNumber').value || null, + AirsBeforeSeasonNumber: form.querySelector('#txtAirsBeforeSeason').value, + AirsAfterSeasonNumber: form.querySelector('#txtAirsAfterSeason').value, + AirsBeforeEpisodeNumber: form.querySelector('#txtAirsBeforeEpisode').value, + ParentIndexNumber: form.querySelector('#txtParentIndexNumber').value || null, + DisplayOrder: form.querySelector('#selectDisplayOrder').value, + Album: form.querySelector('#txtAlbum').value, + AlbumArtists: getAlbumArtists(form), + ArtistItems: getArtists(form), + Overview: form.querySelector('#txtOverview').value, + Status: form.querySelector('#selectStatus').value, + AirDays: getSelectedAirDays(form), + AirTime: form.querySelector('#txtAirTime').value, + Genres: getListValues(form.querySelector('#listGenres')), + Tags: getListValues(form.querySelector('#listTags')), + Studios: getListValues(form.querySelector('#listStudios')).map(function (element) { + return { Name: element }; + }), + + PremiereDate: getDateValue(form, '#txtPremiereDate', 'PremiereDate'), + DateCreated: getDateValue(form, '#txtDateAdded', 'DateCreated'), + EndDate: getDateValue(form, '#txtEndDate', 'EndDate'), + ProductionYear: form.querySelector('#txtProductionYear').value, + AspectRatio: form.querySelector('#txtOriginalAspectRatio').value, + Video3DFormat: form.querySelector('#select3dFormat').value, + + OfficialRating: form.querySelector('#selectOfficialRating').value, + CustomRating: form.querySelector('#selectCustomRating').value, + People: currentItem.People, + LockData: form.querySelector('#chkLockData').checked, + LockedFields: Array.prototype.filter.call(form.querySelectorAll('.selectLockedField'), function (c) { + return !c.checked; + }).map(function (c) { + return c.getAttribute('data-value'); + }) + }; + + item.ProviderIds = Object.assign({}, currentItem.ProviderIds); + + const idElements = form.querySelectorAll('.txtExternalId'); + Array.prototype.map.call(idElements, function (idElem) { + const providerKey = idElem.getAttribute('data-providerkey'); + item.ProviderIds[providerKey] = idElem.value; + }); + + item.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + item.PreferredMetadataCountryCode = form.querySelector('#selectCountry').value; + + if (currentItem.Type === 'Person') { + const placeOfBirth = form.querySelector('#txtPlaceOfBirth').value; + + item.ProductionLocations = placeOfBirth ? [placeOfBirth] : []; } - function onEditorClick(e) { - const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); - if (btnRemoveFromEditorList) { - removeElementFromList(btnRemoveFromEditorList); - return; - } - - const btnAddTextItem = dom.parentWithClass(e.target, 'btnAddTextItem'); - if (btnAddTextItem) { - addElementToList(btnAddTextItem); - } + if (currentItem.Type === 'Series') { + // 600000000 + const seriesRuntime = form.querySelector('#txtSeriesRuntime').value; + item.RunTimeTicks = seriesRuntime ? (seriesRuntime * 600000000) : null; } - function getApiClient() { - return ServerConnections.getApiClient(currentItem.ServerId); - } + const tagline = form.querySelector('#txtTagline').value; + item.Taglines = tagline ? [tagline] : []; - function bindAll(elems, eventName, fn) { - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener(eventName, fn); - } - } + submitUpdatedItem(form, item); - function init(context) { - context.querySelector('.externalIds').addEventListener('click', function (e) { - const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId'); - if (btnOpenExternalId) { - const field = context.querySelector('#' + btnOpenExternalId.getAttribute('data-fieldid')); + e.preventDefault(); + e.stopPropagation(); - const formatString = field.getAttribute('data-formatstring'); + // Disable default form submission + return false; +} - if (field.value) { - shell.openUrl(formatString.replace('{0}', field.value)); - } - } +function getListValues(list) { + return Array.prototype.map.call(list.querySelectorAll('.textValue'), function (el) { + return el.textContent; + }); +} + +function addElementToList(source, sortCallback) { + import('../prompt/prompt').then(({ default: prompt }) => { + prompt({ + label: 'Value:' + }).then(function (text) { + const list = dom.parentWithClass(source, 'editableListviewContainer').querySelector('.paperList'); + const items = getListValues(list); + items.push(text); + populateListView(list, items, sortCallback); }); + }); +} - if (!layoutManager.desktop) { - context.querySelector('.btnBack').classList.remove('hide'); - context.querySelector('.btnClose').classList.add('hide'); - } +function removeElementFromList(source) { + const el = dom.parentWithClass(source, 'listItem'); + el.parentNode.removeChild(el); +} - bindAll(context.querySelectorAll('.btnCancel'), 'click', function (event) { - event.preventDefault(); - closeDialog(); - }); +function editPerson(context, person, index) { + import('./personEditor').then(({ default: personEditor }) => { + personEditor.show(person).then(function (updatedPerson) { + const isNew = index === -1; - context.querySelector('.btnMore').addEventListener('click', function (e) { - getApiClient().getCurrentUser().then(function (user) { - showMoreMenu(context, e.target, user); - }); - }); - - context.querySelector('.btnHeaderSave').addEventListener('click', function () { - context.querySelector('.btnSave').click(); - }); - - context.querySelector('#chkLockData').addEventListener('click', function (e) { - if (!e.target.checked) { - showElement('.providerSettingsContainer'); - } else { - hideElement('.providerSettingsContainer'); - } - }); - - context.removeEventListener('click', onEditorClick); - context.addEventListener('click', onEditorClick); - - const form = context.querySelector('form'); - form.removeEventListener('submit', onSubmit); - form.addEventListener('submit', onSubmit); - - context.querySelector('#btnAddPerson').addEventListener('click', function () { - editPerson(context, {}, -1); - }); - - context.querySelector('#peopleList').addEventListener('click', function (e) { - let index; - const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); - if (btnDeletePerson) { - index = parseInt(btnDeletePerson.getAttribute('data-index'), 10); - currentItem.People.splice(index, 1); - populatePeople(context, currentItem.People); + if (isNew) { + currentItem.People.push(updatedPerson); } - const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); - if (btnEditPerson) { - index = parseInt(btnEditPerson.getAttribute('data-index'), 10); - editPerson(context, currentItem.People[index], index); + populatePeople(context, currentItem.People); + }); + }); +} + +function afterDeleted(context, item) { + const parentId = item.ParentId || item.SeasonId || item.SeriesId; + + if (parentId) { + reload(context, parentId, item.ServerId); + } else { + appRouter.goHome(); + } +} + +function showMoreMenu(context, button, user) { + import('../itemContextMenu').then(({ default: itemContextMenu }) => { + const item = currentItem; + + itemContextMenu.show({ + item: item, + positionTo: button, + edit: false, + editImages: true, + editSubtitles: true, + sync: false, + share: false, + play: false, + queue: false, + user: user + }).then(function (result) { + if (result.deleted) { + afterDeleted(context, item); + } else if (result.updated) { + reload(context, item.Id, item.ServerId); } }); + }); +} + +function onEditorClick(e) { + const btnRemoveFromEditorList = dom.parentWithClass(e.target, 'btnRemoveFromEditorList'); + if (btnRemoveFromEditorList) { + removeElementFromList(btnRemoveFromEditorList); + return; } - function getItem(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - - if (itemId) { - return apiClient.getItem(apiClient.getCurrentUserId(), itemId); - } - - return apiClient.getRootFolder(apiClient.getCurrentUserId()); + const btnAddTextItem = dom.parentWithClass(e.target, 'btnAddTextItem'); + if (btnAddTextItem) { + addElementToList(btnAddTextItem); } +} - function getEditorConfig(itemId, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); +function getApiClient() { + return ServerConnections.getApiClient(currentItem.ServerId); +} - if (itemId) { - return apiClient.getJSON(apiClient.getUrl('Items/' + itemId + '/MetadataEditor')); - } - - return Promise.resolve({}); +function bindAll(elems, eventName, fn) { + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener(eventName, fn); } +} - function populateCountries(select, allCountries) { - let html = ''; +function init(context) { + context.querySelector('.externalIds').addEventListener('click', function (e) { + const btnOpenExternalId = dom.parentWithClass(e.target, 'btnOpenExternalId'); + if (btnOpenExternalId) { + const field = context.querySelector('#' + btnOpenExternalId.getAttribute('data-fieldid')); - html += ""; + const formatString = field.getAttribute('data-formatstring'); - for (let i = 0, length = allCountries.length; i < length; i++) { - const culture = allCountries[i]; - - html += "'; - } - - select.innerHTML = html; - } - - function populateLanguages(select, languages) { - let html = ''; - - html += ""; - - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; - - html += "'; - } - - select.innerHTML = html; - } - - function renderContentTypeOptions(context, metadataInfo) { - if (!metadataInfo.ContentTypeOptions.length) { - hideElement('#fldContentType', context); - } else { - showElement('#fldContentType', context); - } - - const html = metadataInfo.ContentTypeOptions.map(function (i) { - return ''; - }).join(''); - - const selectEl = context.querySelector('#selectContentType'); - selectEl.innerHTML = html; - selectEl.value = metadataInfo.ContentType || ''; - } - - function loadExternalIds(context, item, externalIds) { - let html = ''; - - const providerIds = item.ProviderIds || {}; - - for (let i = 0, length = externalIds.length; i < length; i++) { - const idInfo = externalIds[i]; - - const id = 'txt1' + idInfo.Key; - const formatString = idInfo.UrlFormatString || ''; - - let fullName = idInfo.Name; - if (idInfo.Type) { - fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + if (field.value) { + shell.openUrl(formatString.replace('{0}', field.value)); } + } + }); - const labelText = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); + if (!layoutManager.desktop) { + context.querySelector('.btnBack').classList.remove('hide'); + context.querySelector('.btnClose').classList.add('hide'); + } - html += '
'; - html += '
'; + bindAll(context.querySelectorAll('.btnCancel'), 'click', function (event) { + event.preventDefault(); + closeDialog(); + }); - const value = escapeHtml(providerIds[idInfo.Key] || ''); + context.querySelector('.btnMore').addEventListener('click', function (e) { + getApiClient().getCurrentUser().then(function (user) { + showMoreMenu(context, e.target, user); + }); + }); - html += '
'; - html += ''; - html += '
'; + context.querySelector('.btnHeaderSave').addEventListener('click', function () { + context.querySelector('.btnSave').click(); + }); - if (formatString) { - html += ''; + context.querySelector('#chkLockData').addEventListener('click', function (e) { + if (!e.target.checked) { + showElement('.providerSettingsContainer'); + } else { + hideElement('.providerSettingsContainer'); + } + }); + + context.removeEventListener('click', onEditorClick); + context.addEventListener('click', onEditorClick); + + const form = context.querySelector('form'); + form.removeEventListener('submit', onSubmit); + form.addEventListener('submit', onSubmit); + + context.querySelector('#btnAddPerson').addEventListener('click', function () { + editPerson(context, {}, -1); + }); + + context.querySelector('#peopleList').addEventListener('click', function (e) { + let index; + const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); + if (btnDeletePerson) { + index = parseInt(btnDeletePerson.getAttribute('data-index'), 10); + currentItem.People.splice(index, 1); + populatePeople(context, currentItem.People); + } + + const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); + if (btnEditPerson) { + index = parseInt(btnEditPerson.getAttribute('data-index'), 10); + editPerson(context, currentItem.People[index], index); + } + }); +} + +function getItem(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + + if (itemId) { + return apiClient.getItem(apiClient.getCurrentUserId(), itemId); + } + + return apiClient.getRootFolder(apiClient.getCurrentUserId()); +} + +function getEditorConfig(itemId, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); + + if (itemId) { + return apiClient.getJSON(apiClient.getUrl('Items/' + itemId + '/MetadataEditor')); + } + + return Promise.resolve({}); +} + +function populateCountries(select, allCountries) { + let html = ''; + + html += ""; + + for (let i = 0, length = allCountries.length; i < length; i++) { + const culture = allCountries[i]; + + html += "'; + } + + select.innerHTML = html; +} + +function populateLanguages(select, languages) { + let html = ''; + + html += ""; + + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + + html += "'; + } + + select.innerHTML = html; +} + +function renderContentTypeOptions(context, metadataInfo) { + if (!metadataInfo.ContentTypeOptions.length) { + hideElement('#fldContentType', context); + } else { + showElement('#fldContentType', context); + } + + const html = metadataInfo.ContentTypeOptions.map(function (i) { + return ''; + }).join(''); + + const selectEl = context.querySelector('#selectContentType'); + selectEl.innerHTML = html; + selectEl.value = metadataInfo.ContentType || ''; +} + +function loadExternalIds(context, item, externalIds) { + let html = ''; + + const providerIds = item.ProviderIds || {}; + + for (let i = 0, length = externalIds.length; i < length; i++) { + const idInfo = externalIds[i]; + + const id = 'txt1' + idInfo.Key; + const formatString = idInfo.UrlFormatString || ''; + + let fullName = idInfo.Name; + if (idInfo.Type) { + fullName = idInfo.Name + ' ' + globalize.translate(idInfo.Type); + } + + const labelText = globalize.translate('LabelDynamicExternalId', escapeHtml(fullName)); + + html += '
'; + html += '
'; + + const value = escapeHtml(providerIds[idInfo.Key] || ''); + + html += '
'; + html += ''; + html += '
'; + + if (formatString) { + html += ''; + } + html += '
'; + + html += '
'; + } + + const elem = context.querySelector('.externalIds', context); + elem.innerHTML = html; + + if (externalIds.length) { + context.querySelector('.externalIdsSection').classList.remove('hide'); + } else { + context.querySelector('.externalIdsSection').classList.add('hide'); + } +} + +// Function to hide the element by selector or raw element +// Selector can be an element or a selector string +// Context is optional and restricts the querySelector to the context +function hideElement(selector, context, multiple) { + context = context || document; + if (typeof selector === 'string') { + const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; + + Array.prototype.forEach.call(elements, function (el) { + if (el) { + el.classList.add('hide'); } - html += '
'; + }); + } else { + selector.classList.add('hide'); + } +} - html += '
'; - } +// Function to show the element by selector or raw element +// Selector can be an element or a selector string +// Context is optional and restricts the querySelector to the context +function showElement(selector, context, multiple) { + context = context || document; + if (typeof selector === 'string') { + const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - const elem = context.querySelector('.externalIds', context); - elem.innerHTML = html; + Array.prototype.forEach.call(elements, function (el) { + if (el) { + el.classList.remove('hide'); + } + }); + } else { + selector.classList.remove('hide'); + } +} - if (externalIds.length) { - context.querySelector('.externalIdsSection').classList.remove('hide'); - } else { - context.querySelector('.externalIdsSection').classList.add('hide'); - } +function setFieldVisibilities(context, item) { + if (item.Path && item.EnableMediaSourceDisplay !== false) { + showElement('#fldPath', context); + } else { + hideElement('#fldPath', context); } - // Function to hide the element by selector or raw element - // Selector can be an element or a selector string - // Context is optional and restricts the querySelector to the context - function hideElement(selector, context, multiple) { - context = context || document; - if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - - Array.prototype.forEach.call(elements, function (el) { - if (el) { - el.classList.add('hide'); - } - }); - } else { - selector.classList.add('hide'); - } + if (item.Type === 'Series' || item.Type === 'Movie' || item.Type === 'Trailer' || item.Type === 'Person') { + showElement('#fldOriginalName', context); + } else { + hideElement('#fldOriginalName', context); } - // Function to show the element by selector or raw element - // Selector can be an element or a selector string - // Context is optional and restricts the querySelector to the context - function showElement(selector, context, multiple) { - context = context || document; - if (typeof selector === 'string') { - const elements = multiple ? context.querySelectorAll(selector) : [context.querySelector(selector)]; - - Array.prototype.forEach.call(elements, function (el) { - if (el) { - el.classList.remove('hide'); - } - }); - } else { - selector.classList.remove('hide'); - } + if (item.Type === 'Series') { + showElement('#fldSeriesRuntime', context); + } else { + hideElement('#fldSeriesRuntime', context); } - function setFieldVisibilities(context, item) { - if (item.Path && item.EnableMediaSourceDisplay !== false) { - showElement('#fldPath', context); - } else { - hideElement('#fldPath', context); - } + if (item.Type === 'Series' || item.Type === 'Person') { + showElement('#fldEndDate', context); + } else { + hideElement('#fldEndDate', context); + } - if (item.Type === 'Series' || item.Type === 'Movie' || item.Type === 'Trailer' || item.Type === 'Person') { - showElement('#fldOriginalName', context); - } else { - hideElement('#fldOriginalName', context); - } + if (item.Type === 'MusicAlbum') { + showElement('#albumAssociationMessage', context); + } else { + hideElement('#albumAssociationMessage', context); + } - if (item.Type === 'Series') { - showElement('#fldSeriesRuntime', context); - } else { - hideElement('#fldSeriesRuntime', context); - } + if (item.Type === 'Movie' || item.Type === 'Trailer') { + showElement('#fldCriticRating', context); + } else { + hideElement('#fldCriticRating', context); + } - if (item.Type === 'Series' || item.Type === 'Person') { - showElement('#fldEndDate', context); - } else { - hideElement('#fldEndDate', context); - } + if (item.Type === 'Series') { + showElement('#fldStatus', context); + showElement('#fldAirDays', context); + showElement('#fldAirTime', context); + } else { + hideElement('#fldStatus', context); + hideElement('#fldAirDays', context); + hideElement('#fldAirTime', context); + } - if (item.Type === 'MusicAlbum') { - showElement('#albumAssociationMessage', context); - } else { - hideElement('#albumAssociationMessage', context); - } + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { + showElement('#fld3dFormat', context); + } else { + hideElement('#fld3dFormat', context); + } - if (item.Type === 'Movie' || item.Type === 'Trailer') { - showElement('#fldCriticRating', context); - } else { - hideElement('#fldCriticRating', context); - } + if (item.Type === 'Audio') { + showElement('#fldAlbumArtist', context); + } else { + hideElement('#fldAlbumArtist', context); + } - if (item.Type === 'Series') { - showElement('#fldStatus', context); - showElement('#fldAirDays', context); - showElement('#fldAirTime', context); - } else { - hideElement('#fldStatus', context); - hideElement('#fldAirDays', context); - hideElement('#fldAirTime', context); - } + if (item.Type === 'Audio' || item.Type === 'MusicVideo') { + showElement('#fldArtist', context); + showElement('#fldAlbum', context); + } else { + hideElement('#fldArtist', context); + hideElement('#fldAlbum', context); + } - if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { - showElement('#fld3dFormat', context); - } else { - hideElement('#fld3dFormat', context); - } + if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { + showElement('#collapsibleSpecialEpisodeInfo', context); + } else { + hideElement('#collapsibleSpecialEpisodeInfo', context); + } - if (item.Type === 'Audio') { - showElement('#fldAlbumArtist', context); - } else { - hideElement('#fldAlbumArtist', context); - } - - if (item.Type === 'Audio' || item.Type === 'MusicVideo') { - showElement('#fldArtist', context); - showElement('#fldAlbum', context); - } else { - hideElement('#fldArtist', context); - hideElement('#fldAlbum', context); - } - - if (item.Type === 'Episode' && item.ParentIndexNumber === 0) { - showElement('#collapsibleSpecialEpisodeInfo', context); - } else { - hideElement('#collapsibleSpecialEpisodeInfo', context); - } - - if (item.Type === 'Person' + if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel' || item.Type === 'Book') { - hideElement('#peopleCollapsible', context); - } else { - showElement('#peopleCollapsible', context); - } - - if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel') { - hideElement('#fldCommunityRating', context); - hideElement('#genresCollapsible', context); - hideElement('#studiosCollapsible', context); - - if (item.Type === 'TvChannel') { - showElement('#fldOfficialRating', context); - } else { - hideElement('#fldOfficialRating', context); - } - hideElement('#fldCustomRating', context); - } else { - showElement('#fldCommunityRating', context); - showElement('#genresCollapsible', context); - showElement('#studiosCollapsible', context); - showElement('#fldOfficialRating', context); - showElement('#fldCustomRating', context); - } - - showElement('#tagsCollapsible', context); - - if (item.Type === 'TvChannel') { - hideElement('#metadataSettingsCollapsible', context); - hideElement('#fldPremiereDate', context); - hideElement('#fldDateAdded', context); - hideElement('#fldYear', context); - } else { - showElement('#metadataSettingsCollapsible', context); - showElement('#fldPremiereDate', context); - showElement('#fldDateAdded', context); - showElement('#fldYear', context); - } - - if (item.Type === 'TvChannel') { - hideElement('.overviewContainer', context); - } else { - showElement('.overviewContainer', context); - } - - if (item.Type === 'Person') { - context.querySelector('#txtName').label(globalize.translate('LabelName')); - context.querySelector('#txtSortName').label(globalize.translate('LabelSortName')); - context.querySelector('#txtOriginalName').label(globalize.translate('LabelOriginalName')); - context.querySelector('#txtProductionYear').label(globalize.translate('LabelBirthYear')); - context.querySelector('#txtPremiereDate').label(globalize.translate('LabelBirthDate')); - context.querySelector('#txtEndDate').label(globalize.translate('LabelDeathDate')); - showElement('#fldPlaceOfBirth'); - } else { - context.querySelector('#txtProductionYear').label(globalize.translate('LabelYear')); - context.querySelector('#txtPremiereDate').label(globalize.translate('LabelReleaseDate')); - context.querySelector('#txtEndDate').label(globalize.translate('LabelEndDate')); - hideElement('#fldPlaceOfBirth'); - } - - if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { - showElement('#fldOriginalAspectRatio'); - } else { - hideElement('#fldOriginalAspectRatio'); - } - - if (item.Type === 'Audio' || item.Type === 'Episode' || item.Type === 'Season') { - showElement('#fldIndexNumber'); - - if (item.Type === 'Episode') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelEpisodeNumber')); - } else if (item.Type === 'Season') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === 'Audio') { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelTrackNumber')); - } else { - context.querySelector('#txtIndexNumber').label(globalize.translate('LabelNumber')); - } - } else { - hideElement('#fldIndexNumber'); - } - - if (item.Type === 'Audio' || item.Type === 'Episode') { - showElement('#fldParentIndexNumber'); - - if (item.Type === 'Episode') { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelSeasonNumber')); - } else if (item.Type === 'Audio') { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelDiscNumber')); - } else { - context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelParentNumber')); - } - } else { - hideElement('#fldParentIndexNumber', context); - } - - if (item.Type === 'BoxSet') { - showElement('#fldDisplayOrder', context); - hideElement('.seriesDisplayOrderDescription', context); - - context.querySelector('#selectDisplayOrder').innerHTML = ''; - } else if (item.Type === 'Series') { - showElement('#fldDisplayOrder', context); - showElement('.seriesDisplayOrderDescription', context); - - let html = ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - - context.querySelector('#selectDisplayOrder').innerHTML = html; - } else { - context.querySelector('#selectDisplayOrder').innerHTML = ''; - hideElement('#fldDisplayOrder', context); - } + hideElement('#peopleCollapsible', context); + } else { + showElement('#peopleCollapsible', context); } - function fillItemInfo(context, item, parentalRatingOptions) { - let select = context.querySelector('#selectOfficialRating'); + if (item.Type === 'Person' || item.Type === 'Genre' || item.Type === 'Studio' || item.Type === 'MusicGenre' || item.Type === 'TvChannel') { + hideElement('#fldCommunityRating', context); + hideElement('#genresCollapsible', context); + hideElement('#studiosCollapsible', context); - populateRatings(parentalRatingOptions, select, item.OfficialRating); - - select.value = item.OfficialRating || ''; - - select = context.querySelector('#selectCustomRating'); - - populateRatings(parentalRatingOptions, select, item.CustomRating); - - select.value = item.CustomRating || ''; - - const selectStatus = context.querySelector('#selectStatus'); - populateStatus(selectStatus); - selectStatus.value = item.Status || ''; - - context.querySelector('#select3dFormat', context).value = item.Video3DFormat || ''; - - Array.prototype.forEach.call(context.querySelectorAll('.chkAirDay', context), function (el) { - el.checked = (item.AirDays || []).indexOf(el.getAttribute('data-day')) !== -1; - }); - - populateListView(context.querySelector('#listGenres'), item.Genres); - populatePeople(context, item.People || []); - - populateListView(context.querySelector('#listStudios'), (item.Studios || []).map(function (element) { - return element.Name || ''; - })); - - populateListView(context.querySelector('#listTags'), item.Tags); - - const lockData = (item.LockData || false); - const chkLockData = context.querySelector('#chkLockData'); - chkLockData.checked = lockData; - if (chkLockData.checked) { - hideElement('.providerSettingsContainer', context); + if (item.Type === 'TvChannel') { + showElement('#fldOfficialRating', context); } else { - showElement('.providerSettingsContainer', context); + hideElement('#fldOfficialRating', context); } - fillMetadataSettings(context, item, item.LockedFields); + hideElement('#fldCustomRating', context); + } else { + showElement('#fldCommunityRating', context); + showElement('#genresCollapsible', context); + showElement('#studiosCollapsible', context); + showElement('#fldOfficialRating', context); + showElement('#fldCustomRating', context); + } - context.querySelector('#txtPath').value = item.Path || ''; - context.querySelector('#txtName').value = item.Name || ''; - context.querySelector('#txtOriginalName').value = item.OriginalTitle || ''; - context.querySelector('#txtOverview').value = item.Overview || ''; - context.querySelector('#txtTagline').value = (item.Taglines && item.Taglines.length ? item.Taglines[0] : ''); - context.querySelector('#txtSortName').value = item.ForcedSortName || ''; - context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; + showElement('#tagsCollapsible', context); - context.querySelector('#txtCriticRating').value = item.CriticRating || ''; + if (item.Type === 'TvChannel') { + hideElement('#metadataSettingsCollapsible', context); + hideElement('#fldPremiereDate', context); + hideElement('#fldDateAdded', context); + hideElement('#fldYear', context); + } else { + showElement('#metadataSettingsCollapsible', context); + showElement('#fldPremiereDate', context); + showElement('#fldDateAdded', context); + showElement('#fldYear', context); + } - context.querySelector('#txtIndexNumber').value = item.IndexNumber == null ? '' : item.IndexNumber; - context.querySelector('#txtParentIndexNumber').value = item.ParentIndexNumber == null ? '' : item.ParentIndexNumber; + if (item.Type === 'TvChannel') { + hideElement('.overviewContainer', context); + } else { + showElement('.overviewContainer', context); + } - context.querySelector('#txtAirsBeforeSeason').value = ('AirsBeforeSeasonNumber' in item) ? item.AirsBeforeSeasonNumber : ''; - context.querySelector('#txtAirsAfterSeason').value = ('AirsAfterSeasonNumber' in item) ? item.AirsAfterSeasonNumber : ''; - context.querySelector('#txtAirsBeforeEpisode').value = ('AirsBeforeEpisodeNumber' in item) ? item.AirsBeforeEpisodeNumber : ''; + if (item.Type === 'Person') { + context.querySelector('#txtName').label(globalize.translate('LabelName')); + context.querySelector('#txtSortName').label(globalize.translate('LabelSortName')); + context.querySelector('#txtOriginalName').label(globalize.translate('LabelOriginalName')); + context.querySelector('#txtProductionYear').label(globalize.translate('LabelBirthYear')); + context.querySelector('#txtPremiereDate').label(globalize.translate('LabelBirthDate')); + context.querySelector('#txtEndDate').label(globalize.translate('LabelDeathDate')); + showElement('#fldPlaceOfBirth'); + } else { + context.querySelector('#txtProductionYear').label(globalize.translate('LabelYear')); + context.querySelector('#txtPremiereDate').label(globalize.translate('LabelReleaseDate')); + context.querySelector('#txtEndDate').label(globalize.translate('LabelEndDate')); + hideElement('#fldPlaceOfBirth'); + } - context.querySelector('#txtAlbum').value = item.Album || ''; + if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { + showElement('#fldOriginalAspectRatio'); + } else { + hideElement('#fldOriginalAspectRatio'); + } - context.querySelector('#txtAlbumArtist').value = (item.AlbumArtists || []).map(function (a) { - return a.Name; - }).join(';'); + if (item.Type === 'Audio' || item.Type === 'Episode' || item.Type === 'Season') { + showElement('#fldIndexNumber'); - context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; - - context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { - return a.Name; - }).join(';'); - - let date; - - if (item.DateCreated) { - try { - date = datetime.parseISO8601Date(item.DateCreated, true); - - context.querySelector('#txtDateAdded').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtDateAdded').value = ''; - } + if (item.Type === 'Episode') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelEpisodeNumber')); + } else if (item.Type === 'Season') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelSeasonNumber')); + } else if (item.Type === 'Audio') { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelTrackNumber')); } else { + context.querySelector('#txtIndexNumber').label(globalize.translate('LabelNumber')); + } + } else { + hideElement('#fldIndexNumber'); + } + + if (item.Type === 'Audio' || item.Type === 'Episode') { + showElement('#fldParentIndexNumber'); + + if (item.Type === 'Episode') { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelSeasonNumber')); + } else if (item.Type === 'Audio') { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelDiscNumber')); + } else { + context.querySelector('#txtParentIndexNumber').label(globalize.translate('LabelParentNumber')); + } + } else { + hideElement('#fldParentIndexNumber', context); + } + + if (item.Type === 'BoxSet') { + showElement('#fldDisplayOrder', context); + hideElement('.seriesDisplayOrderDescription', context); + + context.querySelector('#selectDisplayOrder').innerHTML = ''; + } else if (item.Type === 'Series') { + showElement('#fldDisplayOrder', context); + showElement('.seriesDisplayOrderDescription', context); + + let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + + context.querySelector('#selectDisplayOrder').innerHTML = html; + } else { + context.querySelector('#selectDisplayOrder').innerHTML = ''; + hideElement('#fldDisplayOrder', context); + } +} + +function fillItemInfo(context, item, parentalRatingOptions) { + let select = context.querySelector('#selectOfficialRating'); + + populateRatings(parentalRatingOptions, select, item.OfficialRating); + + select.value = item.OfficialRating || ''; + + select = context.querySelector('#selectCustomRating'); + + populateRatings(parentalRatingOptions, select, item.CustomRating); + + select.value = item.CustomRating || ''; + + const selectStatus = context.querySelector('#selectStatus'); + populateStatus(selectStatus); + selectStatus.value = item.Status || ''; + + context.querySelector('#select3dFormat', context).value = item.Video3DFormat || ''; + + Array.prototype.forEach.call(context.querySelectorAll('.chkAirDay', context), function (el) { + el.checked = (item.AirDays || []).indexOf(el.getAttribute('data-day')) !== -1; + }); + + populateListView(context.querySelector('#listGenres'), item.Genres); + populatePeople(context, item.People || []); + + populateListView(context.querySelector('#listStudios'), (item.Studios || []).map(function (element) { + return element.Name || ''; + })); + + populateListView(context.querySelector('#listTags'), item.Tags); + + const lockData = (item.LockData || false); + const chkLockData = context.querySelector('#chkLockData'); + chkLockData.checked = lockData; + if (chkLockData.checked) { + hideElement('.providerSettingsContainer', context); + } else { + showElement('.providerSettingsContainer', context); + } + fillMetadataSettings(context, item, item.LockedFields); + + context.querySelector('#txtPath').value = item.Path || ''; + context.querySelector('#txtName').value = item.Name || ''; + context.querySelector('#txtOriginalName').value = item.OriginalTitle || ''; + context.querySelector('#txtOverview').value = item.Overview || ''; + context.querySelector('#txtTagline').value = (item.Taglines && item.Taglines.length ? item.Taglines[0] : ''); + context.querySelector('#txtSortName').value = item.ForcedSortName || ''; + context.querySelector('#txtCommunityRating').value = item.CommunityRating || ''; + + context.querySelector('#txtCriticRating').value = item.CriticRating || ''; + + context.querySelector('#txtIndexNumber').value = item.IndexNumber == null ? '' : item.IndexNumber; + context.querySelector('#txtParentIndexNumber').value = item.ParentIndexNumber == null ? '' : item.ParentIndexNumber; + + context.querySelector('#txtAirsBeforeSeason').value = ('AirsBeforeSeasonNumber' in item) ? item.AirsBeforeSeasonNumber : ''; + context.querySelector('#txtAirsAfterSeason').value = ('AirsAfterSeasonNumber' in item) ? item.AirsAfterSeasonNumber : ''; + context.querySelector('#txtAirsBeforeEpisode').value = ('AirsBeforeEpisodeNumber' in item) ? item.AirsBeforeEpisodeNumber : ''; + + context.querySelector('#txtAlbum').value = item.Album || ''; + + context.querySelector('#txtAlbumArtist').value = (item.AlbumArtists || []).map(function (a) { + return a.Name; + }).join(';'); + + context.querySelector('#selectDisplayOrder').value = item.DisplayOrder || ''; + + context.querySelector('#txtArtist').value = (item.ArtistItems || []).map(function (a) { + return a.Name; + }).join(';'); + + let date; + + if (item.DateCreated) { + try { + date = datetime.parseISO8601Date(item.DateCreated, true); + + context.querySelector('#txtDateAdded').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtDateAdded').value = ''; } + } else { + context.querySelector('#txtDateAdded').value = ''; + } - if (item.PremiereDate) { - try { - date = datetime.parseISO8601Date(item.PremiereDate, true); + if (item.PremiereDate) { + try { + date = datetime.parseISO8601Date(item.PremiereDate, true); - context.querySelector('#txtPremiereDate').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtPremiereDate').value = ''; - } - } else { + context.querySelector('#txtPremiereDate').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtPremiereDate').value = ''; } + } else { + context.querySelector('#txtPremiereDate').value = ''; + } - if (item.EndDate) { - try { - date = datetime.parseISO8601Date(item.EndDate, true); + if (item.EndDate) { + try { + date = datetime.parseISO8601Date(item.EndDate, true); - context.querySelector('#txtEndDate').value = date.toISOString().slice(0, 10); - } catch (e) { - context.querySelector('#txtEndDate').value = ''; - } - } else { + context.querySelector('#txtEndDate').value = date.toISOString().slice(0, 10); + } catch (e) { context.querySelector('#txtEndDate').value = ''; } + } else { + context.querySelector('#txtEndDate').value = ''; + } - context.querySelector('#txtProductionYear').value = item.ProductionYear || ''; + context.querySelector('#txtProductionYear').value = item.ProductionYear || ''; - context.querySelector('#txtAirTime').value = item.AirTime || ''; + context.querySelector('#txtAirTime').value = item.AirTime || ''; - const placeofBirth = item.ProductionLocations && item.ProductionLocations.length ? item.ProductionLocations[0] : ''; - context.querySelector('#txtPlaceOfBirth').value = placeofBirth; + const placeofBirth = item.ProductionLocations && item.ProductionLocations.length ? item.ProductionLocations[0] : ''; + context.querySelector('#txtPlaceOfBirth').value = placeofBirth; - context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; + context.querySelector('#txtOriginalAspectRatio').value = item.AspectRatio || ''; - context.querySelector('#selectLanguage').value = item.PreferredMetadataLanguage || ''; - context.querySelector('#selectCountry').value = item.PreferredMetadataCountryCode || ''; + context.querySelector('#selectLanguage').value = item.PreferredMetadataLanguage || ''; + context.querySelector('#selectCountry').value = item.PreferredMetadataCountryCode || ''; - if (item.RunTimeTicks) { - const minutes = item.RunTimeTicks / 600000000; + if (item.RunTimeTicks) { + const minutes = item.RunTimeTicks / 600000000; - context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); - } else { - context.querySelector('#txtSeriesRuntime', context).value = ''; + context.querySelector('#txtSeriesRuntime').value = Math.round(minutes); + } else { + context.querySelector('#txtSeriesRuntime', context).value = ''; + } +} + +function populateRatings(allParentalRatings, select, currentValue) { + let html = ''; + + html += ""; + + const ratings = []; + let rating; + + let currentValueFound = false; + + for (let i = 0, length = allParentalRatings.length; i < length; i++) { + rating = allParentalRatings[i]; + + ratings.push({ Name: rating.Name, Value: rating.Name }); + + if (rating.Name === currentValue) { + currentValueFound = true; } } - function populateRatings(allParentalRatings, select, currentValue) { - let html = ''; - - html += ""; - - const ratings = []; - let rating; - - let currentValueFound = false; - - for (let i = 0, length = allParentalRatings.length; i < length; i++) { - rating = allParentalRatings[i]; - - ratings.push({ Name: rating.Name, Value: rating.Name }); - - if (rating.Name === currentValue) { - currentValueFound = true; - } - } - - if (currentValue && !currentValueFound) { - ratings.push({ Name: currentValue, Value: currentValue }); - } - - for (let i = 0, length = ratings.length; i < length; i++) { - rating = ratings[i]; - - html += "'; - } - - select.innerHTML = html; + if (currentValue && !currentValueFound) { + ratings.push({ Name: currentValue, Value: currentValue }); } - function populateStatus(select) { - let html = ''; + for (let i = 0, length = ratings.length; i < length; i++) { + rating = ratings[i]; - html += ""; - html += "'; - html += "'; - select.innerHTML = html; + html += "'; } - function populateListView(list, items, sortCallback) { - items = items || []; - if (typeof (sortCallback) === 'undefined') { - items.sort(function (a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }); - } else { - items = sortCallback(items); - } - let html = ''; - for (let i = 0; i < items.length; i++) { - html += '
'; + select.innerHTML = html; +} - html += ''; +function populateStatus(select) { + let html = ''; - html += '
'; + html += ""; + html += "'; + html += "'; + select.innerHTML = html; +} - html += '
'; - html += escapeHtml(items[i]); - html += '
'; - - html += '
'; - - html += ''; - - html += '
'; - } - - list.innerHTML = html; - } - - function populatePeople(context, people) { - const lastType = ''; - let html = ''; - - const elem = context.querySelector('#peopleList'); - - for (let i = 0, length = people.length; i < length; i++) { - const person = people[i]; - - html += '
'; - - html += ''; - - html += '
'; - html += ''; - html += '
'; - - html += ''; - - html += '
'; - } - - elem.innerHTML = html; - } - - function getLockedFieldsHtml(fields, currentFields) { - let html = ''; - for (let i = 0; i < fields.length; i++) { - const field = fields[i]; - const name = field.name; - const value = field.value || field.name; - const checkedHtml = currentFields.indexOf(value) === -1 ? ' checked' : ''; - html += ''; - } - return html; - } - - function fillMetadataSettings(context, item, lockedFields) { - const container = context.querySelector('.providerSettingsContainer'); - lockedFields = lockedFields || []; - - const lockedFieldsList = [ - { name: globalize.translate('Name'), value: 'Name' }, - { name: globalize.translate('Overview'), value: 'Overview' }, - { name: globalize.translate('Genres'), value: 'Genres' }, - { name: globalize.translate('ParentalRating'), value: 'OfficialRating' }, - { name: globalize.translate('People'), value: 'Cast' } - ]; - - if (item.Type === 'Person') { - lockedFieldsList.push({ name: globalize.translate('BirthLocation'), value: 'ProductionLocations' }); - } else { - lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: 'ProductionLocations' }); - } - - if (item.Type === 'Series') { - lockedFieldsList.push({ name: globalize.translate('Runtime'), value: 'Runtime' }); - } - - lockedFieldsList.push({ name: globalize.translate('Studios'), value: 'Studios' }); - lockedFieldsList.push({ name: globalize.translate('Tags'), value: 'Tags' }); - - let html = ''; - - html += '

' + globalize.translate('HeaderEnabledFields') + '

'; - html += '

' + globalize.translate('HeaderEnabledFieldsHelp') + '

'; - html += getLockedFieldsHtml(lockedFieldsList, lockedFields); - container.innerHTML = html; - } - - function reload(context, itemId, serverId) { - loading.show(); - - Promise.all([getItem(itemId, serverId), getEditorConfig(itemId, serverId)]).then(function (responses) { - const item = responses[0]; - metadataEditorInfo = responses[1]; - - currentItem = item; - - const languages = metadataEditorInfo.Cultures; - const countries = metadataEditorInfo.Countries; - - renderContentTypeOptions(context, metadataEditorInfo); - - loadExternalIds(context, item, metadataEditorInfo.ExternalIdInfos); - - populateLanguages(context.querySelector('#selectLanguage'), languages); - populateCountries(context.querySelector('#selectCountry'), countries); - - setFieldVisibilities(context, item); - fillItemInfo(context, item, metadataEditorInfo.ParentalRatingOptions); - - if (item.MediaType === 'Video' && item.Type !== 'Episode' && item.Type !== 'TvChannel') { - showElement('#fldTagline', context); - } else { - hideElement('#fldTagline', context); - } - - loading.hide(); +function populateListView(list, items, sortCallback) { + items = items || []; + if (typeof (sortCallback) === 'undefined') { + items.sort(function (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); }); + } else { + items = sortCallback(items); + } + let html = ''; + for (let i = 0; i < items.length; i++) { + html += '
'; + + html += ''; + + html += '
'; + + html += '
'; + html += escapeHtml(items[i]); + html += '
'; + + html += '
'; + + html += ''; + + html += '
'; } - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } + list.innerHTML = html; +} - function show(itemId, serverId, resolve) { - loading.show(); +function populatePeople(context, people) { + const lastType = ''; + let html = ''; - const dialogOptions = { - removeOnClose: true, - scrollY: false - }; + const elem = context.querySelector('#peopleList'); - if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + for (let i = 0, length = people.length; i < length; i++) { + const person = people[i]; + + html += '
'; + + html += ''; + + html += '
'; + html += ''; + html += '
'; - dlg.classList.add('formDialog'); + html += ''; - let html = ''; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - dialogHelper.open(dlg); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - resolve(); - }); - - currentContext = dlg; - - init(dlg); - - reload(dlg, itemId, serverId); + html += '
'; } - export default { - show: function (itemId, serverId) { - return new Promise(resolve => show(itemId, serverId, resolve)); - }, + elem.innerHTML = html; +} - embed: function (elem, itemId, serverId) { - return new Promise(function () { - loading.show(); +function getLockedFieldsHtml(fields, currentFields) { + let html = ''; + for (let i = 0; i < fields.length; i++) { + const field = fields[i]; + const name = field.name; + const value = field.value || field.name; + const checkedHtml = currentFields.indexOf(value) === -1 ? ' checked' : ''; + html += ''; + } + return html; +} - elem.innerHTML = globalize.translateHtml(template, 'core'); +function fillMetadataSettings(context, item, lockedFields) { + const container = context.querySelector('.providerSettingsContainer'); + lockedFields = lockedFields || []; - elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter'); - elem.querySelector('.btnClose').classList.add('hide'); - elem.querySelector('.btnHeaderSave').classList.remove('hide'); - elem.querySelector('.btnCancel').classList.add('hide'); + const lockedFieldsList = [ + { name: globalize.translate('Name'), value: 'Name' }, + { name: globalize.translate('Overview'), value: 'Overview' }, + { name: globalize.translate('Genres'), value: 'Genres' }, + { name: globalize.translate('ParentalRating'), value: 'OfficialRating' }, + { name: globalize.translate('People'), value: 'Cast' } + ]; - currentContext = elem; + if (item.Type === 'Person') { + lockedFieldsList.push({ name: globalize.translate('BirthLocation'), value: 'ProductionLocations' }); + } else { + lockedFieldsList.push({ name: globalize.translate('ProductionLocations'), value: 'ProductionLocations' }); + } - init(elem); - reload(elem, itemId, serverId); + if (item.Type === 'Series') { + lockedFieldsList.push({ name: globalize.translate('Runtime'), value: 'Runtime' }); + } - focusManager.autoFocus(elem); - }); + lockedFieldsList.push({ name: globalize.translate('Studios'), value: 'Studios' }); + lockedFieldsList.push({ name: globalize.translate('Tags'), value: 'Tags' }); + + let html = ''; + + html += '

' + globalize.translate('HeaderEnabledFields') + '

'; + html += '

' + globalize.translate('HeaderEnabledFieldsHelp') + '

'; + html += getLockedFieldsHtml(lockedFieldsList, lockedFields); + container.innerHTML = html; +} + +function reload(context, itemId, serverId) { + loading.show(); + + Promise.all([getItem(itemId, serverId), getEditorConfig(itemId, serverId)]).then(function (responses) { + const item = responses[0]; + metadataEditorInfo = responses[1]; + + currentItem = item; + + const languages = metadataEditorInfo.Cultures; + const countries = metadataEditorInfo.Countries; + + renderContentTypeOptions(context, metadataEditorInfo); + + loadExternalIds(context, item, metadataEditorInfo.ExternalIdInfos); + + populateLanguages(context.querySelector('#selectLanguage'), languages); + populateCountries(context.querySelector('#selectCountry'), countries); + + setFieldVisibilities(context, item); + fillItemInfo(context, item, metadataEditorInfo.ParentalRatingOptions); + + if (item.MediaType === 'Video' && item.Type !== 'Episode' && item.Type !== 'TvChannel') { + showElement('#fldTagline', context); + } else { + hideElement('#fldTagline', context); } + + loading.hide(); + }); +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +function show(itemId, serverId, resolve) { + loading.show(); + + const dialogOptions = { + removeOnClose: true, + scrollY: false }; -/* eslint-enable indent */ + if (layoutManager.tv) { + dialogOptions.size = 'fullscreen'; + } else { + dialogOptions.size = 'small'; + } + + const dlg = dialogHelper.createDialog(dialogOptions); + + dlg.classList.add('formDialog'); + + let html = ''; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + dialogHelper.open(dlg); + + dlg.addEventListener('close', function () { + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + resolve(); + }); + + currentContext = dlg; + + init(dlg); + + reload(dlg, itemId, serverId); +} + +export default { + show: function (itemId, serverId) { + return new Promise(resolve => show(itemId, serverId, resolve)); + }, + + embed: function (elem, itemId, serverId) { + return new Promise(function () { + loading.show(); + + elem.innerHTML = globalize.translateHtml(template, 'core'); + + elem.querySelector('.formDialogFooter').classList.remove('formDialogFooter'); + elem.querySelector('.btnClose').classList.add('hide'); + elem.querySelector('.btnHeaderSave').classList.remove('hide'); + elem.querySelector('.btnCancel').classList.add('hide'); + + currentContext = elem; + + init(elem); + reload(elem, itemId, serverId); + + focusManager.autoFocus(elem); + }); + } +}; + diff --git a/src/components/metadataEditor/personEditor.js b/src/components/metadataEditor/personEditor.js index 51f784db4f..4236137197 100644 --- a/src/components/metadataEditor/personEditor.js +++ b/src/components/metadataEditor/personEditor.js @@ -8,94 +8,91 @@ import '../../elements/emby-select/emby-select'; import '../formdialog.scss'; import template from './personEditor.template.html'; -/* eslint-disable indent */ +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); - }); - } +function show(person) { + return new Promise(function (resolve, reject) { + const dialogOptions = { + removeOnClose: true, + scrollY: false + }; - function show(person) { - return new Promise(function (resolve, reject) { - 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 = ''; + let submitted = false; + + html += globalize.translateHtml(template, 'core'); + + dlg.innerHTML = html; + + dlg.querySelector('.txtPersonName', dlg).value = person.Name || ''; + dlg.querySelector('.selectPersonType', dlg).value = person.Type || ''; + dlg.querySelector('.txtPersonRole', dlg).value = person.Role || ''; + + if (layoutManager.tv) { + centerFocus(dlg.querySelector('.formDialogContent'), false, true); + } + + dialogHelper.open(dlg); + + dlg.addEventListener('close', function () { if (layoutManager.tv) { - dialogOptions.size = 'fullscreen'; + centerFocus(dlg.querySelector('.formDialogContent'), false, false); + } + + if (submitted) { + resolve(person); } else { - dialogOptions.size = 'small'; + reject(); } - - const dlg = dialogHelper.createDialog(dialogOptions); - - dlg.classList.add('formDialog'); - - let html = ''; - let submitted = false; - - html += globalize.translateHtml(template, 'core'); - - dlg.innerHTML = html; - - dlg.querySelector('.txtPersonName', dlg).value = person.Name || ''; - dlg.querySelector('.selectPersonType', dlg).value = person.Type || ''; - dlg.querySelector('.txtPersonRole', dlg).value = person.Role || ''; - - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, true); - } - - dialogHelper.open(dlg); - - dlg.addEventListener('close', function () { - if (layoutManager.tv) { - centerFocus(dlg.querySelector('.formDialogContent'), false, false); - } - - if (submitted) { - resolve(person); - } else { - reject(); - } - }); - - dlg.querySelector('.selectPersonType').addEventListener('change', function () { - if (this.value === 'Actor') { - dlg.querySelector('.fldRole').classList.remove('hide'); - } else { - dlg.querySelector('.fldRole').classList.add('hide'); - } - }); - - dlg.querySelector('.btnCancel').addEventListener('click', function () { - dialogHelper.close(dlg); - }); - - dlg.querySelector('form').addEventListener('submit', function (e) { - submitted = true; - - person.Name = dlg.querySelector('.txtPersonName', dlg).value; - person.Type = dlg.querySelector('.selectPersonType', dlg).value; - person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null; - - dialogHelper.close(dlg); - - e.preventDefault(); - return false; - }); - - dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', { - bubbles: true - })); }); - } + + dlg.querySelector('.selectPersonType').addEventListener('change', function () { + if (this.value === 'Actor') { + dlg.querySelector('.fldRole').classList.remove('hide'); + } else { + dlg.querySelector('.fldRole').classList.add('hide'); + } + }); + + dlg.querySelector('.btnCancel').addEventListener('click', function () { + dialogHelper.close(dlg); + }); + + dlg.querySelector('form').addEventListener('submit', function (e) { + submitted = true; + + person.Name = dlg.querySelector('.txtPersonName', dlg).value; + person.Type = dlg.querySelector('.selectPersonType', dlg).value; + person.Role = dlg.querySelector('.txtPersonRole', dlg).value || null; + + dialogHelper.close(dlg); + + e.preventDefault(); + return false; + }); + + dlg.querySelector('.selectPersonType').dispatchEvent(new CustomEvent('change', { + bubbles: true + })); + }); +} export default { show: show }; -/* eslint-enable indent */ diff --git a/src/components/multiSelect/multiSelect.js b/src/components/multiSelect/multiSelect.js index 3984968174..142a31a5d7 100644 --- a/src/components/multiSelect/multiSelect.js +++ b/src/components/multiSelect/multiSelect.js @@ -11,42 +11,373 @@ import confirm from '../confirm/confirm'; import itemHelper from '../itemHelper'; import datetime from '../../scripts/datetime'; -/* eslint-disable indent */ +let selectedItems = []; +let selectedElements = []; +let currentSelectionCommandsPanel; - let selectedItems = []; - let selectedElements = []; - let currentSelectionCommandsPanel; +function hideSelections() { + const selectionCommandsPanel = currentSelectionCommandsPanel; + if (selectionCommandsPanel) { + selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); + currentSelectionCommandsPanel = null; - function hideSelections() { - const selectionCommandsPanel = currentSelectionCommandsPanel; - if (selectionCommandsPanel) { - selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); - currentSelectionCommandsPanel = null; + selectedItems = []; + selectedElements = []; + const elems = document.querySelectorAll('.itemSelectionPanel'); + for (let i = 0, length = elems.length; i < length; i++) { + const parent = elems[i].parentNode; + parent.removeChild(elems[i]); + parent.classList.remove('withMultiSelect'); + } + } +} - selectedItems = []; - selectedElements = []; - const elems = document.querySelectorAll('.itemSelectionPanel'); - for (let i = 0, length = elems.length; i < length; i++) { - const parent = elems[i].parentNode; - parent.removeChild(elems[i]); - parent.classList.remove('withMultiSelect'); +function onItemSelectionPanelClick(e, itemSelectionPanel) { + // toggle the checkbox, if it wasn't clicked on + if (!dom.parentWithClass(e.target, 'chkItemSelect')) { + const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + + if (chkItemSelect) { + if (chkItemSelect.classList.contains('checkedInitial')) { + chkItemSelect.classList.remove('checkedInitial'); + } else { + const newValue = !chkItemSelect.checked; + chkItemSelect.checked = newValue; + updateItemSelection(chkItemSelect, newValue); } } } - function onItemSelectionPanelClick(e, itemSelectionPanel) { - // toggle the checkbox, if it wasn't clicked on - if (!dom.parentWithClass(e.target, 'chkItemSelect')) { - const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + e.preventDefault(); + e.stopPropagation(); + return false; +} - if (chkItemSelect) { - if (chkItemSelect.classList.contains('checkedInitial')) { - chkItemSelect.classList.remove('checkedInitial'); - } else { - const newValue = !chkItemSelect.checked; - chkItemSelect.checked = newValue; - updateItemSelection(chkItemSelect, newValue); - } +function updateItemSelection(chkItemSelect, selected) { + const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); + + if (selected) { + const current = selectedItems.filter(i => { + return i === id; + }); + + if (!current.length) { + selectedItems.push(id); + selectedElements.push(chkItemSelect); + } + } else { + selectedItems = selectedItems.filter(i => { + return i !== id; + }); + selectedElements = selectedElements.filter(i => { + return i !== chkItemSelect; + }); + } + + if (selectedItems.length) { + const itemSelectionCount = document.querySelector('.itemSelectionCount'); + if (itemSelectionCount) { + itemSelectionCount.innerHTML = datetime.toLocaleString(selectedItems.length); + } + } else { + hideSelections(); + } +} + +function onSelectionChange() { + updateItemSelection(this, this.checked); +} + +function showSelection(item, isChecked) { + let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); + + if (!itemSelectionPanel) { + itemSelectionPanel = document.createElement('div'); + itemSelectionPanel.classList.add('itemSelectionPanel'); + + const parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); + parent.classList.add('withMultiSelect'); + parent.appendChild(itemSelectionPanel); + + let cssClass = 'chkItemSelect'; + if (isChecked && !browser.firefox) { + // In firefox, the initial tap hold doesnt' get treated as a click + // In other browsers it does, so we need to make sure that initial click is ignored + cssClass += ' checkedInitial'; + } + const checkedAttribute = isChecked ? ' checked' : ''; + itemSelectionPanel.innerHTML = ``; + const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); + chkItemSelect.addEventListener('change', onSelectionChange); + } +} + +function showSelectionCommands() { + let selectionCommandsPanel = currentSelectionCommandsPanel; + + if (!selectionCommandsPanel) { + selectionCommandsPanel = document.createElement('div'); + selectionCommandsPanel.classList.add('selectionCommandsPanel'); + + document.body.appendChild(selectionCommandsPanel); + currentSelectionCommandsPanel = selectionCommandsPanel; + + let html = ''; + + html += ''; + html += '

'; + + const moreIcon = 'more_vert'; + html += ``; + + selectionCommandsPanel.innerHTML = html; + + selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); + + const btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); + + dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); + } +} + +function alertText(options) { + return new Promise((resolve) => { + alert(options).then(resolve, resolve); + }); +} + +function deleteItems(apiClient, itemIds) { + return new Promise((resolve, reject) => { + let msg = globalize.translate('ConfirmDeleteItem'); + let title = globalize.translate('HeaderDeleteItem'); + + if (itemIds.length > 1) { + msg = globalize.translate('ConfirmDeleteItems'); + title = globalize.translate('HeaderDeleteItems'); + } + + confirm(msg, title).then(() => { + const promises = itemIds.map(itemId => apiClient.deleteItem(itemId)); + + Promise.all(promises).then(resolve, () => { + alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); + }); + }, reject); + }); +} + +function showMenuForSelectedItems(e) { + const apiClient = ServerConnections.currentApiClient(); + + apiClient.getCurrentUser().then(user => { + // get first selected item to perform metadata refresh permission check + apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => { + const menuItems = []; + + menuItems.push({ + name: globalize.translate('SelectAll'), + id: 'selectall', + icon: 'select_all' + }); + + menuItems.push({ + name: globalize.translate('AddToCollection'), + id: 'addtocollection', + icon: 'add' + }); + + menuItems.push({ + name: globalize.translate('AddToPlaylist'), + id: 'playlist', + icon: 'playlist_add' + }); + + // TODO: Be more dynamic based on what is selected + if (user.Policy.EnableContentDeletion) { + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' + }); + } + + if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { + // Disabled because there is no callback for this item + } + + if (user.Policy.IsAdministrator) { + menuItems.push({ + name: globalize.translate('GroupVersions'), + id: 'groupvideos', + icon: 'call_merge' + }); + } + + menuItems.push({ + name: globalize.translate('MarkPlayed'), + id: 'markplayed', + icon: 'check_box' + }); + + menuItems.push({ + name: globalize.translate('MarkUnplayed'), + id: 'markunplayed', + icon: 'check_box_outline_blank' + }); + + // this assues that if the user can refresh metadata for the first item + // they can refresh metadata for all items + if (itemHelper.canRefreshMetadata(firstItem, user)) { + menuItems.push({ + name: globalize.translate('RefreshMetadata'), + id: 'refresh', + icon: 'refresh' + }); + } + + import('../actionSheet/actionSheet').then((actionsheet) => { + actionsheet.show({ + items: menuItems, + positionTo: e.target, + callback: function (id) { + const items = selectedItems.slice(0); + const serverId = apiClient.serverInfo().Id; + + switch (id) { + case 'selectall': + { + const elems = document.querySelectorAll('.itemSelectionPanel'); + for (let i = 0, length = elems.length; i < length; i++) { + const chkItemSelect = elems[i].querySelector('.chkItemSelect'); + + if (chkItemSelect && !chkItemSelect.classList.contains('checkedInitial') && !chkItemSelect.checked && chkItemSelect.getBoundingClientRect().width != 0) { + chkItemSelect.checked = true; + updateItemSelection(chkItemSelect, true); + } + } + } + break; + case 'addtocollection': + import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: items, + serverId: serverId + }); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'playlist': + new playlistEditor({ + items: items, + serverId: serverId + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'delete': + deleteItems(apiClient, items).then(dispatchNeedsRefresh); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'groupvideos': + combineVersions(apiClient, items); + break; + case 'markplayed': + items.forEach(itemId => { + apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'markunplayed': + items.forEach(itemId => { + apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + case 'refresh': + import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: items, + serverId: serverId + }).show(); + }); + hideSelections(); + dispatchNeedsRefresh(); + break; + default: + break; + } + } + }); + }); + }); + }); +} + +function dispatchNeedsRefresh() { + const elems = []; + + [].forEach.call(selectedElements, i => { + const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); + + if (container && !elems.includes(container)) { + elems.push(container); + } + }); + + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].notifyRefreshNeeded(true); + } +} + +function combineVersions(apiClient, selection) { + if (selection.length < 2) { + alert({ + text: globalize.translate('PleaseSelectTwoItems') + }); + + return; + } + + loading.show(); + + apiClient.ajax({ + + type: 'POST', + url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) + + }).then(() => { + loading.hide(); + hideSelections(); + dispatchNeedsRefresh(); + }); +} + +function showSelections(initialCard) { + import('../../elements/emby-checkbox/emby-checkbox').then(() => { + const cards = document.querySelectorAll('.card'); + for (let i = 0, length = cards.length; i < length; i++) { + showSelection(cards[i], initialCard === cards[i]); + } + + showSelectionCommands(); + updateItemSelection(initialCard, true); + }); +} + +function onContainerClick(e) { + const target = e.target; + + if (selectedItems.length) { + const card = dom.parentWithClass(target, 'card'); + if (card) { + const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); + if (itemSelectionPanel) { + return onItemSelectionPanelClick(e, itemSelectionPanel); } } @@ -54,523 +385,182 @@ import datetime from '../../scripts/datetime'; e.stopPropagation(); return false; } +} - function updateItemSelection(chkItemSelect, selected) { - const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); +document.addEventListener('viewbeforehide', hideSelections); - if (selected) { - const current = selectedItems.filter(i => { - return i === id; - }); +export default function (options) { + const self = this; - if (!current.length) { - selectedItems.push(id); - selectedElements.push(chkItemSelect); - } - } else { - selectedItems = selectedItems.filter(i => { - return i !== id; - }); - selectedElements = selectedElements.filter(i => { - return i !== chkItemSelect; - }); + const container = options.container; + + function onTapHold(e) { + const card = dom.parentWithClass(e.target, 'card'); + + if (card) { + showSelections(card); } - if (selectedItems.length) { - const itemSelectionCount = document.querySelector('.itemSelectionCount'); - if (itemSelectionCount) { - itemSelectionCount.innerHTML = datetime.toLocaleString(selectedItems.length); - } - } else { - hideSelections(); + e.preventDefault(); + // It won't have this if it's a hammer event + if (e.stopPropagation) { + e.stopPropagation(); } + return false; } - function onSelectionChange() { - updateItemSelection(this, this.checked); + function getTouches(e) { + return e.changedTouches || e.targetTouches || e.touches; } - function showSelection(item, isChecked) { - let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); + let touchTarget; + let touchStartTimeout; + let touchStartX; + let touchStartY; + function onTouchStart(e) { + const touch = getTouches(e)[0]; + touchTarget = null; + touchStartX = 0; + touchStartY = 0; - if (!itemSelectionPanel) { - itemSelectionPanel = document.createElement('div'); - itemSelectionPanel.classList.add('itemSelectionPanel'); + if (touch) { + touchStartX = touch.clientX; + touchStartY = touch.clientY; + const element = touch.target; - const parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); - parent.classList.add('withMultiSelect'); - parent.appendChild(itemSelectionPanel); + if (element) { + const card = dom.parentWithClass(element, 'card'); - let cssClass = 'chkItemSelect'; - if (isChecked && !browser.firefox) { - // In firefox, the initial tap hold doesnt' get treated as a click - // In other browsers it does, so we need to make sure that initial click is ignored - cssClass += ' checkedInitial'; - } - const checkedAttribute = isChecked ? ' checked' : ''; - itemSelectionPanel.innerHTML = ``; - const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); - chkItemSelect.addEventListener('change', onSelectionChange); - } - } + if (card) { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } - function showSelectionCommands() { - let selectionCommandsPanel = currentSelectionCommandsPanel; - - if (!selectionCommandsPanel) { - selectionCommandsPanel = document.createElement('div'); - selectionCommandsPanel.classList.add('selectionCommandsPanel'); - - document.body.appendChild(selectionCommandsPanel); - currentSelectionCommandsPanel = selectionCommandsPanel; - - let html = ''; - - html += ''; - html += '

'; - - const moreIcon = 'more_vert'; - html += ``; - - selectionCommandsPanel.innerHTML = html; - - selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); - - const btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); - - dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); - } - } - - function alertText(options) { - return new Promise((resolve) => { - alert(options).then(resolve, resolve); - }); - } - - function deleteItems(apiClient, itemIds) { - return new Promise((resolve, reject) => { - let msg = globalize.translate('ConfirmDeleteItem'); - let title = globalize.translate('HeaderDeleteItem'); - - if (itemIds.length > 1) { - msg = globalize.translate('ConfirmDeleteItems'); - title = globalize.translate('HeaderDeleteItems'); - } - - confirm(msg, title).then(() => { - const promises = itemIds.map(itemId => apiClient.deleteItem(itemId)); - - Promise.all(promises).then(resolve, () => { - alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); - }); - }, reject); - }); - } - - function showMenuForSelectedItems(e) { - const apiClient = ServerConnections.currentApiClient(); - - apiClient.getCurrentUser().then(user => { - // get first selected item to perform metadata refresh permission check - apiClient.getItem(apiClient.getCurrentUserId(), selectedItems[0]).then(firstItem => { - const menuItems = []; - - menuItems.push({ - name: globalize.translate('SelectAll'), - id: 'selectall', - icon: 'select_all' - }); - - menuItems.push({ - name: globalize.translate('AddToCollection'), - id: 'addtocollection', - icon: 'add' - }); - - menuItems.push({ - name: globalize.translate('AddToPlaylist'), - id: 'playlist', - icon: 'playlist_add' - }); - - // TODO: Be more dynamic based on what is selected - if (user.Policy.EnableContentDeletion) { - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); + touchTarget = card; + touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); } - - if (user.Policy.EnableContentDownloading && appHost.supports('filedownload')) { - // Disabled because there is no callback for this item - /* - menuItems.push({ - name: globalize.translate('Download'), - id: 'download', - icon: 'file_download' - }); - */ - } - - if (user.Policy.IsAdministrator) { - menuItems.push({ - name: globalize.translate('GroupVersions'), - id: 'groupvideos', - icon: 'call_merge' - }); - } - - menuItems.push({ - name: globalize.translate('MarkPlayed'), - id: 'markplayed', - icon: 'check_box' - }); - - menuItems.push({ - name: globalize.translate('MarkUnplayed'), - id: 'markunplayed', - icon: 'check_box_outline_blank' - }); - - // this assues that if the user can refresh metadata for the first item - // they can refresh metadata for all items - if (itemHelper.canRefreshMetadata(firstItem, user)) { - menuItems.push({ - name: globalize.translate('RefreshMetadata'), - id: 'refresh', - icon: 'refresh' - }); - } - - import('../actionSheet/actionSheet').then((actionsheet) => { - actionsheet.show({ - items: menuItems, - positionTo: e.target, - callback: function (id) { - const items = selectedItems.slice(0); - const serverId = apiClient.serverInfo().Id; - - switch (id) { - case 'selectall': - { - const elems = document.querySelectorAll('.itemSelectionPanel'); - for (let i = 0, length = elems.length; i < length; i++) { - const chkItemSelect = elems[i].querySelector('.chkItemSelect'); - - if (chkItemSelect && !chkItemSelect.classList.contains('checkedInitial') && !chkItemSelect.checked && chkItemSelect.getBoundingClientRect().width != 0) { - chkItemSelect.checked = true; - updateItemSelection(chkItemSelect, true); - } - } - } - break; - case 'addtocollection': - import('../collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { - const collectionEditor = new CollectionEditor(); - collectionEditor.show({ - items: items, - serverId: serverId - }); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'playlist': - new playlistEditor({ - items: items, - serverId: serverId - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'delete': - deleteItems(apiClient, items).then(dispatchNeedsRefresh); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'groupvideos': - combineVersions(apiClient, items); - break; - case 'markplayed': - items.forEach(itemId => { - apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'markunplayed': - items.forEach(itemId => { - apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - case 'refresh': - import('../refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: items, - serverId: serverId - }).show(); - }); - hideSelections(); - dispatchNeedsRefresh(); - break; - default: - break; - } - } - }); - }); - }); - }); - } - - function dispatchNeedsRefresh() { - const elems = []; - - [].forEach.call(selectedElements, i => { - const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); - - if (container && !elems.includes(container)) { - elems.push(container); } - }); - - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].notifyRefreshNeeded(true); } } - function combineVersions(apiClient, selection) { - if (selection.length < 2) { - alert({ - text: globalize.translate('PleaseSelectTwoItems') - }); + function onTouchMove(e) { + if (touchTarget) { + const touch = getTouches(e)[0]; + let deltaX; + let deltaY; + if (touch) { + const touchEndX = touch.clientX || 0; + const touchEndY = touch.clientY || 0; + deltaX = Math.abs(touchEndX - (touchStartX || 0)); + deltaY = Math.abs(touchEndY - (touchStartY || 0)); + } else { + deltaX = 100; + deltaY = 100; + } + if (deltaX >= 5 || deltaY >= 5) { + onMouseOut(); + } + } + } + + function onTouchEnd() { + onMouseOut(); + } + + function onMouseDown(e) { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } + + touchTarget = e.target; + touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); + } + + function onMouseOut() { + if (touchStartTimeout) { + clearTimeout(touchStartTimeout); + touchStartTimeout = null; + } + touchTarget = null; + } + + function onTouchStartTimerFired() { + if (!touchTarget) { return; } - loading.show(); + const card = dom.parentWithClass(touchTarget, 'card'); + touchTarget = null; - apiClient.ajax({ + if (card) { + showSelections(card); + } + } - type: 'POST', - url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) + function initTapHold(element) { + // mobile safari doesn't allow contextmenu override + if (browser.touch && !browser.safari) { + element.addEventListener('contextmenu', onTapHold); + } else { + dom.addEventListener(element, 'touchstart', onTouchStart, { + passive: true + }); + dom.addEventListener(element, 'touchmove', onTouchMove, { + passive: true + }); + dom.addEventListener(element, 'touchend', onTouchEnd, { + passive: true + }); + dom.addEventListener(element, 'touchcancel', onTouchEnd, { + passive: true + }); + dom.addEventListener(element, 'mousedown', onMouseDown, { + passive: true + }); + dom.addEventListener(element, 'mouseleave', onMouseOut, { + passive: true + }); + dom.addEventListener(element, 'mouseup', onMouseOut, { + passive: true + }); + } + } - }).then(() => { - loading.hide(); - hideSelections(); - dispatchNeedsRefresh(); + initTapHold(container); + + if (options.bindOnClick !== false) { + container.addEventListener('click', onContainerClick); + } + + self.onContainerClick = onContainerClick; + + self.destroy = () => { + container.removeEventListener('click', onContainerClick); + container.removeEventListener('contextmenu', onTapHold); + + const element = container; + + dom.removeEventListener(element, 'touchstart', onTouchStart, { + passive: true }); - } - - function showSelections(initialCard) { - import('../../elements/emby-checkbox/emby-checkbox').then(() => { - const cards = document.querySelectorAll('.card'); - for (let i = 0, length = cards.length; i < length; i++) { - showSelection(cards[i], initialCard === cards[i]); - } - - showSelectionCommands(); - updateItemSelection(initialCard, true); + dom.removeEventListener(element, 'touchmove', onTouchMove, { + passive: true }); - } + dom.removeEventListener(element, 'touchend', onTouchEnd, { + passive: true + }); + dom.removeEventListener(element, 'mousedown', onMouseDown, { + passive: true + }); + dom.removeEventListener(element, 'mouseleave', onMouseOut, { + passive: true + }); + dom.removeEventListener(element, 'mouseup', onMouseOut, { + passive: true + }); + }; +} - function onContainerClick(e) { - const target = e.target; - - if (selectedItems.length) { - const card = dom.parentWithClass(target, 'card'); - if (card) { - const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); - if (itemSelectionPanel) { - return onItemSelectionPanelClick(e, itemSelectionPanel); - } - } - - e.preventDefault(); - e.stopPropagation(); - return false; - } - } - - document.addEventListener('viewbeforehide', hideSelections); - - export default function (options) { - const self = this; - - const container = options.container; - - function onTapHold(e) { - const card = dom.parentWithClass(e.target, 'card'); - - if (card) { - showSelections(card); - } - - e.preventDefault(); - // It won't have this if it's a hammer event - if (e.stopPropagation) { - e.stopPropagation(); - } - return false; - } - - function getTouches(e) { - return e.changedTouches || e.targetTouches || e.touches; - } - - let touchTarget; - let touchStartTimeout; - let touchStartX; - let touchStartY; - function onTouchStart(e) { - const touch = getTouches(e)[0]; - touchTarget = null; - touchStartX = 0; - touchStartY = 0; - - if (touch) { - touchStartX = touch.clientX; - touchStartY = touch.clientY; - const element = touch.target; - - if (element) { - const card = dom.parentWithClass(element, 'card'); - - if (card) { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - - touchTarget = card; - touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); - } - } - } - } - - function onTouchMove(e) { - if (touchTarget) { - const touch = getTouches(e)[0]; - let deltaX; - let deltaY; - - if (touch) { - const touchEndX = touch.clientX || 0; - const touchEndY = touch.clientY || 0; - deltaX = Math.abs(touchEndX - (touchStartX || 0)); - deltaY = Math.abs(touchEndY - (touchStartY || 0)); - } else { - deltaX = 100; - deltaY = 100; - } - if (deltaX >= 5 || deltaY >= 5) { - onMouseOut(); - } - } - } - - function onTouchEnd() { - onMouseOut(); - } - - function onMouseDown(e) { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - - touchTarget = e.target; - touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); - } - - function onMouseOut() { - if (touchStartTimeout) { - clearTimeout(touchStartTimeout); - touchStartTimeout = null; - } - touchTarget = null; - } - - function onTouchStartTimerFired() { - if (!touchTarget) { - return; - } - - const card = dom.parentWithClass(touchTarget, 'card'); - touchTarget = null; - - if (card) { - showSelections(card); - } - } - - function initTapHold(element) { - // mobile safari doesn't allow contextmenu override - if (browser.touch && !browser.safari) { - element.addEventListener('contextmenu', onTapHold); - } else { - dom.addEventListener(element, 'touchstart', onTouchStart, { - passive: true - }); - dom.addEventListener(element, 'touchmove', onTouchMove, { - passive: true - }); - dom.addEventListener(element, 'touchend', onTouchEnd, { - passive: true - }); - dom.addEventListener(element, 'touchcancel', onTouchEnd, { - passive: true - }); - dom.addEventListener(element, 'mousedown', onMouseDown, { - passive: true - }); - dom.addEventListener(element, 'mouseleave', onMouseOut, { - passive: true - }); - dom.addEventListener(element, 'mouseup', onMouseOut, { - passive: true - }); - } - } - - initTapHold(container); - - if (options.bindOnClick !== false) { - container.addEventListener('click', onContainerClick); - } - - self.onContainerClick = onContainerClick; - - self.destroy = () => { - container.removeEventListener('click', onContainerClick); - container.removeEventListener('contextmenu', onTapHold); - - const element = container; - - dom.removeEventListener(element, 'touchstart', onTouchStart, { - passive: true - }); - dom.removeEventListener(element, 'touchmove', onTouchMove, { - passive: true - }); - dom.removeEventListener(element, 'touchend', onTouchEnd, { - passive: true - }); - dom.removeEventListener(element, 'mousedown', onMouseDown, { - passive: true - }); - dom.removeEventListener(element, 'mouseleave', onMouseOut, { - passive: true - }); - dom.removeEventListener(element, 'mouseup', onMouseOut, { - passive: true - }); - }; - } - -/* eslint-enable indent */ diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index 16c8312867..2d11691e95 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -2,6 +2,7 @@ import serverNotifications from '../../scripts/serverNotifications'; import { playbackManager } from '../playback/playbackmanager'; import Events from '../../utils/events.ts'; import globalize from '../../scripts/globalize'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; import NotificationIcon from './notificationicon.png'; @@ -130,7 +131,7 @@ function onLibraryChanged(data, apiClient) { newItems.length = 12; } - apiClient.getItems(apiClient.getCurrentUserId(), { + getItems(apiClient, apiClient.getCurrentUserId(), { Recursive: true, Limit: 3, diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index 8ec7103fe3..b2c892e85c 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -17,780 +17,780 @@ import './nowPlayingBar.scss'; import '../../elements/emby-slider/emby-slider'; import { appRouter } from '../appRouter'; -/* eslint-disable indent */ +let currentPlayer; +let currentPlayerSupportedCommands = []; - let currentPlayer; - let currentPlayerSupportedCommands = []; +let currentTimeElement; +let nowPlayingImageElement; +let nowPlayingImageUrl; +let nowPlayingTextElement; +let nowPlayingUserData; +let muteButton; +let volumeSlider; +let volumeSliderContainer; +let playPauseButtons; +let positionSlider; +let toggleAirPlayButton; +let toggleRepeatButton; +let toggleRepeatButtonIcon; - let currentTimeElement; - let nowPlayingImageElement; - let nowPlayingImageUrl; - let nowPlayingTextElement; - let nowPlayingUserData; - let muteButton; - let volumeSlider; - let volumeSliderContainer; - let playPauseButtons; - let positionSlider; - let toggleAirPlayButton; - let toggleRepeatButton; - let toggleRepeatButtonIcon; +let lastUpdateTime = 0; +let lastPlayerState = {}; +let isEnabled; +let currentRuntimeTicks = 0; - let lastUpdateTime = 0; - let lastPlayerState = {}; - let isEnabled; - let currentRuntimeTicks = 0; +let isVisibilityAllowed = true; - let isVisibilityAllowed = true; +function getNowPlayingBarHtml() { + let html = ''; - function getNowPlayingBarHtml() { - let html = ''; + html += '
'; - html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; - html += '
'; - html += '
'; - html += ''; - html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; + // The onclicks are needed due to the return false above + html += '
'; - // The onclicks are needed due to the return false above - html += '
'; + html += ''; - html += ''; + html += ''; - html += ''; + html += ''; + if (!layoutManager.mobile) { + html += ''; + } - html += ''; - if (!layoutManager.mobile) { - html += ''; + html += '
'; + html += '
'; + + html += '
'; + + html += ''; + + html += '
'; + html += ''; + html += '
'; + + html += ''; + + html += ''; + html += ''; + + html += '
'; + html += '
'; + + html += ''; + if (layoutManager.mobile) { + html += ''; + } else { + html += ''; + } + + html += '
'; + html += '
'; + + html += '
'; + + return html; +} + +function onSlideDownComplete() { + this.classList.add('hide'); +} + +function slideDown(elem) { + // trigger reflow + void elem.offsetWidth; + + elem.classList.add('nowPlayingBar-hidden'); + + dom.addEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { + once: true + }); +} + +function slideUp(elem) { + dom.removeEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { + once: true + }); + + elem.classList.remove('hide'); + + // trigger reflow + void elem.offsetWidth; + + elem.classList.remove('nowPlayingBar-hidden'); +} + +function onPlayPauseClick() { + playbackManager.playPause(currentPlayer); +} + +function bindEvents(elem) { + currentTimeElement = elem.querySelector('.nowPlayingBarCurrentTime'); + nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); + nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); + nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons'); + positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); + muteButton = elem.querySelector('.muteButton'); + playPauseButtons = elem.querySelectorAll('.playPauseButton'); + toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); + volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); + volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); + + muteButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleMute(currentPlayer); } + }); - html += '
'; - html += '
'; - - html += '
'; - - html += ''; - - html += '
'; - html += ''; - html += '
'; - - html += ''; - - html += ''; - html += ''; - - html += '
'; - html += '
'; - - html += ''; - if (layoutManager.mobile) { - html += ''; - } else { - html += ''; + elem.querySelector('.stopButton').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.stop(currentPlayer); } + }); - html += '
'; - html += '
'; + playPauseButtons.forEach((button) => { + button.addEventListener('click', onPlayPauseClick); + }); - html += '
'; + elem.querySelector('.nextTrackButton').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.nextTrack(currentPlayer); + } + }); - return html; - } - - function onSlideDownComplete() { - this.classList.add('hide'); - } - - function slideDown(elem) { - // trigger reflow - void elem.offsetWidth; - - elem.classList.add('nowPlayingBar-hidden'); - - dom.addEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { - once: true - }); - } - - function slideUp(elem) { - dom.removeEventListener(elem, dom.whichTransitionEvent(), onSlideDownComplete, { - once: true - }); - - elem.classList.remove('hide'); - - // trigger reflow - void elem.offsetWidth; - - elem.classList.remove('nowPlayingBar-hidden'); - } - - function onPlayPauseClick() { - playbackManager.playPause(currentPlayer); - } - - function bindEvents(elem) { - currentTimeElement = elem.querySelector('.nowPlayingBarCurrentTime'); - nowPlayingImageElement = elem.querySelector('.nowPlayingImage'); - nowPlayingTextElement = elem.querySelector('.nowPlayingBarText'); - nowPlayingUserData = elem.querySelector('.nowPlayingBarUserDataButtons'); - positionSlider = elem.querySelector('.nowPlayingBarPositionSlider'); - muteButton = elem.querySelector('.muteButton'); - playPauseButtons = elem.querySelectorAll('.playPauseButton'); - toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); - volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); - volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); - - muteButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleMute(currentPlayer); - } - }); - - elem.querySelector('.stopButton').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.stop(currentPlayer); - } - }); - - playPauseButtons.forEach((button) => { - button.addEventListener('click', onPlayPauseClick); - }); - - elem.querySelector('.nextTrackButton').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.nextTrack(currentPlayer); - } - }); - - elem.querySelector('.previousTrackButton').addEventListener('click', function (e) { - if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { - // Cancel this event if doubleclick is fired - if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { - return; - } + elem.querySelector('.previousTrackButton').addEventListener('click', function (e) { + if (currentPlayer) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') { + // Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event + if (e.detail > 1 ) { + return; + } + // Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move + // to the previous track, unless we are at the first track so no previous track exists. + if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) { playbackManager.seekPercent(0, currentPlayer); // This is done automatically by playbackManager, however, setting this here gives instant visual feedback. // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. positionSlider.value = 0; - } else { - playbackManager.previousTrack(currentPlayer); + return; } } - }); + playbackManager.previousTrack(currentPlayer); + } + }); - elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () { - if (currentPlayer) { - playbackManager.previousTrack(currentPlayer); - } - }); + elem.querySelector('.previousTrackButton').addEventListener('dblclick', function () { + if (currentPlayer) { + playbackManager.previousTrack(currentPlayer); + } + }); - toggleAirPlayButton = elem.querySelector('.btnAirPlay'); - toggleAirPlayButton.addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleAirPlay(currentPlayer); - } - }); + toggleAirPlayButton = elem.querySelector('.btnAirPlay'); + toggleAirPlayButton.addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleAirPlay(currentPlayer); + } + }); - elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { - if (currentPlayer) { - playbackManager.toggleQueueShuffleMode(); - } - }); + elem.querySelector('.btnShuffleQueue').addEventListener('click', function () { + if (currentPlayer) { + playbackManager.toggleQueueShuffleMode(); + } + }); - toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); - toggleRepeatButton.addEventListener('click', function () { - switch (playbackManager.getRepeatMode()) { - case 'RepeatAll': - playbackManager.setRepeatMode('RepeatOne'); - break; - case 'RepeatOne': - playbackManager.setRepeatMode('RepeatNone'); - break; - case 'RepeatNone': - playbackManager.setRepeatMode('RepeatAll'); - } - }); + toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); + toggleRepeatButton.addEventListener('click', function () { + switch (playbackManager.getRepeatMode()) { + case 'RepeatAll': + playbackManager.setRepeatMode('RepeatOne'); + break; + case 'RepeatOne': + playbackManager.setRepeatMode('RepeatNone'); + break; + case 'RepeatNone': + playbackManager.setRepeatMode('RepeatAll'); + } + }); - toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); + toggleRepeatButtonIcon = toggleRepeatButton.querySelector('.material-icons'); - volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); + volumeSliderContainer.classList.toggle('hide', appHost.supports('physicalvolumecontrol')); - volumeSlider.addEventListener('input', (e) => { - if (currentPlayer) { - currentPlayer.setVolume(e.target.value); - } - }); + volumeSlider.addEventListener('input', (e) => { + if (currentPlayer) { + currentPlayer.setVolume(e.target.value); + } + }); - positionSlider.addEventListener('change', function () { - if (currentPlayer) { - const newPercent = parseFloat(this.value); + positionSlider.addEventListener('change', function () { + if (currentPlayer) { + const newPercent = parseFloat(this.value); - playbackManager.seekPercent(newPercent, currentPlayer); - } - }); + playbackManager.seekPercent(newPercent, currentPlayer); + } + }); - positionSlider.getBubbleText = function (value) { - const state = lastPlayerState; + positionSlider.getBubbleText = function (value) { + const state = lastPlayerState; - if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { - return '--:--'; - } - - let ticks = currentRuntimeTicks; - ticks /= 100; - ticks *= value; - - return datetime.getDisplayRunningTime(ticks); - }; - - elem.addEventListener('click', function (e) { - if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { - showRemoteControl(); - } - }); - } - - function showRemoteControl() { - appRouter.showNowPlaying(); - } - - let nowPlayingBarElement; - function getNowPlayingBar() { - if (nowPlayingBarElement) { - return nowPlayingBarElement; + if (!state || !state.NowPlayingItem || !currentRuntimeTicks) { + return '--:--'; } - const parentContainer = appFooter.element; - nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + let ticks = currentRuntimeTicks; + ticks /= 100; + ticks *= value; - if (nowPlayingBarElement) { - return nowPlayingBarElement; + return datetime.getDisplayRunningTime(ticks); + }; + + elem.addEventListener('click', function (e) { + if (!dom.parentWithTag(e.target, ['BUTTON', 'INPUT'])) { + showRemoteControl(); } + }); +} - parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); - window.CustomElements.upgradeSubtree(parentContainer); - - nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); - - if (layoutManager.mobile) { - hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue')); - hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter')); - } - - if (browser.safari && browser.slow) { - // Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc. - nowPlayingBarElement.classList.add('noMediaProgress'); - } - - itemShortcuts.on(nowPlayingBarElement); - - bindEvents(nowPlayingBarElement); +function showRemoteControl() { + appRouter.showNowPlaying(); +} +let nowPlayingBarElement; +function getNowPlayingBar() { + if (nowPlayingBarElement) { return nowPlayingBarElement; } - function showButton(button) { - button.classList.remove('hide'); + const parentContainer = appFooter.element; + nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + + if (nowPlayingBarElement) { + return nowPlayingBarElement; } - function hideButton(button) { - button.classList.add('hide'); + parentContainer.insertAdjacentHTML('afterbegin', getNowPlayingBarHtml()); + window.CustomElements.upgradeSubtree(parentContainer); + + nowPlayingBarElement = parentContainer.querySelector('.nowPlayingBar'); + + if (layoutManager.mobile) { + hideButton(nowPlayingBarElement.querySelector('.btnShuffleQueue')); + hideButton(nowPlayingBarElement.querySelector('.nowPlayingBarCenter')); } - function updatePlayPauseState(isPaused) { - if (playPauseButtons) { - playPauseButtons.forEach((button) => { - const icon = button.querySelector('.material-icons'); - icon.classList.remove('play_arrow', 'pause'); - icon.classList.add(isPaused ? 'play_arrow' : 'pause'); - }); - } + if (browser.safari && browser.slow) { + // Not handled well here. The wrong elements receive events, bar doesn't update quickly enough, etc. + nowPlayingBarElement.classList.add('noMediaProgress'); } - function updatePlayerStateInternal(event, state, player) { - showNowPlayingBar(); + itemShortcuts.on(nowPlayingBarElement); - lastPlayerState = state; + bindEvents(nowPlayingBarElement); - const playerInfo = playbackManager.getPlayerInfo(); + return nowPlayingBarElement; +} - const playState = state.PlayState || {}; +function showButton(button) { + button.classList.remove('hide'); +} - updatePlayPauseState(playState.IsPaused); +function hideButton(button) { + button.classList.add('hide'); +} - const supportedCommands = playerInfo.supportedCommands; - currentPlayerSupportedCommands = supportedCommands; +function updatePlayPauseState(isPaused) { + if (playPauseButtons) { + playPauseButtons.forEach((button) => { + const icon = button.querySelector('.material-icons'); + icon.classList.remove('play_arrow', 'pause'); + icon.classList.add(isPaused ? 'play_arrow' : 'pause'); + }); + } +} - if (supportedCommands.indexOf('SetRepeatMode') === -1) { - toggleRepeatButton.classList.add('hide'); +function updatePlayerStateInternal(event, state, player) { + showNowPlayingBar(); + + lastPlayerState = state; + + const playerInfo = playbackManager.getPlayerInfo(); + + const playState = state.PlayState || {}; + + updatePlayPauseState(playState.IsPaused); + + const supportedCommands = playerInfo.supportedCommands; + currentPlayerSupportedCommands = supportedCommands; + + if (supportedCommands.indexOf('SetRepeatMode') === -1) { + toggleRepeatButton.classList.add('hide'); + } else { + toggleRepeatButton.classList.remove('hide'); + } + + const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1; + toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton); + + updateRepeatModeDisplay(playbackManager.getRepeatMode()); + onQueueShuffleModeChange(); + + updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); + + if (positionSlider && !positionSlider.dragging) { + positionSlider.disabled = !playState.CanSeek; + + // determines if both forward and backward buffer progress will be visible + const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; + positionSlider.setIsClear(isProgressClear); + } + + const nowPlayingItem = state.NowPlayingItem || {}; + updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); + + updateNowPlayingInfo(state); +} + +function updateRepeatModeDisplay(repeatMode) { + toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one'); + const cssClass = 'buttonActive'; + + switch (repeatMode) { + case 'RepeatAll': + toggleRepeatButtonIcon.classList.add('repeat'); + toggleRepeatButton.classList.add(cssClass); + break; + case 'RepeatOne': + toggleRepeatButtonIcon.classList.add('repeat_one'); + toggleRepeatButton.classList.add(cssClass); + break; + case 'RepeatNone': + default: + toggleRepeatButtonIcon.classList.add('repeat'); + toggleRepeatButton.classList.remove(cssClass); + break; + } +} + +function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) { + // See bindEvents for why this is necessary + if (positionSlider && !positionSlider.dragging) { + if (runtimeTicks) { + let pct = positionTicks / runtimeTicks; + pct *= 100; + + positionSlider.value = pct; } else { - toggleRepeatButton.classList.remove('hide'); - } - - const hideAirPlayButton = supportedCommands.indexOf('AirPlay') === -1; - toggleAirPlayButton.classList.toggle('hide', hideAirPlayButton); - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - onQueueShuffleModeChange(); - - updatePlayerVolumeState(playState.IsMuted, playState.VolumeLevel); - - if (positionSlider && !positionSlider.dragging) { - positionSlider.disabled = !playState.CanSeek; - - // determines if both forward and backward buffer progress will be visible - const isProgressClear = state.MediaSource && state.MediaSource.RunTimeTicks == null; - positionSlider.setIsClear(isProgressClear); - } - - const nowPlayingItem = state.NowPlayingItem || {}; - updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); - - updateNowPlayingInfo(state); - } - - function updateRepeatModeDisplay(repeatMode) { - toggleRepeatButtonIcon.classList.remove('repeat', 'repeat_one'); - const cssClass = 'buttonActive'; - - switch (repeatMode) { - case 'RepeatAll': - toggleRepeatButtonIcon.classList.add('repeat'); - toggleRepeatButton.classList.add(cssClass); - break; - case 'RepeatOne': - toggleRepeatButtonIcon.classList.add('repeat_one'); - toggleRepeatButton.classList.add(cssClass); - break; - case 'RepeatNone': - default: - toggleRepeatButtonIcon.classList.add('repeat'); - toggleRepeatButton.classList.remove(cssClass); - break; + positionSlider.value = 0; } } - function updateTimeDisplay(positionTicks, runtimeTicks, bufferedRanges) { - // See bindEvents for why this is necessary - if (positionSlider && !positionSlider.dragging) { - if (runtimeTicks) { - let pct = positionTicks / runtimeTicks; - pct *= 100; - - positionSlider.value = pct; - } else { - positionSlider.value = 0; - } - } - - if (positionSlider) { - positionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); - } - - if (currentTimeElement) { - let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); - if (runtimeTicks) { - timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); - } - - currentTimeElement.innerHTML = timeText; - } + if (positionSlider) { + positionSlider.setBufferedRanges(bufferedRanges, runtimeTicks, positionTicks); } - function updatePlayerVolumeState(isMuted, volumeLevel) { - const supportedCommands = currentPlayerSupportedCommands; - - let showMuteButton = true; - let showVolumeSlider = true; - - if (supportedCommands.indexOf('ToggleMute') === -1) { - showMuteButton = false; + if (currentTimeElement) { + let timeText = positionTicks == null ? '--:--' : datetime.getDisplayRunningTime(positionTicks); + if (runtimeTicks) { + timeText += ' / ' + datetime.getDisplayRunningTime(runtimeTicks); } - const muteButtonIcon = muteButton.querySelector('.material-icons'); - muteButtonIcon.classList.remove('volume_off', 'volume_up'); - muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up'); + currentTimeElement.innerHTML = timeText; + } +} - if (supportedCommands.indexOf('SetVolume') === -1) { - showVolumeSlider = false; - } +function updatePlayerVolumeState(isMuted, volumeLevel) { + const supportedCommands = currentPlayerSupportedCommands; - if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { - showMuteButton = false; - showVolumeSlider = false; - } + let showMuteButton = true; + let showVolumeSlider = true; - if (showMuteButton) { - showButton(muteButton); - } else { - hideButton(muteButton); - } - - // See bindEvents for why this is necessary - if (volumeSlider) { - volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); - - if (!volumeSlider.dragging) { - volumeSlider.value = volumeLevel || 0; - } - } + if (supportedCommands.indexOf('ToggleMute') === -1) { + showMuteButton = false; } - function seriesImageUrl(item, options) { - if (!item) { - throw new Error('item cannot be null!'); + const muteButtonIcon = muteButton.querySelector('.material-icons'); + muteButtonIcon.classList.remove('volume_off', 'volume_up'); + muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up'); + + if (supportedCommands.indexOf('SetVolume') === -1) { + showVolumeSlider = false; + } + + if (currentPlayer.isLocalPlayer && appHost.supports('physicalvolumecontrol')) { + showMuteButton = false; + showVolumeSlider = false; + } + + if (showMuteButton) { + showButton(muteButton); + } else { + hideButton(muteButton); + } + + // See bindEvents for why this is necessary + if (volumeSlider) { + volumeSliderContainer.classList.toggle('hide', !showVolumeSlider); + + if (!volumeSlider.dragging) { + volumeSlider.value = volumeLevel || 0; } + } +} - if (item.Type !== 'Episode') { - return null; - } +function seriesImageUrl(item, options) { + if (!item) { + throw new Error('item cannot be null!'); + } - options = options || {}; - options.type = options.type || 'Primary'; + if (item.Type !== 'Episode') { + return null; + } - if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + options = options || {}; + options.type = options.type || 'Primary'; + + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } + + if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } + if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; - if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } - if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } - - return null; } - function imageUrl(item, options) { - if (!item) { - throw new Error('item cannot be null!'); - } + return null; +} - options = options || {}; - options.type = options.type || 'Primary'; - - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); - } - - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); - } - - return null; +function imageUrl(item, options) { + if (!item) { + throw new Error('item cannot be null!'); } - function updateNowPlayingInfo(state) { - const nowPlayingItem = state.NowPlayingItem; + options = options || {}; + options.type = options.type || 'Primary'; - const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; - nowPlayingTextElement.innerHTML = ''; - if (textLines) { - const itemText = document.createElement('div'); - const secondaryText = document.createElement('div'); - secondaryText.classList.add('nowPlayingBarSecondaryText'); - if (textLines.length > 1) { - textLines[1].secondary = true; - if (textLines[1].text) { - const text = document.createElement('a'); - text.innerText = textLines[1].text; - secondaryText.appendChild(text); - } - } + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.PrimaryImageItemId || item.Id, options); + } - if (textLines[0].text) { + if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function updateNowPlayingInfo(state) { + const nowPlayingItem = state.NowPlayingItem; + + const textLines = nowPlayingItem ? nowPlayingHelper.getNowPlayingNames(nowPlayingItem) : []; + nowPlayingTextElement.innerHTML = ''; + if (textLines) { + const itemText = document.createElement('div'); + const secondaryText = document.createElement('div'); + secondaryText.classList.add('nowPlayingBarSecondaryText'); + if (textLines.length > 1) { + textLines[1].secondary = true; + if (textLines[1].text) { const text = document.createElement('a'); - text.innerText = textLines[0].text; - itemText.appendChild(text); - } - nowPlayingTextElement.appendChild(itemText); - nowPlayingTextElement.appendChild(secondaryText); - } - - const imgHeight = 70; - - const url = nowPlayingItem ? (seriesImageUrl(nowPlayingItem, { - height: imgHeight - }) || imageUrl(nowPlayingItem, { - height: imgHeight - })) : null; - - if (url !== nowPlayingImageUrl) { - if (url) { - nowPlayingImageUrl = url; - imageLoader.lazyImage(nowPlayingImageElement, nowPlayingImageUrl); - nowPlayingImageElement.style.display = null; - nowPlayingTextElement.style.marginLeft = null; - } else { - nowPlayingImageUrl = null; - nowPlayingImageElement.style.backgroundImage = ''; - nowPlayingImageElement.style.display = 'none'; - nowPlayingTextElement.style.marginLeft = '1em'; + text.innerText = textLines[1].text; + secondaryText.appendChild(text); } } - if (nowPlayingItem.Id) { - const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId); - apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { - const userData = item.UserData || {}; - const likes = userData.Likes == null ? '' : userData.Likes; - if (!layoutManager.mobile) { - let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - // We remove the previous event listener by replacing the item in each update event - const contextButtonClone = contextButton.cloneNode(true); - contextButton.parentNode.replaceChild(contextButtonClone, contextButton); - contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); - const options = { - play: false, - queue: false, - stopPlayback: true, - clearQueue: true, - positionTo: contextButton - }; - apiClient.getCurrentUser().then(function (user) { - contextButton.addEventListener('click', function () { - itemContextMenu.show(Object.assign({ - item: item, - user: user - }, options)); - }); + if (textLines[0].text) { + const text = document.createElement('a'); + text.innerText = textLines[0].text; + itemText.appendChild(text); + } + nowPlayingTextElement.appendChild(itemText); + nowPlayingTextElement.appendChild(secondaryText); + } + + const imgHeight = 70; + + const url = nowPlayingItem ? (seriesImageUrl(nowPlayingItem, { + height: imgHeight + }) || imageUrl(nowPlayingItem, { + height: imgHeight + })) : null; + + if (url !== nowPlayingImageUrl) { + if (url) { + nowPlayingImageUrl = url; + imageLoader.lazyImage(nowPlayingImageElement, nowPlayingImageUrl); + nowPlayingImageElement.style.display = null; + nowPlayingTextElement.style.marginLeft = null; + } else { + nowPlayingImageUrl = null; + nowPlayingImageElement.style.backgroundImage = ''; + nowPlayingImageElement.style.display = 'none'; + nowPlayingTextElement.style.marginLeft = '1em'; + } + } + + if (nowPlayingItem.Id) { + const apiClient = ServerConnections.getApiClient(nowPlayingItem.ServerId); + apiClient.getItem(apiClient.getCurrentUserId(), nowPlayingItem.Id).then(function (item) { + const userData = item.UserData || {}; + const likes = userData.Likes == null ? '' : userData.Likes; + if (!layoutManager.mobile) { + let contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + // We remove the previous event listener by replacing the item in each update event + const contextButtonClone = contextButton.cloneNode(true); + contextButton.parentNode.replaceChild(contextButtonClone, contextButton); + contextButton = nowPlayingBarElement.querySelector('.btnToggleContextMenu'); + const options = { + play: false, + queue: false, + stopPlayback: true, + clearQueue: true, + positionTo: contextButton + }; + apiClient.getCurrentUser().then(function (user) { + contextButton.addEventListener('click', function () { + itemContextMenu.show(Object.assign({ + item: item, + user: user + }, options)); }); - } - nowPlayingUserData.innerHTML = ''; - }); - } else { - nowPlayingUserData.innerHTML = ''; - } - } - - function onPlaybackStart(e, state) { - console.debug('nowplaying event: ' + e.type); - const player = this; - onStateChanged.call(player, e, state); - } - - function onRepeatModeChange() { - if (!isEnabled) { - return; - } - - updateRepeatModeDisplay(playbackManager.getRepeatMode()); - } - - function onQueueShuffleModeChange() { - if (!isEnabled) { - return; - } - - const shuffleMode = playbackManager.getQueueShuffleMode(); - const context = nowPlayingBarElement; - const cssClass = 'buttonActive'; - const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); - switch (shuffleMode) { - case 'Shuffle': - toggleShuffleButton.classList.add(cssClass); - break; - case 'Sorted': - default: - toggleShuffleButton.classList.remove(cssClass); - break; - } - } - - function showNowPlayingBar() { - if (!isVisibilityAllowed) { - hideNowPlayingBar(); - return; - } - - slideUp(getNowPlayingBar()); - } - - function hideNowPlayingBar() { - isEnabled = false; - - // Use a timeout to prevent the bar from hiding and showing quickly - // in the event of a stop->play command - - // Don't call getNowPlayingBar here because we don't want to end up creating it just to hide it - const elem = document.getElementsByClassName('nowPlayingBar')[0]; - if (elem) { - slideDown(elem); - } - } - - function onPlaybackStopped(e, state) { - console.debug('nowplaying event: ' + e.type); - const player = this; - - if (player.isLocalPlayer) { - if (state.NextMediaType !== 'Audio') { - hideNowPlayingBar(); + }); } - } else { - if (!state.NextMediaType) { - hideNowPlayingBar(); - } - } + nowPlayingUserData.innerHTML = ''; + }); + } else { + nowPlayingUserData.innerHTML = ''; + } +} + +function onPlaybackStart(e, state) { + console.debug('nowplaying event: ' + e.type); + const player = this; + onStateChanged.call(player, e, state); +} + +function onRepeatModeChange() { + if (!isEnabled) { + return; } - function onPlayPauseStateChanged() { - if (!isEnabled) { - return; - } + updateRepeatModeDisplay(playbackManager.getRepeatMode()); +} - const player = this; - updatePlayPauseState(player.paused()); +function onQueueShuffleModeChange() { + if (!isEnabled) { + return; } - function onStateChanged(event, state) { - if (event.type === 'init') { - // skip non-ready state - return; - } + const shuffleMode = playbackManager.getQueueShuffleMode(); + const context = nowPlayingBarElement; + const cssClass = 'buttonActive'; + const toggleShuffleButton = context.querySelector('.btnShuffleQueue'); + switch (shuffleMode) { + case 'Shuffle': + toggleShuffleButton.classList.add(cssClass); + break; + case 'Sorted': + default: + toggleShuffleButton.classList.remove(cssClass); + break; + } +} - console.debug('nowplaying event: ' + event.type); - const player = this; +function showNowPlayingBar() { + if (!isVisibilityAllowed) { + hideNowPlayingBar(); + return; + } - if (!state.NowPlayingItem || layoutManager.tv || state.IsFullscreen === false) { + slideUp(getNowPlayingBar()); +} + +function hideNowPlayingBar() { + isEnabled = false; + + // Use a timeout to prevent the bar from hiding and showing quickly + // in the event of a stop->play command + + // Don't call getNowPlayingBar here because we don't want to end up creating it just to hide it + const elem = document.getElementsByClassName('nowPlayingBar')[0]; + if (elem) { + slideDown(elem); + } +} + +function onPlaybackStopped(e, state) { + console.debug('nowplaying event: ' + e.type); + const player = this; + + if (player.isLocalPlayer) { + if (state.NextMediaType !== 'Audio') { hideNowPlayingBar(); - return; } - - if (player.isLocalPlayer && state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + } else { + if (!state.NextMediaType) { hideNowPlayingBar(); - return; } + } +} - isEnabled = true; +function onPlayPauseStateChanged() { + if (!isEnabled) { + return; + } - if (nowPlayingBarElement) { - updatePlayerStateInternal(event, state, player); - return; - } + const player = this; + updatePlayPauseState(player.paused()); +} - getNowPlayingBar(); +function onStateChanged(event, state) { + if (event.type === 'init') { + // skip non-ready state + return; + } + + console.debug('nowplaying event: ' + event.type); + const player = this; + + if (!state.NowPlayingItem || layoutManager.tv || state.IsFullscreen === false) { + hideNowPlayingBar(); + return; + } + + if (player.isLocalPlayer && state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + hideNowPlayingBar(); + return; + } + + isEnabled = true; + + if (nowPlayingBarElement) { updatePlayerStateInternal(event, state, player); + return; } - function onTimeUpdate() { - if (!isEnabled) { - return; - } + getNowPlayingBar(); + updatePlayerStateInternal(event, state, player); +} - // Try to avoid hammering the document with changes - const now = new Date().getTime(); - if ((now - lastUpdateTime) < 700) { - return; - } - lastUpdateTime = now; - - const player = this; - currentRuntimeTicks = playbackManager.duration(player); - updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks, playbackManager.getBufferedRanges(player)); +function onTimeUpdate() { + if (!isEnabled) { + return; } - function releaseCurrentPlayer() { - const player = currentPlayer; + // Try to avoid hammering the document with changes + const now = new Date().getTime(); + if ((now - lastUpdateTime) < 700) { + return; + } + lastUpdateTime = now; - if (player) { - Events.off(player, 'playbackstart', onPlaybackStart); - Events.off(player, 'statechange', onPlaybackStart); - Events.off(player, 'repeatmodechange', onRepeatModeChange); - Events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange); - Events.off(player, 'playbackstop', onPlaybackStopped); - Events.off(player, 'volumechange', onVolumeChanged); - Events.off(player, 'pause', onPlayPauseStateChanged); - Events.off(player, 'unpause', onPlayPauseStateChanged); - Events.off(player, 'timeupdate', onTimeUpdate); + const player = this; + currentRuntimeTicks = playbackManager.duration(player); + updateTimeDisplay(playbackManager.currentTime(player) * 10000, currentRuntimeTicks, playbackManager.getBufferedRanges(player)); +} - currentPlayer = null; +function releaseCurrentPlayer() { + const player = currentPlayer; + + if (player) { + Events.off(player, 'playbackstart', onPlaybackStart); + Events.off(player, 'statechange', onPlaybackStart); + Events.off(player, 'repeatmodechange', onRepeatModeChange); + Events.off(player, 'shufflequeuemodechange', onQueueShuffleModeChange); + Events.off(player, 'playbackstop', onPlaybackStopped); + Events.off(player, 'volumechange', onVolumeChanged); + Events.off(player, 'pause', onPlayPauseStateChanged); + Events.off(player, 'unpause', onPlayPauseStateChanged); + Events.off(player, 'timeupdate', onTimeUpdate); + + currentPlayer = null; + hideNowPlayingBar(); + } +} + +function onVolumeChanged() { + if (!isEnabled) { + return; + } + + const player = this; + + updatePlayerVolumeState(player.isMuted(), player.getVolume()); +} + +function refreshFromPlayer(player, type) { + const state = playbackManager.getPlayerState(player); + + onStateChanged.call(player, { type }, state); +} + +function bindToPlayer(player) { + if (player === currentPlayer) { + return; + } + + releaseCurrentPlayer(); + + currentPlayer = player; + + if (!player) { + return; + } + + refreshFromPlayer(player, 'init'); + + Events.on(player, 'playbackstart', onPlaybackStart); + Events.on(player, 'statechange', onPlaybackStart); + Events.on(player, 'repeatmodechange', onRepeatModeChange); + Events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange); + Events.on(player, 'playbackstop', onPlaybackStopped); + Events.on(player, 'volumechange', onVolumeChanged); + Events.on(player, 'pause', onPlayPauseStateChanged); + Events.on(player, 'unpause', onPlayPauseStateChanged); + Events.on(player, 'timeupdate', onTimeUpdate); +} + +Events.on(playbackManager, 'playerchange', function () { + bindToPlayer(playbackManager.getCurrentPlayer()); +}); + +bindToPlayer(playbackManager.getCurrentPlayer()); + +document.addEventListener('viewbeforeshow', function (e) { + if (!e.detail.options.enableMediaControl) { + if (isVisibilityAllowed) { + isVisibilityAllowed = false; + hideNowPlayingBar(); + } + } else if (!isVisibilityAllowed) { + isVisibilityAllowed = true; + if (currentPlayer) { + refreshFromPlayer(currentPlayer, 'refresh'); + } else { hideNowPlayingBar(); } } - - function onVolumeChanged() { - if (!isEnabled) { - return; - } - - const player = this; - - updatePlayerVolumeState(player.isMuted(), player.getVolume()); - } - - function refreshFromPlayer(player, type) { - const state = playbackManager.getPlayerState(player); - - onStateChanged.call(player, { type }, state); - } - - function bindToPlayer(player) { - if (player === currentPlayer) { - return; - } - - releaseCurrentPlayer(); - - currentPlayer = player; - - if (!player) { - return; - } - - refreshFromPlayer(player, 'init'); - - Events.on(player, 'playbackstart', onPlaybackStart); - Events.on(player, 'statechange', onPlaybackStart); - Events.on(player, 'repeatmodechange', onRepeatModeChange); - Events.on(player, 'shufflequeuemodechange', onQueueShuffleModeChange); - Events.on(player, 'playbackstop', onPlaybackStopped); - Events.on(player, 'volumechange', onVolumeChanged); - Events.on(player, 'pause', onPlayPauseStateChanged); - Events.on(player, 'unpause', onPlayPauseStateChanged); - Events.on(player, 'timeupdate', onTimeUpdate); - } - - Events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); - }); - - bindToPlayer(playbackManager.getCurrentPlayer()); - - document.addEventListener('viewbeforeshow', function (e) { - if (!e.detail.options.enableMediaControl) { - if (isVisibilityAllowed) { - isVisibilityAllowed = false; - hideNowPlayingBar(); - } - } else if (!isVisibilityAllowed) { - isVisibilityAllowed = true; - if (currentPlayer) { - refreshFromPlayer(currentPlayer, 'refresh'); - } else { - hideNowPlayingBar(); - } - } - }); - -/* eslint-enable indent */ +}); \ No newline at end of file diff --git a/src/components/packageManager.js b/src/components/packageManager.js index 3890577b24..b4b7edcf72 100644 --- a/src/components/packageManager.js +++ b/src/components/packageManager.js @@ -1,18 +1,17 @@ import appSettings from '../scripts/settings/appSettings'; import { pluginManager } from './pluginManager'; -/* eslint-disable indent */ - class PackageManager { - #packagesList = []; - #settingsKey = 'installedpackages1'; +class PackageManager { + #packagesList = []; + #settingsKey = 'installedpackages1'; - init() { - console.groupCollapsed('loading packages'); - const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + init() { + console.groupCollapsed('loading packages'); + const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - return Promise.all(manifestUrls.map((url) => { - return this.loadPackage(url); - })) + return Promise.all(manifestUrls.map((url) => { + return this.loadPackage(url); + })) .then(() => { console.debug('finished loading packages'); return Promise.resolve(); @@ -22,119 +21,117 @@ import { pluginManager } from './pluginManager'; }).finally(() => { console.groupEnd('loading packages'); }); - } - - get packages() { - return this.#packagesList.slice(0); - } - - install(url) { - return this.loadPackage(url, true).then((pkg) => { - const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - - if (!manifestUrls.includes(url)) { - manifestUrls.push(url); - appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); - } - - return pkg; - }); - } - - uninstall(name) { - const pkg = this.#packagesList.filter((p) => { - return p.name === name; - })[0]; - - if (pkg) { - this.#packagesList = this.#packagesList.filter((p) => { - return p.name !== name; - }); - - this.removeUrl(pkg.url); - } - - return Promise.resolve(); - } - - mapPath(pkg, pluginUrl) { - const urlLower = pluginUrl.toLowerCase(); - if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) { - return pluginUrl; - } - - let packageUrl = pkg.url; - packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); - - packageUrl += '/'; - packageUrl += pluginUrl; - - return packageUrl; - } - - addPackage(pkg) { - this.#packagesList = this.#packagesList.filter((p) => { - return p.name !== pkg.name; - }); - - this.#packagesList.push(pkg); - } - - removeUrl(url) { - let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); - - manifestUrls = manifestUrls.filter((i) => { - return i !== url; - }); - - appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); - } - - loadPackage(url, throwError = false) { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - const originalUrl = url; - url += url.indexOf('?') === -1 ? '?' : '&'; - url += 't=' + new Date().getTime(); - - xhr.open('GET', url, true); - - const onError = () => { - if (throwError === true) { - reject(); - } else { - this.removeUrl(originalUrl); - resolve(); - } - }; - - xhr.onload = () => { - if (this.status < 400) { - const pkg = JSON.parse(this.response); - pkg.url = originalUrl; - - this.addPackage(pkg); - - const plugins = pkg.plugins || []; - if (pkg.plugin) { - plugins.push(pkg.plugin); - } - const promises = plugins.map((pluginUrl) => { - return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl)); - }); - Promise.all(promises).then(resolve, resolve); - } else { - onError(); - } - }; - - xhr.onerror = onError; - - xhr.send(); - }); - } } -/* eslint-enable indent */ + get packages() { + return this.#packagesList.slice(0); + } + + install(url) { + return this.loadPackage(url, true).then((pkg) => { + const manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + + if (!manifestUrls.includes(url)) { + manifestUrls.push(url); + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); + } + + return pkg; + }); + } + + uninstall(name) { + const pkg = this.#packagesList.filter((p) => { + return p.name === name; + })[0]; + + if (pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== name; + }); + + this.removeUrl(pkg.url); + } + + return Promise.resolve(); + } + + mapPath(pkg, pluginUrl) { + const urlLower = pluginUrl.toLowerCase(); + if (urlLower.startsWith('http:') || urlLower.startsWith('https:') || urlLower.startsWith('file:')) { + return pluginUrl; + } + + let packageUrl = pkg.url; + packageUrl = packageUrl.substring(0, packageUrl.lastIndexOf('/')); + + packageUrl += '/'; + packageUrl += pluginUrl; + + return packageUrl; + } + + addPackage(pkg) { + this.#packagesList = this.#packagesList.filter((p) => { + return p.name !== pkg.name; + }); + + this.#packagesList.push(pkg); + } + + removeUrl(url) { + let manifestUrls = JSON.parse(appSettings.get(this.#settingsKey) || '[]'); + + manifestUrls = manifestUrls.filter((i) => { + return i !== url; + }); + + appSettings.set(this.#settingsKey, JSON.stringify(manifestUrls)); + } + + loadPackage(url, throwError = false) { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + const originalUrl = url; + url += url.indexOf('?') === -1 ? '?' : '&'; + url += 't=' + new Date().getTime(); + + xhr.open('GET', url, true); + + const onError = () => { + if (throwError === true) { + reject(); + } else { + this.removeUrl(originalUrl); + resolve(); + } + }; + + xhr.onload = () => { + if (this.status < 400) { + const pkg = JSON.parse(this.response); + pkg.url = originalUrl; + + this.addPackage(pkg); + + const plugins = pkg.plugins || []; + if (pkg.plugin) { + plugins.push(pkg.plugin); + } + const promises = plugins.map((pluginUrl) => { + return pluginManager.loadPlugin(this.mapPath(pkg, pluginUrl)); + }); + Promise.all(promises).then(resolve, resolve); + } else { + onError(); + } + }; + + xhr.onerror = onError; + + xhr.send(); + }); + } +} export default new PackageManager(); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 6c560c222d..3bfe44e37d 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -4,259 +4,256 @@ import Events from '../../utils/events.ts'; import ServerConnections from '../ServerConnections'; import shell from '../../scripts/shell'; -/* eslint-disable indent */ +// Reports media playback to the device for lock screen control - // Reports media playback to the device for lock screen control +let currentPlayer; - let currentPlayer; +function seriesImageUrl(item, options = {}) { + options.type = options.type || 'Primary'; - function seriesImageUrl(item, options = {}) { - options.type = options.type || 'Primary'; + if (item.Type !== 'Episode') { + return null; + } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; - if (item.Type !== 'Episode') { - return null; - } else if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); + } else if (options.type === 'Thumb') { + if (item.SeriesThumbImageTag) { + options.tag = item.SeriesThumbImageTag; return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } else if (options.type === 'Thumb') { - if (item.SeriesThumbImageTag) { - options.tag = item.SeriesThumbImageTag; + } else if (item.ParentThumbImageTag) { + options.tag = item.ParentThumbImageTag; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } else if (item.ParentThumbImageTag) { - options.tag = item.ParentThumbImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); - } + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.ParentThumbItemId, options); } + } + return null; +} + +function imageUrl(item, options = {}) { + options.type = options.type || 'Primary'; + + if (item.ImageTags && item.ImageTags[options.type]) { + options.tag = item.ImageTags[options.type]; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); + } else if (item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + } + + return null; +} + +function getImageUrl(item, imageOptions = {}) { + const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); + + if (url) { + const height = imageOptions.height || imageOptions.maxHeight; + + return { + src: url, + sizes: height + 'x' + height + }; + } else { return null; } +} - function imageUrl(item, options = {}) { - options.type = options.type || 'Primary'; +function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) { + const list = []; - if (item.ImageTags && item.ImageTags[options.type]) { - options.tag = item.ImageTags[options.type]; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.Id, options); - } else if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.AlbumId, options); + imageSizes.forEach((size) => { + const url = getImageUrl(item, { height: size }); + if (url !== null) { + list.push(url); } + }); - return null; + return list; +} + +function updatePlayerState(player, state, eventName) { + // Don't go crazy reporting position changes + if (eventName === 'timeupdate') { + // Only report if this item hasn't been reported yet, or if there's an actual playback change. + // Don't report on simple time updates + return; } - function getImageUrl(item, imageOptions = {}) { - const url = seriesImageUrl(item, imageOptions) || imageUrl(item, imageOptions); + const item = state.NowPlayingItem; - if (url) { - const height = imageOptions.height || imageOptions.maxHeight; - - return { - src: url, - sizes: height + 'x' + height - }; - } else { - return null; - } - } - - function getImageUrls(item, imageSizes = [96, 128, 192, 256, 384, 512]) { - const list = []; - - imageSizes.forEach((size) => { - const url = getImageUrl(item, { height: size }); - if (url !== null) { - list.push(url); - } - }); - - return list; - } - - function updatePlayerState(player, state, eventName) { - // Don't go crazy reporting position changes - if (eventName === 'timeupdate') { - // Only report if this item hasn't been reported yet, or if there's an actual playback change. - // Don't report on simple time updates - return; - } - - const item = state.NowPlayingItem; - - if (!item) { - hideMediaControls(); - return; - } - - if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate - eventName = 'timeupdate'; - } - - const isVideo = item.MediaType === 'Video'; - const isLocalPlayer = player.isLocalPlayer || false; - - // Local players do their own notifications - if (isLocalPlayer && isVideo) { - return; - } - - const playState = state.PlayState || {}; - const parts = nowPlayingHelper.getNowPlayingNames(item); - const artist = parts[parts.length - 1].text; - const title = parts.length === 1 ? '' : parts[0].text; - - const album = item.Album || ''; - const itemId = item.Id; - - // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); - const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10); - - const isPaused = playState.IsPaused || false; - const canSeek = playState.CanSeek || false; - - if ('mediaSession' in navigator) { - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.metadata = new MediaMetadata({ - title: title, - artist: artist, - album: album, - artwork: getImageUrls(item) - }); - } else { - const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); - shell.updateMediaSession({ - action: eventName, - isLocalPlayer: isLocalPlayer, - itemId: itemId, - title: title, - artist: artist, - album: album, - duration: duration, - position: currentTime, - imageUrl: itemImageUrl, - canSeek: canSeek, - isPaused: isPaused - }); - } - } - - function onGeneralEvent(e) { - const state = playbackManager.getPlayerState(this); - - updatePlayerState(this, state, e.type); - } - - function onStateChanged(e, state) { - updatePlayerState(this, state, 'statechange'); - } - - function onPlaybackStart(e, state) { - updatePlayerState(this, state, e.type); - } - - function onPlaybackStopped() { + if (!item) { hideMediaControls(); + return; } - function releaseCurrentPlayer() { - if (currentPlayer) { - Events.off(currentPlayer, 'playbackstart', onPlaybackStart); - Events.off(currentPlayer, 'playbackstop', onPlaybackStopped); - Events.off(currentPlayer, 'unpause', onGeneralEvent); - Events.off(currentPlayer, 'pause', onGeneralEvent); - Events.off(currentPlayer, 'statechange', onStateChanged); - Events.off(currentPlayer, 'timeupdate', onGeneralEvent); - - currentPlayer = null; - - hideMediaControls(); - } + if (eventName === 'init') { // transform "init" event into "timeupdate" to restraint update rate + eventName = 'timeupdate'; } - function hideMediaControls() { - if ('mediaSession' in navigator) { - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.metadata = null; - } else { - shell.hideMediaSession(); - } + const isVideo = item.MediaType === 'Video'; + const isLocalPlayer = player.isLocalPlayer || false; + + // Local players do their own notifications + if (isLocalPlayer && isVideo) { + return; } - function bindToPlayer(player) { - releaseCurrentPlayer(); + const playState = state.PlayState || {}; + const parts = nowPlayingHelper.getNowPlayingNames(item); + const artist = parts[parts.length - 1].text; + const title = parts.length === 1 ? '' : parts[0].text; - if (!player) { - return; - } + const album = item.Album || ''; + const itemId = item.Id; - currentPlayer = player; + // Convert to ms + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); + const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10); - const state = playbackManager.getPlayerState(player); - updatePlayerState(player, state, 'init'); - - Events.on(currentPlayer, 'playbackstart', onPlaybackStart); - Events.on(currentPlayer, 'playbackstop', onPlaybackStopped); - Events.on(currentPlayer, 'unpause', onGeneralEvent); - Events.on(currentPlayer, 'pause', onGeneralEvent); - Events.on(currentPlayer, 'statechange', onStateChanged); - Events.on(currentPlayer, 'timeupdate', onGeneralEvent); - } - - function execute(name) { - playbackManager[name](currentPlayer); - } + const isPaused = playState.IsPaused || false; + const canSeek = playState.CanSeek || false; if ('mediaSession' in navigator) { /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('previoustrack', function () { - execute('previousTrack'); + navigator.mediaSession.metadata = new MediaMetadata({ + title: title, + artist: artist, + album: album, + artwork: getImageUrls(item) }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('nexttrack', function () { - execute('nextTrack'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('play', function () { - execute('unpause'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('pause', function () { - execute('pause'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekbackward', function () { - execute('rewind'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekforward', function () { - execute('fastForward'); - }); - - /* eslint-disable-next-line compat/compat */ - navigator.mediaSession.setActionHandler('seekto', function (object) { - const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; - // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); - const wantedTime = object.seekTime * 1000; - playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + } else { + const itemImageUrl = seriesImageUrl(item, { maxHeight: 3000 }) || imageUrl(item, { maxHeight: 3000 }); + shell.updateMediaSession({ + action: eventName, + isLocalPlayer: isLocalPlayer, + itemId: itemId, + title: title, + artist: artist, + album: album, + duration: duration, + position: currentTime, + imageUrl: itemImageUrl, + canSeek: canSeek, + isPaused: isPaused }); } +} - Events.on(playbackManager, 'playerchange', function () { - bindToPlayer(playbackManager.getCurrentPlayer()); +function onGeneralEvent(e) { + const state = playbackManager.getPlayerState(this); + + updatePlayerState(this, state, e.type); +} + +function onStateChanged(e, state) { + updatePlayerState(this, state, 'statechange'); +} + +function onPlaybackStart(e, state) { + updatePlayerState(this, state, e.type); +} + +function onPlaybackStopped() { + hideMediaControls(); +} + +function releaseCurrentPlayer() { + if (currentPlayer) { + Events.off(currentPlayer, 'playbackstart', onPlaybackStart); + Events.off(currentPlayer, 'playbackstop', onPlaybackStopped); + Events.off(currentPlayer, 'unpause', onGeneralEvent); + Events.off(currentPlayer, 'pause', onGeneralEvent); + Events.off(currentPlayer, 'statechange', onStateChanged); + Events.off(currentPlayer, 'timeupdate', onGeneralEvent); + + currentPlayer = null; + + hideMediaControls(); + } +} + +function hideMediaControls() { + if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.metadata = null; + } else { + shell.hideMediaSession(); + } +} + +function bindToPlayer(player) { + releaseCurrentPlayer(); + + if (!player) { + return; + } + + currentPlayer = player; + + const state = playbackManager.getPlayerState(player); + updatePlayerState(player, state, 'init'); + + Events.on(currentPlayer, 'playbackstart', onPlaybackStart); + Events.on(currentPlayer, 'playbackstop', onPlaybackStopped); + Events.on(currentPlayer, 'unpause', onGeneralEvent); + Events.on(currentPlayer, 'pause', onGeneralEvent); + Events.on(currentPlayer, 'statechange', onStateChanged); + Events.on(currentPlayer, 'timeupdate', onGeneralEvent); +} + +function execute(name) { + playbackManager[name](currentPlayer); +} + +if ('mediaSession' in navigator) { + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('previoustrack', function () { + execute('previousTrack'); }); - bindToPlayer(playbackManager.getCurrentPlayer()); + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('nexttrack', function () { + execute('nextTrack'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('play', function () { + execute('unpause'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('pause', function () { + execute('pause'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekbackward', function () { + execute('rewind'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekforward', function () { + execute('fastForward'); + }); + + /* eslint-disable-next-line compat/compat */ + navigator.mediaSession.setActionHandler('seekto', function (object) { + const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; + // Convert to ms + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); + const wantedTime = object.seekTime * 1000; + playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); + }); +} + +Events.on(playbackManager, 'playerchange', function () { + bindToPlayer(playbackManager.getCurrentPlayer()); +}); + +bindToPlayer(playbackManager.getCurrentPlayer()); -/* eslint-enable indent */ diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 31b04e0fbe..392c6ceafb 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -13,6 +13,7 @@ import ServerConnections from '../ServerConnections'; import alert from '../alert'; import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; const UNLIMITED_ITEMS = -1; @@ -127,7 +128,7 @@ function getItemsForPlayback(serverId, query) { query.EnableTotalRecordCount = false; query.CollapseBoxSetItems = false; - return apiClient.getItems(apiClient.getCurrentUserId(), query); + return getItems(apiClient, apiClient.getCurrentUserId(), query); } } @@ -1038,7 +1039,6 @@ class PlaybackManager { } } - //var mediaType = item.MediaType; return getPlayer(item, getDefaultPlayOptions()) != null; }; diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 1969d478c1..2d5b660629 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -12,102 +12,120 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './playbackSettings.template.html'; -/* eslint-disable indent */ +function fillSkipLengths(select) { + const options = [5, 10, 15, 20, 25, 30]; - function fillSkipLengths(select) { - const options = [5, 10, 15, 20, 25, 30]; + select.innerHTML = options.map(option => { + return { + name: globalize.translate('ValueSeconds', option), + value: option * 1000 + }; + }).map(o => { + return ``; + }).join(''); +} - select.innerHTML = options.map(option => { - return { - name: globalize.translate('ValueSeconds', option), - value: option * 1000 - }; - }).map(o => { - return ``; - }).join(''); +function populateLanguages(select, languages) { + let html = ''; + + html += ``; + + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + + html += ``; } - function populateLanguages(select, languages) { - let html = ''; + select.innerHTML = html; +} - html += ``; +function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) { + const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({ - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; + currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), + isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), + enableAuto: true - html += ``; - } + }) : qualityoptions.getVideoQualityOptions({ - select.innerHTML = html; + currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), + isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), + enableAuto: true, + maxVideoWidth + + }); + + select.innerHTML = options.map(i => { + // render empty string instead of 0 for the auto option + return ``; + }).join(''); +} + +function setMaxBitrateIntoField(select, isInNetwork, mediatype) { + fillQuality(select, isInNetwork, mediatype); + + if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) { + select.value = ''; + } else { + select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype); + } +} + +function fillChromecastQuality(select, maxVideoWidth) { + const options = qualityoptions.getVideoQualityOptions({ + + currentMaxBitrate: appSettings.maxChromecastBitrate(), + isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(), + enableAuto: true, + maxVideoWidth + }); + + select.innerHTML = options.map(i => { + // render empty string instead of 0 for the auto option + return ``; + }).join(''); + + select.value = appSettings.maxChromecastBitrate() || ''; +} + +function setMaxBitrateFromField(select, isInNetwork, mediatype) { + if (select.value) { + appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value); + appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false); + } else { + appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true); + } +} + +function showHideQualityFields(context, user, apiClient) { + if (user.Policy.EnableVideoPlaybackTranscoding) { + context.querySelector('.videoQualitySection').classList.remove('hide'); + } else { + context.querySelector('.videoQualitySection').classList.add('hide'); } - function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) { - const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({ + if (appHost.supports('multiserver')) { + context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); + context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); - currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), - isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), - enableAuto: true - - }) : qualityoptions.getVideoQualityOptions({ - - currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype), - isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype), - enableAuto: true, - maxVideoWidth - - }); - - select.innerHTML = options.map(i => { - // render empty string instead of 0 for the auto option - return ``; - }).join(''); - } - - function setMaxBitrateIntoField(select, isInNetwork, mediatype) { - fillQuality(select, isInNetwork, mediatype); - - if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) { - select.value = ''; + if (user.Policy.EnableAudioPlaybackTranscoding) { + context.querySelector('.musicQualitySection').classList.remove('hide'); } else { - select.value = appSettings.maxStreamingBitrate(isInNetwork, mediatype); - } - } - - function fillChromecastQuality(select, maxVideoWidth) { - const options = qualityoptions.getVideoQualityOptions({ - - currentMaxBitrate: appSettings.maxChromecastBitrate(), - isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(), - enableAuto: true, - maxVideoWidth - }); - - select.innerHTML = options.map(i => { - // render empty string instead of 0 for the auto option - return ``; - }).join(''); - - select.value = appSettings.maxChromecastBitrate() || ''; - } - - function setMaxBitrateFromField(select, isInNetwork, mediatype) { - if (select.value) { - appSettings.maxStreamingBitrate(isInNetwork, mediatype, select.value); - appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, false); - } else { - appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype, true); - } - } - - function showHideQualityFields(context, user, apiClient) { - if (user.Policy.EnableVideoPlaybackTranscoding) { - context.querySelector('.videoQualitySection').classList.remove('hide'); - } else { - context.querySelector('.videoQualitySection').classList.add('hide'); + context.querySelector('.musicQualitySection').classList.add('hide'); } - if (appHost.supports('multiserver')) { + return; + } + + apiClient.getEndpointInfo().then(endpointInfo => { + if (endpointInfo.IsInNetwork) { context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); + + context.querySelector('.fldVideoInternetQuality').classList.add('hide'); + context.querySelector('.musicQualitySection').classList.add('hide'); + } else { + context.querySelector('.fldVideoInNetworkQuality').classList.add('hide'); + context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); if (user.Policy.EnableAudioPlaybackTranscoding) { @@ -115,249 +133,228 @@ import template from './playbackSettings.template.html'; } else { context.querySelector('.musicQualitySection').classList.add('hide'); } - - return; } + }); +} - apiClient.getEndpointInfo().then(endpointInfo => { - if (endpointInfo.IsInNetwork) { - context.querySelector('.fldVideoInNetworkQuality').classList.remove('hide'); - - context.querySelector('.fldVideoInternetQuality').classList.add('hide'); - context.querySelector('.musicQualitySection').classList.add('hide'); - } else { - context.querySelector('.fldVideoInNetworkQuality').classList.add('hide'); - - context.querySelector('.fldVideoInternetQuality').classList.remove('hide'); - - if (user.Policy.EnableAudioPlaybackTranscoding) { - context.querySelector('.musicQualitySection').classList.remove('hide'); - } else { - context.querySelector('.musicQualitySection').classList.add('hide'); - } - } - }); +function showOrHideEpisodesField(context) { + if (browser.tizen || browser.web0s) { + context.querySelector('.fldEpisodeAutoPlay').classList.add('hide'); + return; } - function showOrHideEpisodesField(context) { - if (browser.tizen || browser.web0s) { - context.querySelector('.fldEpisodeAutoPlay').classList.add('hide'); - return; - } + context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide'); +} - context.querySelector('.fldEpisodeAutoPlay').classList.remove('hide'); +function loadForm(context, user, userSettings, apiClient) { + const loggedInUserId = apiClient.getCurrentUserId(); + const userId = user.Id; + + showHideQualityFields(context, user, apiClient); + + context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels(); + + apiClient.getCultures().then(allCultures => { + populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures); + + context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ''; + context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; + }); + + if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { + context.querySelector('.fldExternalPlayer').classList.remove('hide'); + } else { + context.querySelector('.fldExternalPlayer').classList.add('hide'); } - function loadForm(context, user, userSettings, apiClient) { - const loggedInUserId = apiClient.getCurrentUserId(); - const userId = user.Id; + if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { + context.querySelector('.qualitySections').classList.remove('hide'); - showHideQualityFields(context, user, apiClient); - - context.querySelector('#selectAllowedAudioChannels').value = userSettings.allowedAudioChannels(); - - apiClient.getCultures().then(allCultures => { - populateLanguages(context.querySelector('#selectAudioLanguage'), allCultures); - - context.querySelector('#selectAudioLanguage', context).value = user.Configuration.AudioLanguagePreference || ''; - context.querySelector('.chkEpisodeAutoPlay').checked = user.Configuration.EnableNextEpisodeAutoPlay || false; - }); - - if (appHost.supports('externalplayerintent') && userId === loggedInUserId) { - context.querySelector('.fldExternalPlayer').classList.remove('hide'); + if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { + context.querySelector('.fldChromecastQuality').classList.remove('hide'); } else { - context.querySelector('.fldExternalPlayer').classList.add('hide'); - } - - if (userId === loggedInUserId && (user.Policy.EnableVideoPlaybackTranscoding || user.Policy.EnableAudioPlaybackTranscoding)) { - context.querySelector('.qualitySections').classList.remove('hide'); - - if (appHost.supports('chromecast') && user.Policy.EnableVideoPlaybackTranscoding) { - context.querySelector('.fldChromecastQuality').classList.remove('hide'); - } else { - context.querySelector('.fldChromecastQuality').classList.add('hide'); - } - } else { - context.querySelector('.qualitySections').classList.add('hide'); context.querySelector('.fldChromecastQuality').classList.add('hide'); } - - context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; - context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); - context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); - context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); - context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; - context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; - context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); - - setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); - setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); - setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); - - fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality')); - - const selectChromecastVersion = context.querySelector('.selectChromecastVersion'); - selectChromecastVersion.value = userSettings.chromecastVersion(); - - const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth'); - selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth(); - - const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); - fillSkipLengths(selectSkipForwardLength); - selectSkipForwardLength.value = userSettings.skipForwardLength(); - - const selectSkipBackLength = context.querySelector('.selectSkipBackLength'); - fillSkipLengths(selectSkipBackLength); - selectSkipBackLength.value = userSettings.skipBackLength(); - - showOrHideEpisodesField(context); - - loading.hide(); + } else { + context.querySelector('.qualitySections').classList.add('hide'); + context.querySelector('.fldChromecastQuality').classList.add('hide'); } - function saveUser(context, user, userSettingsInstance, apiClient) { - appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); + context.querySelector('.chkPlayDefaultAudioTrack').checked = user.Configuration.PlayDefaultAudioTrack || false; + context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); + context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); + context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); + context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; + context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; + context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); - appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); - appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); + setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); + setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); + setMaxBitrateIntoField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); - setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); - setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); - setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); + fillChromecastQuality(context.querySelector('.selectChromecastVideoQuality')); - userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value); - user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value; - user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked; - user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked; - userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked); - userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); + const selectChromecastVersion = context.querySelector('.selectChromecastVersion'); + selectChromecastVersion.value = userSettings.chromecastVersion(); - userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); - user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked; - user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked; - userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); - userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); - userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); + const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth'); + selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth(); - return apiClient.updateUserConfiguration(user.Id, user.Configuration); + const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); + fillSkipLengths(selectSkipForwardLength); + selectSkipForwardLength.value = userSettings.skipForwardLength(); + + const selectSkipBackLength = context.querySelector('.selectSkipBackLength'); + fillSkipLengths(selectSkipBackLength); + selectSkipBackLength.value = userSettings.skipBackLength(); + + showOrHideEpisodesField(context); + + loading.hide(); +} + +function saveUser(context, user, userSettingsInstance, apiClient) { + appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); + + appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); + appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); + + setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); + setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); + setMaxBitrateFromField(context.querySelector('.selectMusicInternetQuality'), false, 'Audio'); + + userSettingsInstance.allowedAudioChannels(context.querySelector('#selectAllowedAudioChannels').value); + user.Configuration.AudioLanguagePreference = context.querySelector('#selectAudioLanguage').value; + user.Configuration.PlayDefaultAudioTrack = context.querySelector('.chkPlayDefaultAudioTrack').checked; + user.Configuration.EnableNextEpisodeAutoPlay = context.querySelector('.chkEpisodeAutoPlay').checked; + userSettingsInstance.preferFmp4HlsContainer(context.querySelector('.chkPreferFmp4HlsContainer').checked); + userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); + + userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); + user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked; + user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked; + userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); + userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); + userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); + + return apiClient.updateUserConfiguration(user.Id, user.Configuration); +} + +function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + loading.show(); + + apiClient.getUser(userId).then(user => { + saveUser(context, user, userSettings, apiClient).then(() => { + loading.hide(); + if (enableSaveConfirmation) { + toast(globalize.translate('SettingsSaved')); + } + + Events.trigger(instance, 'saved'); + }, () => { + loading.hide(); + }); + }); +} + +function setSelectValue(select, value, defaultValue) { + select.value = value; + + if (select.selectedIndex < 0) { + select.value = defaultValue; + } +} + +function onMaxVideoWidthChange(e) { + const context = this.options.element; + + const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality'); + const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality'); + const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality'); + + const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value; + const selectVideoInternetQualityValue = selectVideoInternetQuality.value; + const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value; + + const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0; + + fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth); + fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth); + fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth); + + setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, ''); + setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, ''); + setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, ''); +} + +function onSubmit(e) { + const self = this; + const apiClient = ServerConnections.getApiClient(self.options.serverId); + const userId = self.options.userId; + const userSettings = self.options.userSettings; + + userSettings.setUserInfo(userId, apiClient).then(() => { + const enableSaveConfirmation = self.options.enableSaveConfirmation; + save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); + }); + + // Disable default form submission + if (e) { + e.preventDefault(); + } + return false; +} + +function embed(options, self) { + options.element.innerHTML = globalize.translateHtml(template, 'core'); + + options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); + + if (options.enableSaveButton) { + options.element.querySelector('.btnSave').classList.remove('hide'); } - function save(instance, context, userId, userSettings, apiClient, enableSaveConfirmation) { + options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); + + self.loadData(); + + if (options.autoFocus) { + focusManager.autoFocus(options.element); + } +} + +class PlaybackSettings { + constructor(options) { + this.options = options; + embed(options, this); + } + + loadData() { + const self = this; + const context = self.options.element; + loading.show(); - apiClient.getUser(userId).then(user => { - saveUser(context, user, userSettings, apiClient).then(() => { - loading.hide(); - if (enableSaveConfirmation) { - toast(globalize.translate('SettingsSaved')); - } - - Events.trigger(instance, 'saved'); - }, () => { - loading.hide(); - }); - }); - } - - function setSelectValue(select, value, defaultValue) { - select.value = value; - - if (select.selectedIndex < 0) { - select.value = defaultValue; - } - } - - function onMaxVideoWidthChange(e) { - const context = this.options.element; - - const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality'); - const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality'); - const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality'); - - const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value; - const selectVideoInternetQualityValue = selectVideoInternetQuality.value; - const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value; - - const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0; - - fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth); - fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth); - fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth); - - setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, ''); - setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, ''); - setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, ''); - } - - function onSubmit(e) { - const self = this; - const apiClient = ServerConnections.getApiClient(self.options.serverId); const userId = self.options.userId; + const apiClient = ServerConnections.getApiClient(self.options.serverId); const userSettings = self.options.userSettings; - userSettings.setUserInfo(userId, apiClient).then(() => { - const enableSaveConfirmation = self.options.enableSaveConfirmation; - save(self, self.options.element, userId, userSettings, apiClient, enableSaveConfirmation); - }); + apiClient.getUser(userId).then(user => { + userSettings.setUserInfo(userId, apiClient).then(() => { + self.dataLoaded = true; - // Disable default form submission - if (e) { - e.preventDefault(); - } - return false; - } - - function embed(options, self) { - options.element.innerHTML = globalize.translateHtml(template, 'core'); - - options.element.querySelector('form').addEventListener('submit', onSubmit.bind(self)); - - if (options.enableSaveButton) { - options.element.querySelector('.btnSave').classList.remove('hide'); - } - - options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); - - self.loadData(); - - if (options.autoFocus) { - focusManager.autoFocus(options.element); - } - } - - class PlaybackSettings { - constructor(options) { - this.options = options; - embed(options, this); - } - - loadData() { - const self = this; - const context = self.options.element; - - loading.show(); - - const userId = self.options.userId; - const apiClient = ServerConnections.getApiClient(self.options.serverId); - const userSettings = self.options.userSettings; - - apiClient.getUser(userId).then(user => { - userSettings.setUserInfo(userId, apiClient).then(() => { - self.dataLoaded = true; - - loadForm(context, user, userSettings, apiClient); - }); + loadForm(context, user, userSettings, apiClient); }); - } - - submit() { - onSubmit.call(this); - } - - destroy() { - this.options = null; - } + }); } -/* eslint-enable indent */ + submit() { + onSubmit.call(this); + } + + destroy() { + this.options = null; + } +} + export default PlaybackSettings; diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index 7ce9448306..d03db1ef71 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -9,473 +9,471 @@ import { PluginType } from '../../types/plugin.ts'; import './playerstats.scss'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +function init(instance) { + const parent = document.createElement('div'); - function init(instance) { - const parent = document.createElement('div'); + parent.classList.add('playerStats'); - parent.classList.add('playerStats'); - - if (layoutManager.tv) { - parent.classList.add('playerStats-tv'); - } - - parent.classList.add('hide'); - - let button; - - if (layoutManager.tv) { - button = ''; - } else { - button = ''; - } - - const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content'; - - parent.innerHTML = '
' + button + '
'; - - button = parent.querySelector('.playerStats-closeButton'); - - if (button) { - button.addEventListener('click', onCloseButtonClick.bind(instance)); - } - - document.body.appendChild(parent); - - instance.element = parent; + if (layoutManager.tv) { + parent.classList.add('playerStats-tv'); } - function onCloseButtonClick() { - this.enabled(false); + parent.classList.add('hide'); + + let button; + + if (layoutManager.tv) { + button = ''; + } else { + button = ''; } - function renderStats(elem, categories) { - elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) { - let categoryHtml = ''; + const contentClass = layoutManager.tv ? 'playerStats-content playerStats-content-tv' : 'playerStats-content'; - const stats = category.stats; + parent.innerHTML = '
' + button + '
'; - if (stats.length && category.name) { - categoryHtml += '
'; + button = parent.querySelector('.playerStats-closeButton'); - categoryHtml += '
'; - categoryHtml += category.name; - categoryHtml += '
'; - - categoryHtml += '
'; - categoryHtml += category.subText || ''; - categoryHtml += '
'; - - categoryHtml += '
'; - } - - for (let i = 0, length = stats.length; i < length; i++) { - categoryHtml += '
'; - - const stat = stats[i]; - - categoryHtml += '
'; - categoryHtml += stat.label; - categoryHtml += '
'; - - categoryHtml += '
'; - categoryHtml += stat.value; - categoryHtml += '
'; - - categoryHtml += '
'; - } - - return categoryHtml; - }).join(''); + if (button) { + button.addEventListener('click', onCloseButtonClick.bind(instance)); } - function getSession(instance, player) { - const now = new Date().getTime(); + document.body.appendChild(parent); - if ((now - (instance.lastSessionTime || 0)) < 10000) { - return Promise.resolve(instance.lastSession); + instance.element = parent; +} + +function onCloseButtonClick() { + this.enabled(false); +} + +function renderStats(elem, categories) { + elem.querySelector('.playerStats-stats').innerHTML = categories.map(function (category) { + let categoryHtml = ''; + + const stats = category.stats; + + if (stats.length && category.name) { + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += category.name; + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += category.subText || ''; + categoryHtml += '
'; + + categoryHtml += '
'; } - const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); + for (let i = 0, length = stats.length; i < length; i++) { + categoryHtml += '
'; - return apiClient.getSessions({ - deviceId: apiClient.deviceId() - }).then(function (sessions) { - instance.lastSession = sessions[0] || {}; - instance.lastSessionTime = new Date().getTime(); + const stat = stats[i]; - return Promise.resolve(instance.lastSession); - }, function () { - return Promise.resolve({}); + categoryHtml += '
'; + categoryHtml += stat.label; + categoryHtml += '
'; + + categoryHtml += '
'; + categoryHtml += stat.value; + categoryHtml += '
'; + + categoryHtml += '
'; + } + + return categoryHtml; + }).join(''); +} + +function getSession(instance, player) { + const now = new Date().getTime(); + + if ((now - (instance.lastSessionTime || 0)) < 10000) { + return Promise.resolve(instance.lastSession); + } + + const apiClient = ServerConnections.getApiClient(playbackManager.currentItem(player).ServerId); + + return apiClient.getSessions({ + deviceId: apiClient.deviceId() + }).then(function (sessions) { + instance.lastSession = sessions[0] || {}; + instance.lastSessionTime = new Date().getTime(); + + return Promise.resolve(instance.lastSession); + }, function () { + return Promise.resolve({}); + }); +} + +function translateReason(reason) { + return globalize.translate('' + reason); +} + +function getTranscodingStats(session, player, displayPlayMethod) { + const sessionStats = []; + + let videoCodec; + let audioCodec; + let totalBitrate; + let audioChannels; + + if (session.TranscodingInfo) { + videoCodec = session.TranscodingInfo.VideoCodec; + audioCodec = session.TranscodingInfo.AudioCodec; + totalBitrate = session.TranscodingInfo.Bitrate; + audioChannels = session.TranscodingInfo.AudioChannels; + } + + if (videoCodec) { + sessionStats.push({ + label: globalize.translate('LabelVideoCodec'), + value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() }); } - function translateReason(reason) { - return globalize.translate('' + reason); + if (audioCodec) { + sessionStats.push({ + label: globalize.translate('LabelAudioCodec'), + value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() + }); } - function getTranscodingStats(session, player, displayPlayMethod) { - const sessionStats = []; - - let videoCodec; - let audioCodec; - let totalBitrate; - let audioChannels; - - if (session.TranscodingInfo) { - videoCodec = session.TranscodingInfo.VideoCodec; - audioCodec = session.TranscodingInfo.AudioCodec; - totalBitrate = session.TranscodingInfo.Bitrate; - audioChannels = session.TranscodingInfo.AudioChannels; - } - - if (videoCodec) { - sessionStats.push({ - label: globalize.translate('LabelVideoCodec'), - value: session.TranscodingInfo.IsVideoDirect ? (videoCodec.toUpperCase() + ' (direct)') : videoCodec.toUpperCase() - }); - } - - if (audioCodec) { - sessionStats.push({ - label: globalize.translate('LabelAudioCodec'), - value: session.TranscodingInfo.IsAudioDirect ? (audioCodec.toUpperCase() + ' (direct)') : audioCodec.toUpperCase() - }); - } - - if (displayPlayMethod === 'Transcode') { - if (audioChannels) { - sessionStats.push({ - label: globalize.translate('LabelAudioChannels'), - value: audioChannels - }); - } - if (totalBitrate) { - sessionStats.push({ - label: globalize.translate('LabelBitrate'), - value: getDisplayBitrate(totalBitrate) - }); - } - if (session.TranscodingInfo.CompletionPercentage) { - sessionStats.push({ - label: globalize.translate('LabelTranscodingProgress'), - value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' - }); - } - if (session.TranscodingInfo.Framerate) { - sessionStats.push({ - label: globalize.translate('LabelTranscodingFramerate'), - value: session.TranscodingInfo.Framerate + ' fps' - }); - } - if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - sessionStats.push({ - label: globalize.translate('LabelReasonForTranscoding'), - value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') - }); - } - if (session.TranscodingInfo.HardwareAccelerationType) { - sessionStats.push({ - label: globalize.translate('LabelHardwareEncoding'), - value: session.TranscodingInfo.HardwareAccelerationType - }); - } - } - - return sessionStats; - } - - function getDisplayBitrate(bitrate) { - if (bitrate > 1000000) { - return (bitrate / 1000000).toFixed(1) + ' Mbps'; - } else { - return Math.floor(bitrate / 1000) + ' kbps'; - } - } - - function getReadableSize(size) { - if (size >= 1073741824) { - return parseFloat((size / 1073741824).toFixed(1)) + ' GiB'; - } else if (size >= 1048576) { - return parseFloat((size / 1048576).toFixed(1)) + ' MiB'; - } else { - return Math.floor(size / 1024) + ' KiB'; - } - } - - function getMediaSourceStats(session, player) { - const sessionStats = []; - - const mediaSource = playbackManager.currentMediaSource(player) || {}; - const totalBitrate = mediaSource.Bitrate; - const mediaFileSize = mediaSource.Size; - - if (mediaSource.Container) { - sessionStats.push({ - label: globalize.translate('LabelProfileContainer'), - value: mediaSource.Container - }); - } - - if (mediaFileSize) { - sessionStats.push({ - label: globalize.translate('LabelSize'), - value: getReadableSize(mediaFileSize) - }); - } - - if (totalBitrate) { - sessionStats.push({ - label: globalize.translate('LabelBitrate'), - value: getDisplayBitrate(totalBitrate) - }); - } - - const mediaStreams = mediaSource.MediaStreams || []; - const videoStream = mediaStreams.filter(function (s) { - return s.Type === 'Video'; - })[0] || {}; - - const videoCodec = videoStream.Codec; - - const audioStreamIndex = playbackManager.getAudioStreamIndex(player); - const audioStream = playbackManager.audioTracks(player).filter(function (s) { - return s.Type === 'Audio' && s.Index === audioStreamIndex; - })[0] || {}; - - const audioCodec = audioStream.Codec; - const audioChannels = audioStream.Channels; - - const videoInfos = []; - - if (videoCodec) { - videoInfos.push(videoCodec.toUpperCase()); - } - - if (videoStream.Profile) { - videoInfos.push(videoStream.Profile); - } - - if (videoInfos.length) { - sessionStats.push({ - label: globalize.translate('LabelVideoCodec'), - value: videoInfos.join(' ') - }); - } - - if (videoStream.BitRate) { - sessionStats.push({ - label: globalize.translate('LabelVideoBitrate'), - value: getDisplayBitrate(videoStream.BitRate) - }); - } - - if (videoStream.VideoRangeType) { - sessionStats.push({ - label: globalize.translate('LabelVideoRangeType'), - value: videoStream.VideoRangeType - }); - } - - const audioInfos = []; - - if (audioCodec) { - audioInfos.push(audioCodec.toUpperCase()); - } - - if (audioStream.Profile) { - audioInfos.push(audioStream.Profile); - } - - if (audioInfos.length) { - sessionStats.push({ - label: globalize.translate('LabelAudioCodec'), - value: audioInfos.join(' ') - }); - } - - if (audioStream.BitRate) { - sessionStats.push({ - label: globalize.translate('LabelAudioBitrate'), - value: getDisplayBitrate(audioStream.BitRate) - }); - } - + if (displayPlayMethod === 'Transcode') { if (audioChannels) { sessionStats.push({ label: globalize.translate('LabelAudioChannels'), value: audioChannels }); } - - if (audioStream.SampleRate) { + if (totalBitrate) { sessionStats.push({ - label: globalize.translate('LabelAudioSampleRate'), - value: audioStream.SampleRate + ' Hz' + label: globalize.translate('LabelBitrate'), + value: getDisplayBitrate(totalBitrate) }); } - - if (audioStream.BitDepth) { + if (session.TranscodingInfo.CompletionPercentage) { sessionStats.push({ - label: globalize.translate('LabelAudioBitDepth'), - value: audioStream.BitDepth + label: globalize.translate('LabelTranscodingProgress'), + value: session.TranscodingInfo.CompletionPercentage.toFixed(1) + '%' + }); + } + if (session.TranscodingInfo.Framerate) { + sessionStats.push({ + label: globalize.translate('LabelTranscodingFramerate'), + value: session.TranscodingInfo.Framerate + ' fps' + }); + } + if (session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + sessionStats.push({ + label: globalize.translate('LabelReasonForTranscoding'), + value: session.TranscodingInfo.TranscodeReasons.map(translateReason).join('
') + }); + } + if (session.TranscodingInfo.HardwareAccelerationType) { + sessionStats.push({ + label: globalize.translate('LabelHardwareEncoding'), + value: session.TranscodingInfo.HardwareAccelerationType }); } - - return sessionStats; } - function getSyncPlayStats() { - const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + return sessionStats; +} - if (!SyncPlay?.Manager.isSyncPlayEnabled()) { - return []; - } +function getDisplayBitrate(bitrate) { + if (bitrate > 1000000) { + return (bitrate / 1000000).toFixed(1) + ' Mbps'; + } else { + return Math.floor(bitrate / 1000) + ' kbps'; + } +} - const syncStats = []; - const stats = SyncPlay.Manager.getStats(); +function getReadableSize(size) { + if (size >= 1073741824) { + return parseFloat((size / 1073741824).toFixed(1)) + ' GiB'; + } else if (size >= 1048576) { + return parseFloat((size / 1048576).toFixed(1)) + ' MiB'; + } else { + return Math.floor(size / 1024) + ' KiB'; + } +} - syncStats.push({ - label: globalize.translate('LabelSyncPlayTimeSyncDevice'), - value: stats.TimeSyncDevice +function getMediaSourceStats(session, player) { + const sessionStats = []; + + const mediaSource = playbackManager.currentMediaSource(player) || {}; + const totalBitrate = mediaSource.Bitrate; + const mediaFileSize = mediaSource.Size; + + if (mediaSource.Container) { + sessionStats.push({ + label: globalize.translate('LabelProfileContainer'), + value: mediaSource.Container }); - - syncStats.push({ - // TODO: clean old string 'LabelSyncPlayTimeOffset' from translations. - label: globalize.translate('LabelSyncPlayTimeSyncOffset'), - value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit') - }); - - syncStats.push({ - label: globalize.translate('LabelSyncPlayPlaybackDiff'), - value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit') - }); - - syncStats.push({ - label: globalize.translate('LabelSyncPlaySyncMethod'), - value: stats.SyncMethod - }); - - return syncStats; } - function getStats(instance, player) { - const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); - const sessionPromise = getSession(instance, player); + if (mediaFileSize) { + sessionStats.push({ + label: globalize.translate('LabelSize'), + value: getReadableSize(mediaFileSize) + }); + } - return Promise.all([statsPromise, sessionPromise]).then(function (responses) { - const playerStatsResult = responses[0]; - const playerStats = playerStatsResult.categories || []; - const session = responses[1]; + if (totalBitrate) { + sessionStats.push({ + label: globalize.translate('LabelBitrate'), + value: getDisplayBitrate(totalBitrate) + }); + } - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - let localizedDisplayMethod = displayPlayMethod; + const mediaStreams = mediaSource.MediaStreams || []; + const videoStream = mediaStreams.filter(function (s) { + return s.Type === 'Video'; + })[0] || {}; - if (displayPlayMethod === 'DirectPlay') { - localizedDisplayMethod = globalize.translate('DirectPlaying'); - } else if (displayPlayMethod === 'Remux') { - localizedDisplayMethod = globalize.translate('Remuxing'); - } else if (displayPlayMethod === 'DirectStream') { - localizedDisplayMethod = globalize.translate('DirectStreaming'); - } else if (displayPlayMethod === 'Transcode') { - localizedDisplayMethod = globalize.translate('Transcoding'); + const videoCodec = videoStream.Codec; + + const audioStreamIndex = playbackManager.getAudioStreamIndex(player); + const audioStream = playbackManager.audioTracks(player).filter(function (s) { + return s.Type === 'Audio' && s.Index === audioStreamIndex; + })[0] || {}; + + const audioCodec = audioStream.Codec; + const audioChannels = audioStream.Channels; + + const videoInfos = []; + + if (videoCodec) { + videoInfos.push(videoCodec.toUpperCase()); + } + + if (videoStream.Profile) { + videoInfos.push(videoStream.Profile); + } + + if (videoInfos.length) { + sessionStats.push({ + label: globalize.translate('LabelVideoCodec'), + value: videoInfos.join(' ') + }); + } + + if (videoStream.BitRate) { + sessionStats.push({ + label: globalize.translate('LabelVideoBitrate'), + value: getDisplayBitrate(videoStream.BitRate) + }); + } + + if (videoStream.VideoRangeType) { + sessionStats.push({ + label: globalize.translate('LabelVideoRangeType'), + value: videoStream.VideoRangeType + }); + } + + const audioInfos = []; + + if (audioCodec) { + audioInfos.push(audioCodec.toUpperCase()); + } + + if (audioStream.Profile) { + audioInfos.push(audioStream.Profile); + } + + if (audioInfos.length) { + sessionStats.push({ + label: globalize.translate('LabelAudioCodec'), + value: audioInfos.join(' ') + }); + } + + if (audioStream.BitRate) { + sessionStats.push({ + label: globalize.translate('LabelAudioBitrate'), + value: getDisplayBitrate(audioStream.BitRate) + }); + } + + if (audioChannels) { + sessionStats.push({ + label: globalize.translate('LabelAudioChannels'), + value: audioChannels + }); + } + + if (audioStream.SampleRate) { + sessionStats.push({ + label: globalize.translate('LabelAudioSampleRate'), + value: audioStream.SampleRate + ' Hz' + }); + } + + if (audioStream.BitDepth) { + sessionStats.push({ + label: globalize.translate('LabelAudioBitDepth'), + value: audioStream.BitDepth + }); + } + + return sessionStats; +} + +function getSyncPlayStats() { + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + if (!SyncPlay?.Manager.isSyncPlayEnabled()) { + return []; + } + + const syncStats = []; + const stats = SyncPlay.Manager.getStats(); + + syncStats.push({ + label: globalize.translate('LabelSyncPlayTimeSyncDevice'), + value: stats.TimeSyncDevice + }); + + syncStats.push({ + // TODO: clean old string 'LabelSyncPlayTimeOffset' from translations. + label: globalize.translate('LabelSyncPlayTimeSyncOffset'), + value: stats.TimeSyncOffset + ' ' + globalize.translate('MillisecondsUnit') + }); + + syncStats.push({ + label: globalize.translate('LabelSyncPlayPlaybackDiff'), + value: stats.PlaybackDiff + ' ' + globalize.translate('MillisecondsUnit') + }); + + syncStats.push({ + label: globalize.translate('LabelSyncPlaySyncMethod'), + value: stats.SyncMethod + }); + + return syncStats; +} + +function getStats(instance, player) { + const statsPromise = player.getStats ? player.getStats() : Promise.resolve({}); + const sessionPromise = getSession(instance, player); + + return Promise.all([statsPromise, sessionPromise]).then(function (responses) { + const playerStatsResult = responses[0]; + const playerStats = playerStatsResult.categories || []; + const session = responses[1]; + + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + let localizedDisplayMethod = displayPlayMethod; + + if (displayPlayMethod === 'DirectPlay') { + localizedDisplayMethod = globalize.translate('DirectPlaying'); + } else if (displayPlayMethod === 'Remux') { + localizedDisplayMethod = globalize.translate('Remuxing'); + } else if (displayPlayMethod === 'DirectStream') { + localizedDisplayMethod = globalize.translate('DirectStreaming'); + } else if (displayPlayMethod === 'Transcode') { + localizedDisplayMethod = globalize.translate('Transcoding'); + } + + const baseCategory = { + stats: [], + name: globalize.translate('LabelPlaybackInfo') + }; + + baseCategory.stats.unshift({ + label: globalize.translate('LabelPlayMethod'), + value: localizedDisplayMethod + }); + + baseCategory.stats.unshift({ + label: globalize.translate('LabelPlayer'), + value: player.name + }); + + const categories = []; + + categories.push(baseCategory); + + for (let i = 0, length = playerStats.length; i < length; i++) { + const category = playerStats[i]; + if (category.type === 'audio') { + category.name = globalize.translate('LabelAudioInfo'); + } else if (category.type === 'video') { + category.name = globalize.translate('LabelVideoInfo'); } + categories.push(category); + } - const baseCategory = { - stats: [], - name: globalize.translate('LabelPlaybackInfo') - }; - - baseCategory.stats.unshift({ - label: globalize.translate('LabelPlayMethod'), - value: localizedDisplayMethod - }); - - baseCategory.stats.unshift({ - label: globalize.translate('LabelPlayer'), - value: player.name - }); - - const categories = []; - - categories.push(baseCategory); - - for (let i = 0, length = playerStats.length; i < length; i++) { - const category = playerStats[i]; - if (category.type === 'audio') { - category.name = globalize.translate('LabelAudioInfo'); - } else if (category.type === 'video') { - category.name = globalize.translate('LabelVideoInfo'); - } - categories.push(category); - } - - let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo'); - if (displayPlayMethod === 'Remux') { - localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo'); - } else if (displayPlayMethod === 'DirectStream') { - localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo'); - } - - if (session.TranscodingInfo) { - categories.push({ - stats: getTranscodingStats(session, player, displayPlayMethod), - name: localizedTranscodingInfo - }); - } + let localizedTranscodingInfo = globalize.translate('LabelTranscodingInfo'); + if (displayPlayMethod === 'Remux') { + localizedTranscodingInfo = globalize.translate('LabelRemuxingInfo'); + } else if (displayPlayMethod === 'DirectStream') { + localizedTranscodingInfo = globalize.translate('LabelDirectStreamingInfo'); + } + if (session.TranscodingInfo) { categories.push({ - stats: getMediaSourceStats(session, player), - name: globalize.translate('LabelOriginalMediaInfo') + stats: getTranscodingStats(session, player, displayPlayMethod), + name: localizedTranscodingInfo }); + } - const syncPlayStats = getSyncPlayStats(); - if (syncPlayStats.length > 0) { - categories.push({ - stats: syncPlayStats, - name: globalize.translate('LabelSyncPlayInfo') - }); - } - - return Promise.resolve(categories); + categories.push({ + stats: getMediaSourceStats(session, player), + name: globalize.translate('LabelOriginalMediaInfo') }); + + const syncPlayStats = getSyncPlayStats(); + if (syncPlayStats.length > 0) { + categories.push({ + stats: syncPlayStats, + name: globalize.translate('LabelSyncPlayInfo') + }); + } + + return Promise.resolve(categories); + }); +} + +function renderPlayerStats(instance, player) { + const now = new Date().getTime(); + + if ((now - (instance.lastRender || 0)) < 700) { + return; } - function renderPlayerStats(instance, player) { - const now = new Date().getTime(); + instance.lastRender = now; - if ((now - (instance.lastRender || 0)) < 700) { + getStats(instance, player).then(function (stats) { + const elem = instance.element; + if (!elem) { return; } - instance.lastRender = now; + renderStats(elem, stats); + }); +} - getStats(instance, player).then(function (stats) { - const elem = instance.element; - if (!elem) { - return; - } +function bindEvents(instance, player) { + const localOnTimeUpdate = function () { + renderPlayerStats(instance, player); + }; - renderStats(elem, stats); - }); - } - - function bindEvents(instance, player) { - const localOnTimeUpdate = function () { - renderPlayerStats(instance, player); - }; - - instance.onTimeUpdate = localOnTimeUpdate; - Events.on(player, 'timeupdate', localOnTimeUpdate); - } - - function unbindEvents(instance, player) { - const localOnTimeUpdate = instance.onTimeUpdate; - - if (localOnTimeUpdate) { - Events.off(player, 'timeupdate', localOnTimeUpdate); - } + instance.onTimeUpdate = localOnTimeUpdate; + Events.on(player, 'timeupdate', localOnTimeUpdate); +} + +function unbindEvents(instance, player) { + const localOnTimeUpdate = instance.onTimeUpdate; + + if (localOnTimeUpdate) { + Events.off(player, 'timeupdate', localOnTimeUpdate); } +} class PlayerStats { constructor(options) { @@ -527,6 +525,4 @@ class PlayerStats { } } -/* eslint-enable indent */ - export default PlayerStats; diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 90d835e1c5..91c11010be 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -18,269 +18,266 @@ import 'material-design-icons-iconfont'; import '../formdialog.scss'; import ServerConnections from '../ServerConnections'; -/* eslint-disable indent */ +let currentServerId; - let currentServerId; +function onSubmit(e) { + const panel = dom.parentWithClass(this, 'dialog'); - function onSubmit(e) { - const panel = dom.parentWithClass(this, 'dialog'); + const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; + const apiClient = ServerConnections.getApiClient(currentServerId); - const playlistId = panel.querySelector('#selectPlaylistToAddTo').value; - const apiClient = ServerConnections.getApiClient(currentServerId); - - if (playlistId) { - userSettings.set('playlisteditor-lastplaylistid', playlistId); - addToPlaylist(apiClient, panel, playlistId); - } else { - createPlaylist(apiClient, panel); - } - - e.preventDefault(); - return false; + if (playlistId) { + userSettings.set('playlisteditor-lastplaylistid', playlistId); + addToPlaylist(apiClient, panel, playlistId); + } else { + createPlaylist(apiClient, panel); } - function createPlaylist(apiClient, dlg) { - loading.show(); + e.preventDefault(); + return false; +} - const url = apiClient.getUrl('Playlists', { - Name: dlg.querySelector('#txtNewPlaylistName').value, - Ids: dlg.querySelector('.fldSelectedItemIds').value || '', - userId: apiClient.getCurrentUserId() +function createPlaylist(apiClient, dlg) { + loading.show(); - }); + const url = apiClient.getUrl('Playlists', { + Name: dlg.querySelector('#txtNewPlaylistName').value, + Ids: dlg.querySelector('.fldSelectedItemIds').value || '', + userId: apiClient.getCurrentUserId() - apiClient.ajax({ - type: 'POST', - url: url, - dataType: 'json', - contentType: 'application/json' - }).then(result => { - loading.hide(); - - const id = result.Id; - dlg.submitted = true; - dialogHelper.close(dlg); - redirectToPlaylist(apiClient, id); - }); - } - - function redirectToPlaylist(apiClient, id) { - appRouter.showItem(id, apiClient.serverId()); - } - - function addToPlaylist(apiClient, dlg, id) { - const itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; - - if (id === 'queue') { - playbackManager.queue({ - serverId: apiClient.serverId(), - ids: itemIds.split(',') - }); - dlg.submitted = true; - dialogHelper.close(dlg); - return; - } - - loading.show(); - - const url = apiClient.getUrl(`Playlists/${id}/Items`, { - Ids: itemIds, - userId: apiClient.getCurrentUserId() - }); - - apiClient.ajax({ - type: 'POST', - url: url - - }).then(() => { - loading.hide(); - - dlg.submitted = true; - dialogHelper.close(dlg); - }); - } - - function triggerChange(select) { - select.dispatchEvent(new CustomEvent('change', {})); - } - - function populatePlaylists(editorOptions, panel) { - const select = panel.querySelector('#selectPlaylistToAddTo'); + }); + apiClient.ajax({ + type: 'POST', + url: url, + dataType: 'json', + contentType: 'application/json' + }).then(result => { loading.hide(); - panel.querySelector('.newPlaylistInfo').classList.add('hide'); + const id = result.Id; + dlg.submitted = true; + dialogHelper.close(dlg); + redirectToPlaylist(apiClient, id); + }); +} - const options = { - Recursive: true, - IncludeItemTypes: 'Playlist', - SortBy: 'SortName', - EnableTotalRecordCount: false - }; +function redirectToPlaylist(apiClient, id) { + appRouter.showItem(id, apiClient.serverId()); +} - const apiClient = ServerConnections.getApiClient(currentServerId); - const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; +function addToPlaylist(apiClient, dlg, id) { + const itemIds = dlg.querySelector('.fldSelectedItemIds').value || ''; - apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { - let html = ''; - - if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) { - html += ``; - } - - html += ``; - - html += result.Items.map(i => { - return ``; - }); - - select.innerHTML = html; - - let defaultValue = editorOptions.defaultValue; - if (!defaultValue) { - defaultValue = userSettings.get('playlisteditor-lastplaylistid') || ''; - } - select.value = defaultValue === 'new' ? '' : defaultValue; - - // If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid - if (!select.value) { - select.value = ''; - } - - triggerChange(select); - - loading.hide(); + if (id === 'queue') { + playbackManager.queue({ + serverId: apiClient.serverId(), + ids: itemIds.split(',') }); + dlg.submitted = true; + dialogHelper.close(dlg); + return; } - function getEditorHtml(items) { + loading.show(); + + const url = apiClient.getUrl(`Playlists/${id}/Items`, { + Ids: itemIds, + userId: apiClient.getCurrentUserId() + }); + + apiClient.ajax({ + type: 'POST', + url: url + + }).then(() => { + loading.hide(); + + dlg.submitted = true; + dialogHelper.close(dlg); + }); +} + +function triggerChange(select) { + select.dispatchEvent(new CustomEvent('change', {})); +} + +function populatePlaylists(editorOptions, panel) { + const select = panel.querySelector('#selectPlaylistToAddTo'); + + loading.hide(); + + panel.querySelector('.newPlaylistInfo').classList.add('hide'); + + const options = { + Recursive: true, + IncludeItemTypes: 'Playlist', + SortBy: 'SortName', + EnableTotalRecordCount: false + }; + + const apiClient = ServerConnections.getApiClient(currentServerId); + const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance; + + apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => { let html = ''; - html += '
'; - html += '
'; - html += '
'; + if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) { + html += ``; + } - html += '
'; - let autoFocus = items.length ? ' autofocus' : ''; - html += ``; - html += '
'; + html += ``; - html += '
'; - - html += '
'; - autoFocus = items.length ? '' : ' autofocus'; - html += ``; - html += '
'; - - // newPlaylistInfo - html += '
'; - - html += '
'; - html += ``; - html += '
'; - - html += ''; - - html += '
'; - html += '
'; - html += '
'; - - return html; - } - - function initEditor(content, options, items) { - content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { - if (this.value) { - content.querySelector('.newPlaylistInfo').classList.add('hide'); - content.querySelector('#txtNewPlaylistName').removeAttribute('required'); - } else { - content.querySelector('.newPlaylistInfo').classList.remove('hide'); - content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required'); - } + html += result.Items.map(i => { + return ``; }); - content.querySelector('form').addEventListener('submit', onSubmit); + select.innerHTML = html; - content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + let defaultValue = editorOptions.defaultValue; + if (!defaultValue) { + defaultValue = userSettings.get('playlisteditor-lastplaylistid') || ''; + } + select.value = defaultValue === 'new' ? '' : defaultValue; - if (items.length) { - content.querySelector('.fldSelectPlaylist').classList.remove('hide'); - populatePlaylists(options, content); + // If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid + if (!select.value) { + select.value = ''; + } + + triggerChange(select); + + loading.hide(); + }); +} + +function getEditorHtml(items) { + let html = ''; + + html += '
'; + html += '
'; + html += '
'; + + html += '
'; + let autoFocus = items.length ? ' autofocus' : ''; + html += ``; + html += '
'; + + html += '
'; + + html += '
'; + autoFocus = items.length ? '' : ' autofocus'; + html += ``; + html += '
'; + + // newPlaylistInfo + html += '
'; + + html += '
'; + html += ``; + html += '
'; + + html += ''; + + html += '
'; + html += '
'; + html += '
'; + + return html; +} + +function initEditor(content, options, items) { + content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () { + if (this.value) { + content.querySelector('.newPlaylistInfo').classList.add('hide'); + content.querySelector('#txtNewPlaylistName').removeAttribute('required'); } else { - content.querySelector('.fldSelectPlaylist').classList.add('hide'); - - const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); - selectPlaylistToAddTo.innerHTML = ''; - selectPlaylistToAddTo.value = ''; - triggerChange(selectPlaylistToAddTo); + content.querySelector('.newPlaylistInfo').classList.remove('hide'); + content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required'); } - } + }); - function centerFocus(elem, horiz, on) { - import('../../scripts/scrollHelper').then((scrollHelper) => { - const fn = on ? 'on' : 'off'; - scrollHelper.centerFocus[fn](elem, horiz); + content.querySelector('form').addEventListener('submit', onSubmit); + + content.querySelector('.fldSelectedItemIds', content).value = items.join(','); + + if (items.length) { + content.querySelector('.fldSelectPlaylist').classList.remove('hide'); + populatePlaylists(options, content); + } else { + content.querySelector('.fldSelectPlaylist').classList.add('hide'); + + const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo'); + selectPlaylistToAddTo.innerHTML = ''; + selectPlaylistToAddTo.value = ''; + triggerChange(selectPlaylistToAddTo); + } +} + +function centerFocus(elem, horiz, on) { + import('../../scripts/scrollHelper').then((scrollHelper) => { + const fn = on ? 'on' : 'off'; + scrollHelper.centerFocus[fn](elem, horiz); + }); +} + +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(); }); } +} - 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(); - }); - } - } - -/* eslint-enable indent */ export default showEditor; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index 26b7220bce..8fc8d4667a 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -325,10 +325,11 @@ export default function () { if (layoutManager.mobile) { const playingVideo = playbackManager.isPlayingVideo() && item !== null; const playingAudio = !playbackManager.isPlayingVideo() && item !== null; - buttonVisible(context.querySelector('.btnRepeat'), playingAudio); - buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio); - buttonVisible(context.querySelector('.btnRewind'), playingVideo); - buttonVisible(context.querySelector('.btnFastForward'), playingVideo); + const playingAudioBook = playingAudio && item.Type == 'AudioBook'; + buttonVisible(context.querySelector('.btnRepeat'), playingAudio && !playingAudioBook); + buttonVisible(context.querySelector('.btnShuffleQueue'), playingAudio && !playingAudioBook); + buttonVisible(context.querySelector('.btnRewind'), playingVideo || playingAudioBook); + buttonVisible(context.querySelector('.btnFastForward'), playingVideo || playingAudioBook); buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnShuffleQueue'), playingVideo); buttonVisible(context.querySelector('.nowPlayingSecondaryButtons .btnRepeat'), playingVideo); } else { @@ -763,18 +764,22 @@ export default function () { context.querySelector('.btnPreviousTrack').addEventListener('click', function (e) { if (currentPlayer) { - if (lastPlayerState.NowPlayingItem.MediaType === 'Audio' && (currentPlayer._currentTime >= 5 || !playbackManager.previousTrack(currentPlayer))) { - // Cancel this event if doubleclick is fired - if (e.detail > 1 && playbackManager.previousTrack(currentPlayer)) { + if (lastPlayerState.NowPlayingItem.MediaType === 'Audio') { + // Cancel this event if doubleclick is fired. The actual previousTrack will be processed by the 'dblclick' event + if (e.detail > 1 ) { + return; + } + // Return to start of track, unless we are already (almost) at the beginning. In the latter case, continue and move + // to the previous track, unless we are at the first track so no previous track exists. + if (currentPlayer._currentTime >= 5 || playbackManager.getCurrentPlaylistIndex(currentPlayer) <= 1) { + playbackManager.seekPercent(0, currentPlayer); + // This is done automatically by playbackManager, however, setting this here gives instant visual feedback. + // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. + positionSlider.value = 0; return; } - playbackManager.seekPercent(0, currentPlayer); - // This is done automatically by playbackManager. However, setting this here gives instant visual feedback. - // TODO: Check why seekPercentage doesn't reflect the changes inmmediately, so we can remove this workaround. - positionSlider.value = 0; - } else { - playbackManager.previousTrack(currentPlayer); } + playbackManager.previousTrack(currentPlayer); } }); diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 6a5aee3645..7417c7643e 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module for controlling scroll behavior. * @module components/scrollManager @@ -9,50 +7,50 @@ import dom from '../scripts/dom'; import browser from '../scripts/browser'; import layoutManager from './layoutManager'; - /** +/** * Scroll time in ms. */ - const ScrollTime = 270; +const ScrollTime = 270; - /** +/** * Epsilon for comparing values. */ - const Epsilon = 1e-6; +const Epsilon = 1e-6; - // FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers - /** +// FIXME: Need to scroll to top of page to fully show the top menu. This can be solved by some marker of top most elements or their containers +/** * Returns minimum vertical scroll. * Scroll less than that value will be zeroed. * * @return {number} Minimum vertical scroll. */ - function minimumScrollY() { - const topMenu = document.querySelector('.headerTop'); - if (topMenu) { - return topMenu.clientHeight; +function minimumScrollY() { + const topMenu = document.querySelector('.headerTop'); + if (topMenu) { + return topMenu.clientHeight; + } + return 0; +} + +const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; + +let supportsScrollToOptions = false; +try { + const elem = document.createElement('div'); + + const opts = Object.defineProperty({}, 'behavior', { + // eslint-disable-next-line getter-return + get: function () { + supportsScrollToOptions = true; } - return 0; - } + }); - const supportsSmoothScroll = 'scrollBehavior' in document.documentElement.style; + elem.scrollTo(opts); +} catch (e) { + console.error('error checking ScrollToOptions support'); +} - let supportsScrollToOptions = false; - try { - const elem = document.createElement('div'); - - const opts = Object.defineProperty({}, 'behavior', { - // eslint-disable-next-line getter-return - get: function () { - supportsScrollToOptions = true; - } - }); - - elem.scrollTo(opts); - } catch (e) { - console.error('error checking ScrollToOptions support'); - } - - /** +/** * Returns value clamped by range [min, max]. * * @param {number} value - Clamped value. @@ -60,16 +58,16 @@ import layoutManager from './layoutManager'; * @param {number} max - Ending of range. * @return {number} Clamped value. */ - function clamp(value, min, max) { - if (value <= min) { - return min; - } else if (value >= max) { - return max; - } - return value; +function clamp(value, min, max) { + if (value <= min) { + return min; + } else if (value >= max) { + return max; } + return value; +} - /** +/** * Returns the required delta to fit range 1 into range 2. * In case of range 1 is bigger than range 2 returns delta to fit most out of range part. * @@ -79,28 +77,28 @@ import layoutManager from './layoutManager'; * @param {number} end2 - Ending of range 2. * @return {number} Delta: <0 move range1 to the left, >0 - to the right. */ - function fitRange(begin1, end1, begin2, end2) { - const delta1 = begin1 - begin2; - const delta2 = end2 - end1; - if (delta1 < 0 && delta1 < delta2) { - return -delta1; - } else if (delta2 < 0) { - return delta2; - } - return 0; +function fitRange(begin1, end1, begin2, end2) { + const delta1 = begin1 - begin2; + const delta2 = end2 - end1; + if (delta1 < 0 && delta1 < delta2) { + return -delta1; + } else if (delta2 < 0) { + return delta2; } + return 0; +} - /** +/** * Ease value. * * @param {number} t - Value in range [0, 1]. * @return {number} Eased value in range [0, 1]. */ - function ease(t) { - return t * (2 - t); // easeOutQuad === ease-out - } +function ease(t) { + return t * (2 - t); // easeOutQuad === ease-out +} - /** +/** * @typedef {Object} Rect * @property {number} left - X coordinate of top-left corner. * @property {number} top - Y coordinate of top-left corner. @@ -108,7 +106,7 @@ import layoutManager from './layoutManager'; * @property {number} height - Height. */ - /** +/** * Document scroll wrapper helps to unify scrolling and fix issues of some browsers. * * webOS 2 Browser: scrolls documentElement (and window), but body has a scroll size @@ -121,157 +119,157 @@ import layoutManager from './layoutManager'; * * Tizen 5 Browser/Native: scrolls documentElement (and window); has a document.scrollingElement */ - class DocumentScroller { - /** +class DocumentScroller { + /** * Horizontal scroll position. * @type {number} */ - get scrollLeft() { - return window.pageXOffset; - } + get scrollLeft() { + return window.pageXOffset; + } - set scrollLeft(val) { - window.scroll(val, window.pageYOffset); - } + set scrollLeft(val) { + window.scroll(val, window.pageYOffset); + } - /** + /** * Vertical scroll position. * @type {number} */ - get scrollTop() { - return window.pageYOffset; - } + get scrollTop() { + return window.pageYOffset; + } - set scrollTop(val) { - window.scroll(window.pageXOffset, val); - } + set scrollTop(val) { + window.scroll(window.pageXOffset, val); + } - /** + /** * Horizontal scroll size (scroll width). * @type {number} */ - get scrollWidth() { - return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); - } + get scrollWidth() { + return Math.max(document.documentElement.scrollWidth, document.body.scrollWidth); + } - /** + /** * Vertical scroll size (scroll height). * @type {number} */ - get scrollHeight() { - return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); - } + get scrollHeight() { + return Math.max(document.documentElement.scrollHeight, document.body.scrollHeight); + } - /** + /** * Horizontal client size (client width). * @type {number} */ - get clientWidth() { - return Math.min(document.documentElement.clientWidth, document.body.clientWidth); - } + get clientWidth() { + return Math.min(document.documentElement.clientWidth, document.body.clientWidth); + } - /** + /** * Vertical client size (client height). * @type {number} */ - get clientHeight() { - return Math.min(document.documentElement.clientHeight, document.body.clientHeight); - } + get clientHeight() { + return Math.min(document.documentElement.clientHeight, document.body.clientHeight); + } - /** + /** * Returns attribute value. * @param {string} attributeName - Attibute name. * @return {string} Attibute value. */ - getAttribute(attributeName) { - return document.body.getAttribute(attributeName); - } - - /** - * Returns bounding client rect. - * @return {Rect} Bounding client rect. - */ - getBoundingClientRect() { - // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport - return { - left: 0, - top: 0, - width: this.clientWidth, - height: this.clientHeight - }; - } - - /** - * Scrolls window. - * @param {...mixed} args See window.scrollTo. - */ - scrollTo() { - window.scrollTo.apply(window, arguments); - } + getAttribute(attributeName) { + return document.body.getAttribute(attributeName); } /** - * Default (document) scroller. - */ - const documentScroller = new DocumentScroller(); - - const scrollerHints = { - x: { - nameScroll: 'scrollWidth', - nameClient: 'clientWidth', - nameStyle: 'overflowX', - nameScrollMode: 'data-scroll-mode-x' - }, - y: { - nameScroll: 'scrollHeight', - nameClient: 'clientHeight', - nameStyle: 'overflowY', - nameScrollMode: 'data-scroll-mode-y' - } - }; + * Returns bounding client rect. + * @return {Rect} Bounding client rect. + */ + getBoundingClientRect() { + // Make valid viewport coordinates: documentElement.getBoundingClientRect returns rect of entire document relative to viewport + return { + left: 0, + top: 0, + width: this.clientWidth, + height: this.clientHeight + }; + } /** + * Scrolls window. + * @param {...mixed} args See window.scrollTo. + */ + scrollTo() { + window.scrollTo.apply(window, arguments); + } +} + +/** + * Default (document) scroller. + */ +const documentScroller = new DocumentScroller(); + +const scrollerHints = { + x: { + nameScroll: 'scrollWidth', + nameClient: 'clientWidth', + nameStyle: 'overflowX', + nameScrollMode: 'data-scroll-mode-x' + }, + y: { + nameScroll: 'scrollHeight', + nameClient: 'clientHeight', + nameStyle: 'overflowY', + nameScrollMode: 'data-scroll-mode-y' + } +}; + +/** * Returns parent element that can be scrolled. If no such, returns document scroller. * * @param {HTMLElement} element - Element for which parent is being searched. * @param {boolean} vertical - Search for vertical scrollable parent. * @param {HTMLElement|DocumentScroller} Parent element that can be scrolled or document scroller. */ - function getScrollableParent(element, vertical) { - if (element) { - const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; +function getScrollableParent(element, vertical) { + if (element) { + const scrollerHint = vertical ? scrollerHints.y : scrollerHints.x; - let parent = element.parentElement; + let parent = element.parentElement; - while (parent && parent !== document.body) { - const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); + while (parent && parent !== document.body) { + const scrollMode = parent.getAttribute(scrollerHint.nameScrollMode); - // Stop on self-scrolled containers - if (scrollMode === 'custom') { - return parent; - } - - const styles = window.getComputedStyle(parent); - - // Stop on fixed parent - if (styles.position === 'fixed') { - return parent; - } - - const overflow = styles[scrollerHint.nameStyle]; - - if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { - return parent; - } - - parent = parent.parentElement; + // Stop on self-scrolled containers + if (scrollMode === 'custom') { + return parent; } - } - return documentScroller; + const styles = window.getComputedStyle(parent); + + // Stop on fixed parent + if (styles.position === 'fixed') { + return parent; + } + + const overflow = styles[scrollerHint.nameStyle]; + + if (overflow === 'scroll' || overflow === 'auto' && parent[scrollerHint.nameScroll] > parent[scrollerHint.nameClient]) { + return parent; + } + + parent = parent.parentElement; + } } - /** + return documentScroller; +} + +/** * @typedef {Object} ScrollerData * @property {number} scrollPos - Current scroll position. * @property {number} scrollSize - Scroll size. @@ -280,34 +278,34 @@ import layoutManager from './layoutManager'; * @property {boolean} custom - Custom scrolling mode. */ - /** +/** * Returns scroller data for specified orientation. * * @param {HTMLElement} scroller - Scroller. * @param {boolean} vertical - Vertical scroller data. * @return {ScrollerData} Scroller data. */ - function getScrollerData(scroller, vertical) { - const data = {}; +function getScrollerData(scroller, vertical) { + const data = {}; - if (!vertical) { - data.scrollPos = scroller.scrollLeft; - data.scrollSize = scroller.scrollWidth; - data.clientSize = scroller.clientWidth; - data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); - } else { - data.scrollPos = scroller.scrollTop; - data.scrollSize = scroller.scrollHeight; - data.clientSize = scroller.clientHeight; - data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); - } - - data.custom = data.mode === 'custom'; - - return data; + if (!vertical) { + data.scrollPos = scroller.scrollLeft; + data.scrollSize = scroller.scrollWidth; + data.clientSize = scroller.clientWidth; + data.mode = scroller.getAttribute(scrollerHints.x.nameScrollMode); + } else { + data.scrollPos = scroller.scrollTop; + data.scrollSize = scroller.scrollHeight; + data.clientSize = scroller.clientHeight; + data.mode = scroller.getAttribute(scrollerHints.y.nameScrollMode); } - /** + data.custom = data.mode === 'custom'; + + return data; +} + +/** * Returns position of child of scroller for specified orientation. * * @param {HTMLElement} scroller - Scroller. @@ -315,18 +313,18 @@ import layoutManager from './layoutManager'; * @param {boolean} vertical - Vertical scroll. * @return {number} Child position. */ - function getScrollerChildPos(scroller, element, vertical) { - const elementRect = element.getBoundingClientRect(); - const scrollerRect = scroller.getBoundingClientRect(); +function getScrollerChildPos(scroller, element, vertical) { + const elementRect = element.getBoundingClientRect(); + const scrollerRect = scroller.getBoundingClientRect(); - if (!vertical) { - return scroller.scrollLeft + elementRect.left - scrollerRect.left; - } else { - return scroller.scrollTop + elementRect.top - scrollerRect.top; - } + if (!vertical) { + return scroller.scrollLeft + elementRect.left - scrollerRect.left; + } else { + return scroller.scrollTop + elementRect.top - scrollerRect.top; } +} - /** +/** * Returns scroll position for element. * * @param {ScrollerData} scrollerData - Scroller data. @@ -335,47 +333,47 @@ import layoutManager from './layoutManager'; * @param {boolean} centered - Scroll to center. * @return {number} Scroll position. */ - function calcScroll(scrollerData, elementPos, elementSize, centered) { - const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; +function calcScroll(scrollerData, elementPos, elementSize, centered) { + const maxScroll = scrollerData.scrollSize - scrollerData.clientSize; - let scroll; + let scroll; - if (centered) { - scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; - } else { - const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); - scroll = scrollerData.scrollPos - delta; - } - - return clamp(Math.round(scroll), 0, maxScroll); + if (centered) { + scroll = elementPos + (elementSize - scrollerData.clientSize) / 2; + } else { + const delta = fitRange(elementPos, elementPos + elementSize - 1, scrollerData.scrollPos, scrollerData.scrollPos + scrollerData.clientSize - 1); + scroll = scrollerData.scrollPos - delta; } - /** + return clamp(Math.round(scroll), 0, maxScroll); +} + +/** * Calls scrollTo function in proper way. * * @param {HTMLElement} scroller - Scroller. * @param {ScrollToOptions} options - Scroll options. */ - function scrollToHelper(scroller, options) { - if ('scrollTo' in scroller) { - if (!supportsScrollToOptions) { - const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); - const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); - scroller.scrollTo(scrollX, scrollY); - } else { - scroller.scrollTo(options); - } - } else if ('scrollLeft' in scroller) { - if (options.left !== undefined) { - scroller.scrollLeft = options.left; - } - if (options.top !== undefined) { - scroller.scrollTop = options.top; - } +function scrollToHelper(scroller, options) { + if ('scrollTo' in scroller) { + if (!supportsScrollToOptions) { + const scrollX = (options.left !== undefined ? options.left : scroller.scrollLeft); + const scrollY = (options.top !== undefined ? options.top : scroller.scrollTop); + scroller.scrollTo(scrollX, scrollY); + } else { + scroller.scrollTo(options); + } + } else if ('scrollLeft' in scroller) { + if (options.left !== undefined) { + scroller.scrollLeft = options.left; + } + if (options.top !== undefined) { + scroller.scrollTop = options.top; } } +} - /** +/** * Performs built-in scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -384,35 +382,35 @@ import layoutManager from './layoutManager'; * @param {number} scrollY - Vertical coordinate. * @param {boolean} smooth - Smooth scrolling. */ - function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - const scrollBehavior = smooth ? 'smooth' : 'instant'; +function builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth) { + const scrollBehavior = smooth ? 'smooth' : 'instant'; - if (xScroller !== yScroller) { - if (xScroller) { - scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior }); - } - if (yScroller) { - scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior }); - } - } else if (xScroller) { - scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior }); + if (xScroller !== yScroller) { + if (xScroller) { + scrollToHelper(xScroller, { left: scrollX, behavior: scrollBehavior }); } + if (yScroller) { + scrollToHelper(yScroller, { top: scrollY, behavior: scrollBehavior }); + } + } else if (xScroller) { + scrollToHelper(xScroller, { left: scrollX, top: scrollY, behavior: scrollBehavior }); } +} - /** +/** * Requested frame for animated scroll. */ - let scrollTimer; +let scrollTimer; - /** +/** * Resets scroll timer to stop scrolling. */ - function resetScrollTimer() { - cancelAnimationFrame(scrollTimer); - scrollTimer = undefined; - } +function resetScrollTimer() { + cancelAnimationFrame(scrollTimer); + scrollTimer = undefined; +} - /** +/** * Performs animated scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -420,43 +418,43 @@ import layoutManager from './layoutManager'; * @param {HTMLElement} yScroller - Vertical scroller. * @param {number} scrollY - Vertical coordinate. */ - function animateScroll(xScroller, scrollX, yScroller, scrollY) { - const ox = xScroller ? xScroller.scrollLeft : scrollX; - const oy = yScroller ? yScroller.scrollTop : scrollY; - const dx = scrollX - ox; - const dy = scrollY - oy; +function animateScroll(xScroller, scrollX, yScroller, scrollY) { + const ox = xScroller ? xScroller.scrollLeft : scrollX; + const oy = yScroller ? yScroller.scrollTop : scrollY; + const dx = scrollX - ox; + const dy = scrollY - oy; - if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { + if (Math.abs(dx) < Epsilon && Math.abs(dy) < Epsilon) { + return; + } + + let start; + + function scrollAnim(currentTimestamp) { + start = start || currentTimestamp; + + let k = Math.min(1, (currentTimestamp - start) / ScrollTime); + + if (k === 1) { + resetScrollTimer(); + builtinScroll(xScroller, scrollX, yScroller, scrollY, false); return; } - let start; + k = ease(k); - function scrollAnim(currentTimestamp) { - start = start || currentTimestamp; + const x = ox + dx * k; + const y = oy + dy * k; - let k = Math.min(1, (currentTimestamp - start) / ScrollTime); - - if (k === 1) { - resetScrollTimer(); - builtinScroll(xScroller, scrollX, yScroller, scrollY, false); - return; - } - - k = ease(k); - - const x = ox + dx * k; - const y = oy + dy * k; - - builtinScroll(xScroller, x, yScroller, y, false); - - scrollTimer = requestAnimationFrame(scrollAnim); - } + builtinScroll(xScroller, x, yScroller, y, false); scrollTimer = requestAnimationFrame(scrollAnim); } - /** + scrollTimer = requestAnimationFrame(scrollAnim); +} + +/** * Performs scroll. * * @param {HTMLElement} xScroller - Horizontal scroller. @@ -465,142 +463,140 @@ import layoutManager from './layoutManager'; * @param {number} scrollY - Vertical coordinate. * @param {boolean} smooth - Smooth scrolling. */ - function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { - resetScrollTimer(); +function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) { + resetScrollTimer(); - if (smooth && useAnimatedScroll()) { - animateScroll(xScroller, scrollX, yScroller, scrollY); - } else { - builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth); - } + if (smooth && useAnimatedScroll()) { + animateScroll(xScroller, scrollX, yScroller, scrollY); + } else { + builtinScroll(xScroller, scrollX, yScroller, scrollY, smooth); } +} - /** +/** * Returns true if smooth scroll must be used. */ - function useSmoothScroll() { - return !!browser.tizen; - } +function useSmoothScroll() { + return !!browser.tizen; +} - /** +/** * Returns true if animated implementation of smooth scroll must be used. */ - function useAnimatedScroll() { - // Add block to force using (or not) of animated implementation +function useAnimatedScroll() { + // Add block to force using (or not) of animated implementation - return !supportsSmoothScroll; - } + return !supportsSmoothScroll; +} - /** +/** * Returns true if scroll manager is enabled. */ - export function isEnabled() { - return layoutManager.tv; - } +export function isEnabled() { + return layoutManager.tv; +} - /** +/** * Scrolls the document to a given position. * * @param {number} scrollX - Horizontal coordinate. * @param {number} scrollY - Vertical coordinate. * @param {boolean} [smooth=false] - Smooth scrolling. */ - export function scrollTo(scrollX, scrollY, smooth) { - smooth = !!smooth; +export function scrollTo(scrollX, scrollY, smooth) { + smooth = !!smooth; - // Scroller is document itself by default - const scroller = getScrollableParent(null, false); + // Scroller is document itself by default + const scroller = getScrollableParent(null, false); - const xScrollerData = getScrollerData(scroller, false); - const yScrollerData = getScrollerData(scroller, true); + const xScrollerData = getScrollerData(scroller, false); + const yScrollerData = getScrollerData(scroller, true); - scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); - scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); + scrollX = clamp(Math.round(scrollX), 0, xScrollerData.scrollSize - xScrollerData.clientSize); + scrollY = clamp(Math.round(scrollY), 0, yScrollerData.scrollSize - yScrollerData.clientSize); - doScroll(scroller, scrollX, scroller, scrollY, smooth); - } + doScroll(scroller, scrollX, scroller, scrollY, smooth); +} - /** +/** * Scrolls the document to a given element. * * @param {HTMLElement} element - Target element of scroll task. * @param {boolean} [smooth=false] - Smooth scrolling. */ - export function scrollToElement(element, smooth) { - smooth = !!smooth; +export function scrollToElement(element, smooth) { + smooth = !!smooth; - let scrollCenterX = true; - let scrollCenterY = true; + let scrollCenterX = true; + let scrollCenterY = true; - const offsetParent = element.offsetParent; + const offsetParent = element.offsetParent; - // In Firefox offsetParent.offsetParent is BODY - const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed'); + // In Firefox offsetParent.offsetParent is BODY + const isFixed = offsetParent && (!offsetParent.offsetParent || window.getComputedStyle(offsetParent).position === 'fixed'); - // Scroll fixed elements to nearest edge (or do not scroll at all) - if (isFixed) { - scrollCenterX = scrollCenterY = false; - } - - let xScroller = getScrollableParent(element, false); - let yScroller = getScrollableParent(element, true); - - const xScrollerData = getScrollerData(xScroller, false); - const yScrollerData = getScrollerData(yScroller, true); - - // Exit, since we have no control over scrolling in this container - if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) { - return; - } - - // Exit, since we have no control over scrolling in these containers - if (xScrollerData.custom && yScrollerData.custom) { - return; - } - - const elementRect = element.getBoundingClientRect(); - - let scrollX = 0; - let scrollY = 0; - - if (!xScrollerData.custom) { - const xPos = getScrollerChildPos(xScroller, element, false); - scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); - } else { - xScroller = null; - } - - if (!yScrollerData.custom) { - const yPos = getScrollerChildPos(yScroller, element, true); - scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); - - // HACK: Scroll to top for top menu because it is hidden - // FIXME: Need a marker to scroll top/bottom - if (isFixed && elementRect.bottom < 0) { - scrollY = 0; - } - - // HACK: Ensure we are at the top - // FIXME: Need a marker to scroll top/bottom - if (scrollY < minimumScrollY() && yScroller === documentScroller) { - scrollY = 0; - } - } else { - yScroller = null; - } - - doScroll(xScroller, scrollX, yScroller, scrollY, smooth); + // Scroll fixed elements to nearest edge (or do not scroll at all) + if (isFixed) { + scrollCenterX = scrollCenterY = false; } - if (isEnabled()) { - dom.addEventListener(window, 'focusin', function(e) { - setTimeout(function() { - scrollToElement(e.target, useSmoothScroll()); - }, 0); - }, { capture: true }); + let xScroller = getScrollableParent(element, false); + let yScroller = getScrollableParent(element, true); + + const xScrollerData = getScrollerData(xScroller, false); + const yScrollerData = getScrollerData(yScroller, true); + + // Exit, since we have no control over scrolling in this container + if (xScroller === yScroller && (xScrollerData.custom || yScrollerData.custom)) { + return; } -/* eslint-enable indent */ + // Exit, since we have no control over scrolling in these containers + if (xScrollerData.custom && yScrollerData.custom) { + return; + } + + const elementRect = element.getBoundingClientRect(); + + let scrollX = 0; + let scrollY = 0; + + if (!xScrollerData.custom) { + const xPos = getScrollerChildPos(xScroller, element, false); + scrollX = calcScroll(xScrollerData, xPos, elementRect.width, scrollCenterX); + } else { + xScroller = null; + } + + if (!yScrollerData.custom) { + const yPos = getScrollerChildPos(yScroller, element, true); + scrollY = calcScroll(yScrollerData, yPos, elementRect.height, scrollCenterY); + + // HACK: Scroll to top for top menu because it is hidden + // FIXME: Need a marker to scroll top/bottom + if (isFixed && elementRect.bottom < 0) { + scrollY = 0; + } + + // HACK: Ensure we are at the top + // FIXME: Need a marker to scroll top/bottom + if (scrollY < minimumScrollY() && yScroller === documentScroller) { + scrollY = 0; + } + } else { + yScroller = null; + } + + doScroll(xScroller, scrollX, yScroller, scrollY, smooth); +} + +if (isEnabled()) { + dom.addEventListener(window, 'focusin', function(e) { + setTimeout(function() { + scrollToElement(e.target, useSmoothScroll()); + }, 0); + }, { capture: true }); +} export default { isEnabled: isEnabled, diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index 62f43a3436..7c430be567 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -1,5 +1,3 @@ -/* eslint-disable indent */ - /** * Module shortcuts. * @module components/shortcuts @@ -14,385 +12,383 @@ import recordingHelper from './recordingcreator/recordinghelper'; import ServerConnections from './ServerConnections'; import toast from './toast/toast'; - function playAllFromHere(card, serverId, queue) { - const parent = card.parentNode; - const className = card.classList.length ? (`.${card.classList[0]}`) : ''; - const cards = parent.querySelectorAll(`${className}[data-id]`); +function playAllFromHere(card, serverId, queue) { + const parent = card.parentNode; + const className = card.classList.length ? (`.${card.classList[0]}`) : ''; + const cards = parent.querySelectorAll(`${className}[data-id]`); - const ids = []; + const ids = []; - let foundCard = false; - let startIndex; + let foundCard = false; + let startIndex; - for (let i = 0, length = cards.length; i < length; i++) { - if (cards[i] === card) { - foundCard = true; - startIndex = i; - } - if (foundCard || !queue) { - ids.push(cards[i].getAttribute('data-id')); + for (let i = 0, length = cards.length; i < length; i++) { + if (cards[i] === card) { + foundCard = true; + startIndex = i; + } + if (foundCard || !queue) { + ids.push(cards[i].getAttribute('data-id')); + } + } + + const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); + if (itemsContainer && itemsContainer.fetchData) { + const queryOptions = queue ? { StartIndex: startIndex } : {}; + + return itemsContainer.fetchData(queryOptions).then(result => { + if (queue) { + return playbackManager.queue({ + items: result.Items + }); + } else { + return playbackManager.play({ + items: result.Items, + startIndex: startIndex + }); } + }); + } + + if (!ids.length) { + return; + } + + if (queue) { + return playbackManager.queue({ + ids: ids, + serverId: serverId + }); + } else { + return playbackManager.play({ + ids: ids, + serverId: serverId, + startIndex: startIndex + }); + } +} + +function showProgramDialog(item) { + import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => { + recordingCreator.show(item.Id, item.ServerId); + }); +} + +function getItem(button) { + button = dom.parentWithAttribute(button, 'data-id'); + const serverId = button.getAttribute('data-serverid'); + const id = button.getAttribute('data-id'); + const type = button.getAttribute('data-type'); + + const apiClient = ServerConnections.getApiClient(serverId); + + if (type === 'Timer') { + return apiClient.getLiveTvTimer(id); + } + if (type === 'SeriesTimer') { + return apiClient.getLiveTvSeriesTimer(id); + } + return apiClient.getItem(apiClient.getCurrentUserId(), id); +} + +function notifyRefreshNeeded(childElement, itemsContainer) { + itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer'); + + if (itemsContainer) { + itemsContainer.notifyRefreshNeeded(true); + } +} + +function showContextMenu(card, options) { + getItem(card).then(item => { + const playlistId = card.getAttribute('data-playlistid'); + const collectionId = card.getAttribute('data-collectionid'); + + if (playlistId) { + const elem = dom.parentWithAttribute(card, 'data-playlistitemid'); + item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; } - const itemsContainer = dom.parentWithClass(card, 'itemsContainer'); - if (itemsContainer && itemsContainer.fetchData) { - const queryOptions = queue ? { StartIndex: startIndex } : {}; + import('./itemContextMenu').then((itemContextMenu) => { + ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { + itemContextMenu.show(Object.assign({ + item: item, + play: true, + queue: true, + playAllFromHere: !item.IsFolder, + queueAllFromHere: !item.IsFolder, + playlistId: playlistId, + collectionId: collectionId, + user: user - return itemsContainer.fetchData(queryOptions).then(result => { - if (queue) { - return playbackManager.queue({ - items: result.Items - }); - } else { - return playbackManager.play({ - items: result.Items, - startIndex: startIndex - }); - } + }, options || {})).then(result => { + if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { + executeAction(card, options.positionTo, result.command); + } else if (result.updated || result.deleted) { + notifyRefreshNeeded(card, options.itemsContainer); + } + }); }); - } + }); + }); +} - if (!ids.length) { - return; +function getItemInfoFromCard(card) { + return { + Type: card.getAttribute('data-type'), + Id: card.getAttribute('data-id'), + TimerId: card.getAttribute('data-timerid'), + CollectionType: card.getAttribute('data-collectiontype'), + ChannelId: card.getAttribute('data-channelid'), + SeriesId: card.getAttribute('data-seriesid'), + ServerId: card.getAttribute('data-serverid'), + MediaType: card.getAttribute('data-mediatype'), + Path: card.getAttribute('data-path'), + IsFolder: card.getAttribute('data-isfolder') === 'true', + StartDate: card.getAttribute('data-startdate'), + EndDate: card.getAttribute('data-enddate'), + UserData: { + PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10) } + }; +} - if (queue) { - return playbackManager.queue({ - ids: ids, +function showPlayMenu(card, target) { + const item = getItemInfoFromCard(card); + + import('./playmenu').then((playMenu) => { + playMenu.show({ + + item: item, + positionTo: target + }); + }); +} + +function executeAction(card, target, action) { + target = target || card; + + let id = card.getAttribute('data-id'); + + if (!id) { + card = dom.parentWithAttribute(card, 'data-id'); + id = card.getAttribute('data-id'); + } + + const item = getItemInfoFromCard(card); + + const serverId = item.ServerId; + const type = item.Type; + + const playableItemId = type === 'Program' ? item.ChannelId : item.Id; + + if (item.MediaType === 'Photo' && action === 'link') { + action = 'play'; + } + + if (action === 'link') { + appRouter.showItem(item, { + context: card.getAttribute('data-context'), + parentId: card.getAttribute('data-parentid') + }); + } else if (action === 'programdialog') { + showProgramDialog(item); + } else if (action === 'instantmix') { + playbackManager.instantMix({ + Id: playableItemId, + ServerId: serverId + }); + } else if (action === 'play' || action === 'resume') { + const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10); + + if (playbackManager.canPlay(item)) { + playbackManager.play({ + ids: [playableItemId], + startPositionTicks: startPositionTicks, serverId: serverId }); } else { - return playbackManager.play({ - ids: ids, - serverId: serverId, - startIndex: startIndex + console.warn('Unable to play item', item); + } + } else if (action === 'queue') { + if (playbackManager.isPlaying()) { + playbackManager.queue({ + ids: [playableItemId], + serverId: serverId + }); + toast(globalize.translate('MediaQueued')); + } else { + playbackManager.queue({ + ids: [playableItemId], + serverId: serverId }); } - } + } else if (action === 'playallfromhere') { + playAllFromHere(card, serverId); + } else if (action === 'queueallfromhere') { + playAllFromHere(card, serverId, true); + } else if (action === 'setplaylistindex') { + playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid')); + } else if (action === 'record') { + onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); + } else if (action === 'menu') { + const options = target.getAttribute('data-playoptions') === 'false' ? + { + shuffle: false, + instantMix: false, + play: false, + playAllFromHere: false, + queue: false, + queueAllFromHere: false + } : + {}; - function showProgramDialog(item) { - import('./recordingcreator/recordingcreator').then(({ default:recordingCreator }) => { - recordingCreator.show(item.Id, item.ServerId); + options.positionTo = target; + + showContextMenu(card, options); + } else if (action === 'playmenu') { + showPlayMenu(card, target); + } else if (action === 'edit') { + getItem(target).then(itemToEdit => { + editItem(itemToEdit, serverId); }); + } else if (action === 'playtrailer') { + getItem(target).then(playTrailer); + } else if (action === 'addtoplaylist') { + getItem(target).then(addToPlaylist); + } else if (action === 'custom') { + const customAction = target.getAttribute('data-customaction'); + + card.dispatchEvent(new CustomEvent(`action-${customAction}`, { + detail: { + playlistItemId: card.getAttribute('data-playlistitemid') + }, + cancelable: false, + bubbles: true + })); } +} - function getItem(button) { - button = dom.parentWithAttribute(button, 'data-id'); - const serverId = button.getAttribute('data-serverid'); - const id = button.getAttribute('data-id'); - const type = button.getAttribute('data-type'); +function addToPlaylist(item) { + import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor().show({ + items: [item.Id], + serverId: item.ServerId - const apiClient = ServerConnections.getApiClient(serverId); - - if (type === 'Timer') { - return apiClient.getLiveTvTimer(id); - } - if (type === 'SeriesTimer') { - return apiClient.getLiveTvSeriesTimer(id); - } - return apiClient.getItem(apiClient.getCurrentUserId(), id); - } - - function notifyRefreshNeeded(childElement, itemsContainer) { - itemsContainer = itemsContainer || dom.parentWithAttribute(childElement, 'is', 'emby-itemscontainer'); - - if (itemsContainer) { - itemsContainer.notifyRefreshNeeded(true); - } - } - - function showContextMenu(card, options) { - getItem(card).then(item => { - const playlistId = card.getAttribute('data-playlistid'); - const collectionId = card.getAttribute('data-collectionid'); - - if (playlistId) { - const elem = dom.parentWithAttribute(card, 'data-playlistitemid'); - item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; - } - - import('./itemContextMenu').then((itemContextMenu) => { - ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { - itemContextMenu.show(Object.assign({ - item: item, - play: true, - queue: true, - playAllFromHere: !item.IsFolder, - queueAllFromHere: !item.IsFolder, - playlistId: playlistId, - collectionId: collectionId, - user: user - - }, options || {})).then(result => { - if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { - executeAction(card, options.positionTo, result.command); - } else if (result.updated || result.deleted) { - notifyRefreshNeeded(card, options.itemsContainer); - } - }); - }); - }); }); - } + }); +} - function getItemInfoFromCard(card) { - return { - Type: card.getAttribute('data-type'), - Id: card.getAttribute('data-id'), - TimerId: card.getAttribute('data-timerid'), - CollectionType: card.getAttribute('data-collectiontype'), - ChannelId: card.getAttribute('data-channelid'), - SeriesId: card.getAttribute('data-seriesid'), - ServerId: card.getAttribute('data-serverid'), - MediaType: card.getAttribute('data-mediatype'), - Path: card.getAttribute('data-path'), - IsFolder: card.getAttribute('data-isfolder') === 'true', - StartDate: card.getAttribute('data-startdate'), - EndDate: card.getAttribute('data-enddate'), - UserData: { - PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10) - } - }; - } +function playTrailer(item) { + const apiClient = ServerConnections.getApiClient(item.ServerId); - function showPlayMenu(card, target) { - const item = getItemInfoFromCard(card); + apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { + playbackManager.play({ items: trailers }); + }); +} - import('./playmenu').then((playMenu) => { - playMenu.show({ +function editItem(item, serverId) { + const apiClient = ServerConnections.getApiClient(serverId); - item: item, - positionTo: target - }); - }); - } + return new Promise((resolve, reject) => { + const currentServerId = apiClient.serverInfo().Id; - function executeAction(card, target, action) { - target = target || card; - - let id = card.getAttribute('data-id'); - - if (!id) { - card = dom.parentWithAttribute(card, 'data-id'); - id = card.getAttribute('data-id'); - } - - const item = getItemInfoFromCard(card); - - const serverId = item.ServerId; - const type = item.Type; - - const playableItemId = type === 'Program' ? item.ChannelId : item.Id; - - if (item.MediaType === 'Photo' && action === 'link') { - action = 'play'; - } - - if (action === 'link') { - appRouter.showItem(item, { - context: card.getAttribute('data-context'), - parentId: card.getAttribute('data-parentid') - }); - } else if (action === 'programdialog') { - showProgramDialog(item); - } else if (action === 'instantmix') { - playbackManager.instantMix({ - Id: playableItemId, - ServerId: serverId - }); - } else if (action === 'play' || action === 'resume') { - const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10); - - if (playbackManager.canPlay(item)) { - playbackManager.play({ - ids: [playableItemId], - startPositionTicks: startPositionTicks, - serverId: serverId + if (item.Type === 'Timer') { + if (item.ProgramId) { + import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { + recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject); }); } else { - console.warn('Unable to play item', item); - } - } else if (action === 'queue') { - if (playbackManager.isPlaying()) { - playbackManager.queue({ - ids: [playableItemId], - serverId: serverId - }); - toast(globalize.translate('MediaQueued')); - } else { - playbackManager.queue({ - ids: [playableItemId], - serverId: serverId + import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { + recordingEditor.show(item.Id, currentServerId).then(resolve, reject); }); } - } else if (action === 'playallfromhere') { - playAllFromHere(card, serverId); - } else if (action === 'queueallfromhere') { - playAllFromHere(card, serverId, true); - } else if (action === 'setplaylistindex') { - playbackManager.setCurrentPlaylistItem(card.getAttribute('data-playlistitemid')); - } else if (action === 'record') { - onRecordCommand(serverId, id, type, card.getAttribute('data-timerid'), card.getAttribute('data-seriestimerid')); - } else if (action === 'menu') { - const options = target.getAttribute('data-playoptions') === 'false' ? - { - shuffle: false, - instantMix: false, - play: false, - playAllFromHere: false, - queue: false, - queueAllFromHere: false - } : - {}; - - options.positionTo = target; - - showContextMenu(card, options); - } else if (action === 'playmenu') { - showPlayMenu(card, target); - } else if (action === 'edit') { - getItem(target).then(itemToEdit => { - editItem(itemToEdit, serverId); + } else { + import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { + metadataEditor.show(item.Id, currentServerId).then(resolve, reject); }); - } else if (action === 'playtrailer') { - getItem(target).then(playTrailer); - } else if (action === 'addtoplaylist') { - getItem(target).then(addToPlaylist); - } else if (action === 'custom') { - const customAction = target.getAttribute('data-customaction'); + } + }); +} - card.dispatchEvent(new CustomEvent(`action-${customAction}`, { - detail: { - playlistItemId: card.getAttribute('data-playlistitemid') - }, - cancelable: false, - bubbles: true - })); +function onRecordCommand(serverId, id, type, timerId, seriesTimerId) { + if (type === 'Program' || timerId || seriesTimerId) { + const programId = type === 'Program' ? id : null; + recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); + } +} + +export function onClick(e) { + const card = dom.parentWithClass(e.target, 'itemAction'); + + if (card) { + let actionElement = card; + let action = actionElement.getAttribute('data-action'); + + if (!action) { + actionElement = dom.parentWithAttribute(actionElement, 'data-action'); + if (actionElement) { + action = actionElement.getAttribute('data-action'); + } + } + + if (action) { + executeAction(card, actionElement, action); + + e.preventDefault(); + e.stopPropagation(); + return false; } } +} - function addToPlaylist(item) { - import('./playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor().show({ - items: [item.Id], - serverId: item.ServerId +function onCommand(e) { + const cmd = e.detail.command; - }); - }); - } - - function playTrailer(item) { - const apiClient = ServerConnections.getApiClient(item.ServerId); - - apiClient.getLocalTrailers(apiClient.getCurrentUserId(), item.Id).then(trailers => { - playbackManager.play({ items: trailers }); - }); - } - - function editItem(item, serverId) { - const apiClient = ServerConnections.getApiClient(serverId); - - return new Promise((resolve, reject) => { - const currentServerId = apiClient.serverInfo().Id; - - if (item.Type === 'Timer') { - if (item.ProgramId) { - import('./recordingcreator/recordingcreator').then(({ default: recordingCreator }) => { - recordingCreator.show(item.ProgramId, currentServerId).then(resolve, reject); - }); - } else { - import('./recordingcreator/recordingeditor').then(({ default: recordingEditor }) => { - recordingEditor.show(item.Id, currentServerId).then(resolve, reject); - }); - } - } else { - import('./metadataEditor/metadataEditor').then(({ default: metadataEditor }) => { - metadataEditor.show(item.Id, currentServerId).then(resolve, reject); - }); - } - }); - } - - function onRecordCommand(serverId, id, type, timerId, seriesTimerId) { - if (type === 'Program' || timerId || seriesTimerId) { - const programId = type === 'Program' ? id : null; - recordingHelper.toggleRecording(serverId, programId, timerId, seriesTimerId); - } - } - - export function onClick(e) { - const card = dom.parentWithClass(e.target, 'itemAction'); + if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { + const target = e.target; + const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); if (card) { - let actionElement = card; - let action = actionElement.getAttribute('data-action'); - - if (!action) { - actionElement = dom.parentWithAttribute(actionElement, 'data-action'); - if (actionElement) { - action = actionElement.getAttribute('data-action'); - } - } - - if (action) { - executeAction(card, actionElement, action); - - e.preventDefault(); - e.stopPropagation(); - return false; - } + e.preventDefault(); + e.stopPropagation(); + executeAction(card, card, cmd); } } +} - function onCommand(e) { - const cmd = e.detail.command; +export function on(context, options) { + options = options || {}; - if (cmd === 'play' || cmd === 'resume' || cmd === 'record' || cmd === 'menu' || cmd === 'info') { - const target = e.target; - const card = dom.parentWithClass(target, 'itemAction') || dom.parentWithAttribute(target, 'data-id'); - - if (card) { - e.preventDefault(); - e.stopPropagation(); - executeAction(card, card, cmd); - } - } + if (options.click !== false) { + context.addEventListener('click', onClick); } - export function on(context, options) { - options = options || {}; + if (options.command !== false) { + inputManager.on(context, onCommand); + } +} - if (options.click !== false) { - context.addEventListener('click', onClick); - } +export function off(context, options) { + options = options || {}; - if (options.command !== false) { - inputManager.on(context, onCommand); - } + context.removeEventListener('click', onClick); + + if (options.command !== false) { + inputManager.off(context, onCommand); + } +} + +export function getShortcutAttributesHtml(item, serverId) { + 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}"`; + + const collectionType = item.CollectionType; + if (collectionType) { + html += ` data-collectiontype="${collectionType}"`; } - export function off(context, options) { - options = options || {}; - - context.removeEventListener('click', onClick); - - if (options.command !== false) { - inputManager.off(context, onCommand); - } - } - - export function getShortcutAttributesHtml(item, serverId) { - 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}"`; - - const collectionType = item.CollectionType; - if (collectionType) { - html += ` data-collectiontype="${collectionType}"`; - } - - return html; - } - -/* eslint-enable indent */ + return html; +} export default { on: on, diff --git a/src/components/tabbedview/tabbedview.js b/src/components/tabbedview/tabbedview.js new file mode 100644 index 0000000000..8c865ec9f6 --- /dev/null +++ b/src/components/tabbedview/tabbedview.js @@ -0,0 +1,114 @@ +import { clearBackdrop } from '../backdrop/backdrop'; +import * as mainTabsManager from '../maintabsmanager'; +import layoutManager from '../layoutManager'; +import '../../elements/emby-tabs/emby-tabs'; +import LibraryMenu from '../../scripts/libraryMenu'; + +function onViewDestroy() { + const tabControllers = this.tabControllers; + + if (tabControllers) { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); + + this.tabControllers = null; + } + + this.view = null; + this.params = null; + this.currentTabController = null; + this.initialTabIndex = null; +} + +class TabbedView { + constructor(view, params) { + this.tabControllers = []; + this.view = view; + this.params = params; + + const self = this; + + let currentTabIndex = parseInt(params.tab || this.getDefaultTabIndex(params.parentId), 10); + this.initialTabIndex = currentTabIndex; + + function validateTabLoad(index) { + return self.validateTabLoad ? self.validateTabLoad(index) : Promise.resolve(); + } + + function loadTab(index, previousIndex) { + validateTabLoad(index).then(function () { + self.getTabController(index).then(function (controller) { + const refresh = !controller.refreshed; + + controller.onResume({ + autoFocus: previousIndex == null && layoutManager.tv, + refresh: refresh + }); + + controller.refreshed = true; + + currentTabIndex = index; + self.currentTabController = controller; + }); + }); + } + + function getTabContainers() { + return view.querySelectorAll('.tabContent'); + } + + function onTabChange(e) { + const newIndex = parseInt(e.detail.selectedTabIndex, 10); + const previousIndex = e.detail.previousIndex; + + const previousTabController = previousIndex == null ? null : self.tabControllers[previousIndex]; + if (previousTabController && previousTabController.onPause) { + previousTabController.onPause(); + } + + loadTab(newIndex, previousIndex); + } + + view.addEventListener('viewbeforehide', this.onPause.bind(this)); + + view.addEventListener('viewbeforeshow', function () { + mainTabsManager.setTabs(view, currentTabIndex, self.getTabs, getTabContainers, null, onTabChange, false); + }); + + view.addEventListener('viewshow', function (e) { + self.onResume(e.detail); + }); + + view.addEventListener('viewdestroy', onViewDestroy.bind(this)); + } + + onResume() { + this.setTitle(); + clearBackdrop(); + + const currentTabController = this.currentTabController; + + if (!currentTabController) { + mainTabsManager.selectedTabIndex(this.initialTabIndex); + } else if (currentTabController && currentTabController.onResume) { + currentTabController.onResume({}); + } + } + + onPause() { + const currentTabController = this.currentTabController; + + if (currentTabController && currentTabController.onPause) { + currentTabController.onPause(); + } + } + + setTitle() { + LibraryMenu.setTitle(''); + } +} + +export default TabbedView; diff --git a/src/components/upnextdialog/upnextdialog.js b/src/components/upnextdialog/upnextdialog.js index 2a37fcbe45..2434744f24 100644 --- a/src/components/upnextdialog/upnextdialog.js +++ b/src/components/upnextdialog/upnextdialog.js @@ -10,199 +10,197 @@ import './upnextdialog.scss'; import '../../elements/emby-button/emby-button'; import '../../styles/flexstyles.scss'; -/* eslint-disable indent */ +const transitionEndEventName = dom.whichTransitionEvent(); - const transitionEndEventName = dom.whichTransitionEvent(); +function getHtml() { + let html = ''; - function getHtml() { - let html = ''; + html += '
'; - html += '
'; + html += '

 

'; - html += '

 

'; + html += '

'; - html += '

'; + html += '
'; + html += '
'; - html += '
'; - html += '
'; + html += '
'; - html += '
'; + html += ''; - html += ''; + html += ''; - html += ''; + // buttons + html += '
'; - // buttons - html += '
'; + // main + html += '
'; - // main - html += '
'; + return html; +} - return html; +function setNextVideoText() { + const instance = this; + + const elem = instance.options.parent; + + const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); + + console.debug('up next seconds remaining: ' + secondsRemaining); + + const timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; + + const nextVideoText = instance.itemType === 'Episode' ? + globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : + globalize.translate('HeaderNextVideoPlayingInValue', timeText); + + elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText; +} + +function fillItem(item) { + const instance = this; + + const elem = instance.options.parent; + + elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { + criticRating: true, + originalAirDate: false, + starRating: true, + subtitles: false + }); + + let title = itemHelper.getDisplayName(item); + if (item.SeriesName) { + title = item.SeriesName + ' - ' + title; } - function setNextVideoText() { - const instance = this; + elem.querySelector('.upNextDialog-title').innerText = title || ''; - const elem = instance.options.parent; + instance.itemType = item.Type; - const secondsRemaining = Math.max(Math.round(getTimeRemainingMs(instance) / 1000), 0); + instance.show(); +} - console.debug('up next seconds remaining: ' + secondsRemaining); +function clearCountdownTextTimeout(instance) { + if (instance._countdownTextTimeout) { + clearInterval(instance._countdownTextTimeout); + instance._countdownTextTimeout = null; + } +} - const timeText = '' + globalize.translate('HeaderSecondsValue', secondsRemaining) + ''; +async function onStartNowClick() { + const options = this.options; - const nextVideoText = instance.itemType === 'Episode' ? - globalize.translate('HeaderNextEpisodePlayingInValue', timeText) : - globalize.translate('HeaderNextVideoPlayingInValue', timeText); + if (options) { + const player = options.player; - elem.querySelector('.upNextDialog-nextVideoText').innerHTML = nextVideoText; + await this.hide(); + + playbackManager.nextTrack(player); + } +} + +function init(instance, options) { + options.parent.innerHTML = getHtml(); + + options.parent.classList.add('hide'); + options.parent.classList.add('upNextDialog'); + options.parent.classList.add('upNextDialog-hidden'); + + fillItem.call(instance, options.nextItem); + + options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance)); + options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance)); +} + +function clearHideAnimationEventListeners(instance, elem) { + const fn = instance._onHideAnimationComplete; + + if (fn) { + dom.removeEventListener(elem, transitionEndEventName, fn, { + once: true + }); + } +} + +function onHideAnimationComplete(e) { + const instance = this; + const elem = e.target; + + elem.classList.add('hide'); + + clearHideAnimationEventListeners(instance, elem); + Events.trigger(instance, 'hide'); +} + +async function hideComingUpNext() { + const instance = this; + clearCountdownTextTimeout(this); + + if (!instance.options) { + return; } - function fillItem(item) { - const instance = this; + const elem = instance.options.parent; - const elem = instance.options.parent; + if (!elem) { + return; + } - elem.querySelector('.upNextDialog-mediainfo').innerHTML = mediaInfo.getPrimaryMediaInfoHtml(item, { - criticRating: false, - originalAirDate: false, - starRating: false, - subtitles: false + clearHideAnimationEventListeners(this, elem); + + if (elem.classList.contains('upNextDialog-hidden')) { + return; + } + + const fn = onHideAnimationComplete.bind(instance); + instance._onHideAnimationComplete = fn; + + const transitionEvent = await new Promise((resolve) => { + dom.addEventListener(elem, transitionEndEventName, resolve, { + once: true }); - let title = itemHelper.getDisplayName(item); - if (item.SeriesName) { - title = item.SeriesName + ' - ' + title; - } + // trigger a reflow to force it to animate again + void elem.offsetWidth; - elem.querySelector('.upNextDialog-title').innerText = title || ''; + elem.classList.add('upNextDialog-hidden'); + }); - instance.itemType = item.Type; + instance._onHideAnimationComplete(transitionEvent); +} - instance.show(); - } +function getTimeRemainingMs(instance) { + const options = instance.options; + if (options) { + const runtimeTicks = playbackManager.duration(options.player); - function clearCountdownTextTimeout(instance) { - if (instance._countdownTextTimeout) { - clearInterval(instance._countdownTextTimeout); - instance._countdownTextTimeout = null; + if (runtimeTicks) { + const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000; + + return Math.round(timeRemainingTicks / 10000); } } - async function onStartNowClick() { - const options = this.options; + return 0; +} - if (options) { - const player = options.player; +function startComingUpNextHideTimer(instance) { + const timeRemainingMs = getTimeRemainingMs(instance); - await this.hide(); - - playbackManager.nextTrack(player); - } + if (timeRemainingMs <= 0) { + return; } - function init(instance, options) { - options.parent.innerHTML = getHtml(); + setNextVideoText.call(instance); + clearCountdownTextTimeout(instance); - options.parent.classList.add('hide'); - options.parent.classList.add('upNextDialog'); - options.parent.classList.add('upNextDialog-hidden'); - - fillItem.call(instance, options.nextItem); - - options.parent.querySelector('.btnHide').addEventListener('click', instance.hide.bind(instance)); - options.parent.querySelector('.btnStartNow').addEventListener('click', onStartNowClick.bind(instance)); - } - - function clearHideAnimationEventListeners(instance, elem) { - const fn = instance._onHideAnimationComplete; - - if (fn) { - dom.removeEventListener(elem, transitionEndEventName, fn, { - once: true - }); - } - } - - function onHideAnimationComplete(e) { - const instance = this; - const elem = e.target; - - elem.classList.add('hide'); - - clearHideAnimationEventListeners(instance, elem); - Events.trigger(instance, 'hide'); - } - - async function hideComingUpNext() { - const instance = this; - clearCountdownTextTimeout(this); - - if (!instance.options) { - return; - } - - const elem = instance.options.parent; - - if (!elem) { - return; - } - - clearHideAnimationEventListeners(this, elem); - - if (elem.classList.contains('upNextDialog-hidden')) { - return; - } - - const fn = onHideAnimationComplete.bind(instance); - instance._onHideAnimationComplete = fn; - - const transitionEvent = await new Promise((resolve) => { - dom.addEventListener(elem, transitionEndEventName, resolve, { - once: true - }); - - // trigger a reflow to force it to animate again - void elem.offsetWidth; - - elem.classList.add('upNextDialog-hidden'); - }); - - instance._onHideAnimationComplete(transitionEvent); - } - - function getTimeRemainingMs(instance) { - const options = instance.options; - if (options) { - const runtimeTicks = playbackManager.duration(options.player); - - if (runtimeTicks) { - const timeRemainingTicks = runtimeTicks - playbackManager.currentTime(options.player) * 10000; - - return Math.round(timeRemainingTicks / 10000); - } - } - - return 0; - } - - function startComingUpNextHideTimer(instance) { - const timeRemainingMs = getTimeRemainingMs(instance); - - if (timeRemainingMs <= 0) { - return; - } - - setNextVideoText.call(instance); - clearCountdownTextTimeout(instance); - - instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400); - } + instance._countdownTextTimeout = setInterval(setNextVideoText.bind(instance), 400); +} class UpNextDialog { constructor(options) { @@ -243,4 +241,3 @@ class UpNextDialog { export default UpNextDialog; -/* eslint-enable indent */ diff --git a/src/components/viewContainer.js b/src/components/viewContainer.js index 82e7ef40c7..a5547517a7 100644 --- a/src/components/viewContainer.js +++ b/src/components/viewContainer.js @@ -2,243 +2,252 @@ import { importModule } from '@uupaa/dynamic-import-polyfill'; import './viewManager/viewContainer.scss'; import Dashboard from '../utils/dashboard'; -/* eslint-disable indent */ +const getMainAnimatedPages = () => { + if (!mainAnimatedPages) { + mainAnimatedPages = document.querySelector('.mainAnimatedPages'); + } - function setControllerClass(view, options) { - if (options.controllerFactory) { - return Promise.resolve(); - } - - let controllerUrl = view.getAttribute('data-controller'); - - if (controllerUrl) { - if (controllerUrl.indexOf('__plugin/') === 0) { - controllerUrl = controllerUrl.substring('__plugin/'.length); - } - - controllerUrl = Dashboard.getPluginUrl(controllerUrl); - const apiUrl = ApiClient.getUrl('/web/' + controllerUrl); - return importModule(apiUrl).then((ControllerFactory) => { - options.controllerFactory = ControllerFactory; - }); - } + return mainAnimatedPages; +}; +function setControllerClass(view, options) { + if (options.controllerFactory) { return Promise.resolve(); } - export function loadView(options) { - if (!options.cancel) { + let controllerUrl = view.getAttribute('data-controller'); + + if (controllerUrl) { + if (controllerUrl.indexOf('__plugin/') === 0) { + controllerUrl = controllerUrl.substring('__plugin/'.length); + } + + controllerUrl = Dashboard.getPluginUrl(controllerUrl); + const apiUrl = ApiClient.getUrl('/web/' + controllerUrl); + return importModule(apiUrl).then((ControllerFactory) => { + options.controllerFactory = ControllerFactory; + }); + } + + return Promise.resolve(); +} + +export function loadView(options) { + if (!options.cancel) { + const selected = selectedPageIndex; + const previousAnimatable = selected === -1 ? null : allPages[selected]; + let pageIndex = selected + 1; + + if (pageIndex >= pageContainerCount) { + pageIndex = 0; + } + + const isPluginpage = options.url.includes('configurationpage'); + const newViewInfo = normalizeNewView(options, isPluginpage); + const newView = newViewInfo.elem; + + const currentPage = allPages[pageIndex]; + + if (currentPage) { + triggerDestroy(currentPage); + } + + let view = newView; + + if (typeof view == 'string') { + view = document.createElement('div'); + view.innerHTML = newView; + } + + view.classList.add('mainAnimatedPage'); + + if (!getMainAnimatedPages()) { + console.warn('[viewContainer] main animated pages element is not present'); + return; + } + + if (currentPage) { + if (newViewInfo.hasScript && window.$) { + mainAnimatedPages.removeChild(currentPage); + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.replaceChild(view, currentPage); + } + } else { + if (newViewInfo.hasScript && window.$) { + view = $(view).appendTo(mainAnimatedPages)[0]; + } else { + mainAnimatedPages.appendChild(view); + } + } + + if (options.type) { + view.setAttribute('data-type', options.type); + } + + const properties = []; + + if (options.fullscreen) { + properties.push('fullscreen'); + } + + if (properties.length) { + view.setAttribute('data-properties', properties.join(',')); + } + + allPages[pageIndex] = view; + + return setControllerClass(view, options) + // Timeout for polyfilled CustomElements (webOS 1.2) + .then(() => new Promise((resolve) => setTimeout(resolve, 0))) + .then(() => { + if (onBeforeChange) { + onBeforeChange(view, false, options); + } + + beforeAnimate(allPages, pageIndex, selected); + selectedPageIndex = pageIndex; + currentUrls[pageIndex] = options.url; + + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, pageIndex); + } + + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; + } + + return view; + }); + } +} + +function parseHtml(html, hasScript) { + if (hasScript) { + html = html + .replaceAll('\x3c!----\x3e', ''); + } + + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + return wrapper.querySelector('div[data-role="page"]'); +} + +function normalizeNewView(options, isPluginpage) { + const viewHtml = options.view; + + if (viewHtml.indexOf('data-role="page"') === -1) { + return viewHtml; + } + + let hasScript = viewHtml.indexOf('= pageContainerCount) { - pageIndex = 0; - } - - const isPluginpage = options.url.includes('configurationpage'); - const newViewInfo = normalizeNewView(options, isPluginpage); - const newView = newViewInfo.elem; - - const currentPage = allPages[pageIndex]; - - if (currentPage) { - triggerDestroy(currentPage); - } - - let view = newView; - - if (typeof view == 'string') { - view = document.createElement('div'); - view.innerHTML = newView; - } - - view.classList.add('mainAnimatedPage'); - - if (currentPage) { - if (newViewInfo.hasScript && window.$) { - mainAnimatedPages.removeChild(currentPage); - view = $(view).appendTo(mainAnimatedPages)[0]; - } else { - mainAnimatedPages.replaceChild(view, currentPage); - } - } else { - if (newViewInfo.hasScript && window.$) { - view = $(view).appendTo(mainAnimatedPages)[0]; - } else { - mainAnimatedPages.appendChild(view); - } - } - - if (options.type) { - view.setAttribute('data-type', options.type); - } - - const properties = []; - - if (options.fullscreen) { - properties.push('fullscreen'); - } - - if (properties.length) { - view.setAttribute('data-properties', properties.join(',')); - } - - allPages[pageIndex] = view; - - return setControllerClass(view, options) - // Timeout for polyfilled CustomElements (webOS 1.2) - .then(() => new Promise((resolve) => setTimeout(resolve, 0))) - .then(() => { - if (onBeforeChange) { - onBeforeChange(view, false, options); - } - - beforeAnimate(allPages, pageIndex, selected); - selectedPageIndex = pageIndex; - currentUrls[pageIndex] = options.url; - - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, pageIndex); - } - - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - return view; - }); - } - } - - function parseHtml(html, hasScript) { - if (hasScript) { - html = html - .replaceAll('\x3c!----\x3e', ''); - } - - const wrapper = document.createElement('div'); - wrapper.innerHTML = html; - return wrapper.querySelector('div[data-role="page"]'); - } - - function normalizeNewView(options, isPluginpage) { - const viewHtml = options.view; - - if (viewHtml.indexOf('data-role="page"') === -1) { - return viewHtml; - } - - let hasScript = viewHtml.indexOf(' { + if (onBeforeChange) { + onBeforeChange(view, true, options); } - const selected = selectedPageIndex; - const previousAnimatable = selected === -1 ? null : allPages[selected]; - return setControllerClass(view, options).then(() => { - if (onBeforeChange) { - onBeforeChange(view, true, options); - } + beforeAnimate(allPages, index, selected); + animatable.classList.remove('hide'); + selectedPageIndex = index; - beforeAnimate(allPages, index, selected); - animatable.classList.remove('hide'); - selectedPageIndex = index; + if (!options.cancel && previousAnimatable) { + afterAnimate(allPages, index); + } - if (!options.cancel && previousAnimatable) { - afterAnimate(allPages, index); - } + if (window.$) { + $.mobile = $.mobile || {}; + $.mobile.activePage = view; + } - if (window.$) { - $.mobile = $.mobile || {}; - $.mobile.activePage = view; - } - - return view; - }); - } + return view; + }); } - - return Promise.reject(); } - function triggerDestroy(view) { - view.dispatchEvent(new CustomEvent('viewdestroy', {})); - } + return Promise.reject(); +} - export function reset() { - allPages = []; - currentUrls = []; - mainAnimatedPages.innerHTML = ''; - selectedPageIndex = -1; - } +function triggerDestroy(view) { + view.dispatchEvent(new CustomEvent('viewdestroy', {})); +} - let onBeforeChange; - const mainAnimatedPages = document.querySelector('.mainAnimatedPages'); - let allPages = []; - let currentUrls = []; - const pageContainerCount = 3; - let selectedPageIndex = -1; - reset(); - mainAnimatedPages.classList.remove('hide'); +export function reset() { + allPages = []; + currentUrls = []; + if (mainAnimatedPages) mainAnimatedPages.innerHTML = ''; + selectedPageIndex = -1; +} -/* eslint-enable indent */ +let onBeforeChange; +let mainAnimatedPages; +let allPages = []; +let currentUrls = []; +const pageContainerCount = 3; +let selectedPageIndex = -1; +reset(); +getMainAnimatedPages()?.classList.remove('hide'); export default { loadView: loadView, diff --git a/src/controllers/dashboard/apikeys.js b/src/controllers/dashboard/apikeys.js index 0946f10e60..c36aac650f 100644 --- a/src/controllers/dashboard/apikeys.js +++ b/src/controllers/dashboard/apikeys.js @@ -6,85 +6,82 @@ import '../../elements/emby-button/emby-button'; import confirm from '../../components/confirm/confirm'; import { pageIdOn } from '../../utils/dashboard'; -/* eslint-disable indent */ +function revoke(page, key) { + confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () { + loading.show(); + ApiClient.ajax({ + type: 'DELETE', + url: ApiClient.getUrl('Auth/Keys/' + key) + }).then(function () { + loadData(page); + }); + }); +} - function revoke(page, key) { - confirm(globalize.translate('MessageConfirmRevokeApiKey'), globalize.translate('HeaderConfirmRevokeApiKey')).then(function () { - loading.show(); +function renderKeys(page, keys) { + const rows = keys.map(function (item) { + let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += item.AccessToken; + html += ''; + html += ''; + html += item.AppName || ''; + html += ''; + html += ''; + const date = datetime.parseISO8601Date(item.DateCreated, true); + html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); + html += ''; + html += ''; + return html; + }).join(''); + page.querySelector('.resultBody').innerHTML = rows; + loading.hide(); +} + +function loadData(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) { + renderKeys(page, result.Items); + }); +} + +function showNewKeyPrompt(page) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + title: globalize.translate('HeaderNewApiKey'), + label: globalize.translate('LabelAppName'), + description: globalize.translate('LabelAppNameExample') + }).then(function (value) { ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Auth/Keys/' + key) + type: 'POST', + url: ApiClient.getUrl('Auth/Keys', { + App: value + }) }).then(function () { loadData(page); }); }); - } - - function renderKeys(page, keys) { - const rows = keys.map(function (item) { - let html = ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += item.AccessToken; - html += ''; - html += ''; - html += item.AppName || ''; - html += ''; - html += ''; - const date = datetime.parseISO8601Date(item.DateCreated, true); - html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); - html += ''; - html += ''; - return html; - }).join(''); - page.querySelector('.resultBody').innerHTML = rows; - loading.hide(); - } - - function loadData(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Auth/Keys')).then(function (result) { - renderKeys(page, result.Items); - }); - } - - function showNewKeyPrompt(page) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - title: globalize.translate('HeaderNewApiKey'), - label: globalize.translate('LabelAppName'), - description: globalize.translate('LabelAppNameExample') - }).then(function (value) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Auth/Keys', { - App: value - }) - }).then(function () { - loadData(page); - }); - }); - }); - } - - pageIdOn('pageinit', 'apiKeysPage', function () { - const page = this; - page.querySelector('.btnNewKey').addEventListener('click', function () { - showNewKeyPrompt(page); - }); - page.querySelector('.tblApiKeys').addEventListener('click', function (e) { - const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke'); - - if (btnRevoke) { - revoke(page, btnRevoke.getAttribute('data-token')); - } - }); - }); - pageIdOn('pagebeforeshow', 'apiKeysPage', function () { - loadData(this); }); +} + +pageIdOn('pageinit', 'apiKeysPage', function () { + const page = this; + page.querySelector('.btnNewKey').addEventListener('click', function () { + showNewKeyPrompt(page); + }); + page.querySelector('.tblApiKeys').addEventListener('click', function (e) { + const btnRevoke = dom.parentWithClass(e.target, 'btnRevoke'); + + if (btnRevoke) { + revoke(page, btnRevoke.getAttribute('data-token')); + } + }); +}); +pageIdOn('pagebeforeshow', 'apiKeysPage', function () { + loadData(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index d2db85156a..95912a55d6 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -24,826 +24,823 @@ import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; import confirm from '../../components/confirm/confirm'; -/* eslint-disable indent */ +function showPlaybackInfo(btn, session) { + let title; + const text = []; + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); - function showPlaybackInfo(btn, session) { - let title; - const text = []; - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + if (displayPlayMethod === 'Remux') { + title = globalize.translate('Remuxing'); + text.push(globalize.translate('RemuxHelp1')); + text.push('
'); + text.push(globalize.translate('RemuxHelp2')); + } else if (displayPlayMethod === 'DirectStream') { + title = globalize.translate('DirectStreaming'); + text.push(globalize.translate('DirectStreamHelp1')); + text.push('
'); + text.push(globalize.translate('DirectStreamHelp2')); + } else if (displayPlayMethod === 'DirectPlay') { + title = globalize.translate('DirectPlaying'); + text.push(globalize.translate('DirectPlayHelp')); + } else if (displayPlayMethod === 'Transcode') { + title = globalize.translate('Transcoding'); + text.push(globalize.translate('MediaIsBeingConverted')); + text.push(DashboardPage.getSessionNowPlayingStreamInfo(session)); - if (displayPlayMethod === 'Remux') { - title = globalize.translate('Remuxing'); - text.push(globalize.translate('RemuxHelp1')); + if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { text.push('
'); - text.push(globalize.translate('RemuxHelp2')); - } else if (displayPlayMethod === 'DirectStream') { - title = globalize.translate('DirectStreaming'); - text.push(globalize.translate('DirectStreamHelp1')); - text.push('
'); - text.push(globalize.translate('DirectStreamHelp2')); - } else if (displayPlayMethod === 'DirectPlay') { - title = globalize.translate('DirectPlaying'); - text.push(globalize.translate('DirectPlayHelp')); - } else if (displayPlayMethod === 'Transcode') { - title = globalize.translate('Transcoding'); - text.push(globalize.translate('MediaIsBeingConverted')); - text.push(DashboardPage.getSessionNowPlayingStreamInfo(session)); - - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - text.push('
'); - text.push(globalize.translate('LabelReasonForTranscoding')); - session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { - text.push(globalize.translate(transcodeReason)); - }); - } - } - - alert({ - text: text.join('
'), - title: title - }); - } - - function showSendMessageForm(btn, session) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - title: globalize.translate('HeaderSendMessage'), - label: globalize.translate('LabelMessageText'), - confirmText: globalize.translate('ButtonSend') - }).then(function (text) { - if (text) { - ServerConnections.getApiClient(session.ServerId).sendMessageCommand(session.Id, { - Text: text, - TimeoutMs: 5e3 - }); - } + text.push(globalize.translate('LabelReasonForTranscoding')); + session.TranscodingInfo.TranscodeReasons.forEach(function (transcodeReason) { + text.push(globalize.translate(transcodeReason)); }); - }); + } } - function showOptionsMenu(btn, session) { - import('../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { - const menuItems = []; + alert({ + text: text.join('
'), + title: title + }); +} - if (session.ServerId && session.DeviceId !== ServerConnections.deviceId()) { - menuItems.push({ - name: globalize.translate('SendMessage'), - id: 'sendmessage' +function showSendMessageForm(btn, session) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + title: globalize.translate('HeaderSendMessage'), + label: globalize.translate('LabelMessageText'), + confirmText: globalize.translate('ButtonSend') + }).then(function (text) { + if (text) { + ServerConnections.getApiClient(session.ServerId).sendMessageCommand(session.Id, { + Text: text, + TimeoutMs: 5e3 }); } + }); + }); +} - if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { - menuItems.push({ - name: globalize.translate('ViewPlaybackInfo'), - id: 'transcodinginfo' - }); - } +function showOptionsMenu(btn, session) { + import('../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + const menuItems = []; - return actionsheet.show({ - items: menuItems, - positionTo: btn - }).then(function (id) { - switch (id) { - case 'sendmessage': - showSendMessageForm(btn, session); - break; - case 'transcodinginfo': - showPlaybackInfo(btn, session); - break; - } + if (session.ServerId && session.DeviceId !== ServerConnections.deviceId()) { + menuItems.push({ + name: globalize.translate('SendMessage'), + id: 'sendmessage' }); - }); - } - - function onActiveDevicesClick(evt) { - const btn = dom.parentWithClass(evt.target, 'sessionCardButton'); - - if (btn) { - const card = dom.parentWithClass(btn, 'card'); - - if (card) { - const sessionId = card.id; - const session = (DashboardPage.sessionsList || []).filter(function (dashboardSession) { - return 'session' + dashboardSession.Id === sessionId; - })[0]; - - if (session) { - if (btn.classList.contains('btnCardOptions')) { - showOptionsMenu(btn, session); - } else if (btn.classList.contains('btnSessionInfo')) { - showPlaybackInfo(btn, session); - } else if (btn.classList.contains('btnSessionSendMessage')) { - showSendMessageForm(btn, session); - } else if (btn.classList.contains('btnSessionStop')) { - ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'Stop'); - } else if (btn.classList.contains('btnSessionPlayPause') && session.PlayState) { - ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'PlayPause'); - } - } - } - } - } - - function filterSessions(sessions) { - const list = []; - const minActiveDate = new Date().getTime() - 9e5; - - for (let i = 0, length = sessions.length; i < length; i++) { - const session = sessions[i]; - - if (!session.NowPlayingItem && !session.UserId) { - continue; - } - - if (datetime.parseISO8601Date(session.LastActivityDate, true).getTime() >= minActiveDate) { - list.push(session); - } } - return list; - } - - function refreshActiveRecordings(view, apiClient) { - apiClient.getLiveTvRecordings({ - UserId: Dashboard.getCurrentUserId(), - IsInProgress: true, - Fields: 'CanDelete,PrimaryImageAspectRatio', - EnableTotalRecordCount: false, - EnableImageTypes: 'Primary,Thumb,Backdrop' - }).then(function (result) { - const itemsContainer = view.querySelector('.activeRecordingItems'); - - if (!result.Items.length) { - view.querySelector('.activeRecordingsSection').classList.add('hide'); - itemsContainer.innerHTML = ''; - return; - } - - view.querySelector('.activeRecordingsSection').classList.remove('hide'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - defaultShape: 'backdrop', - showTitle: true, - showParentTitle: true, - coverImage: true, - cardLayout: false, - centerText: true, - preferThumb: 'auto', - overlayText: false, - overlayMoreButton: true, - action: 'none', - centerPlayButton: true + if (session.TranscodingInfo && session.TranscodingInfo.TranscodeReasons && session.TranscodingInfo.TranscodeReasons.length) { + menuItems.push({ + name: globalize.translate('ViewPlaybackInfo'), + id: 'transcodinginfo' }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function reloadSystemInfo(view, apiClient) { - apiClient.getSystemInfo().then(function (systemInfo) { - view.querySelector('#serverName').innerText = globalize.translate('DashboardServerName', systemInfo.ServerName); - view.querySelector('#versionNumber').innerText = globalize.translate('DashboardVersionNumber', systemInfo.Version); - - if (systemInfo.CanSelfRestart) { - view.querySelector('#btnRestartServer').classList.remove('hide'); - } else { - view.querySelector('#btnRestartServer').classList.add('hide'); - } - - view.querySelector('#cachePath').innerText = systemInfo.CachePath; - view.querySelector('#logPath').innerText = systemInfo.LogPath; - view.querySelector('#transcodePath').innerText = systemInfo.TranscodingTempPath; - view.querySelector('#metadataPath').innerText = systemInfo.InternalMetadataPath; - view.querySelector('#webPath').innerText = systemInfo.WebPath; - }); - } - - function renderInfo(view, sessions) { - sessions = filterSessions(sessions); - renderActiveConnections(view, sessions); - loading.hide(); - } - - function pollForInfo(view, apiClient) { - apiClient.getSessions({ - ActiveWithinSeconds: 960 - }).then(function (sessions) { - renderInfo(view, sessions); - }); - apiClient.getScheduledTasks().then(function (tasks) { - renderRunningTasks(view, tasks); - }); - } - - function renderActiveConnections(view, sessions) { - let html = ''; - DashboardPage.sessionsList = sessions; - const parentElement = view.querySelector('.activeDevices'); - const cardElem = parentElement.querySelector('.card'); - - if (cardElem) { - cardElem.classList.add('deadSession'); } - for (let i = 0, length = sessions.length; i < length; i++) { - const session = sessions[i]; - const rowId = 'session' + session.Id; - const elem = view.querySelector('#' + rowId); + return actionsheet.show({ + items: menuItems, + positionTo: btn + }).then(function (id) { + switch (id) { + case 'sendmessage': + showSendMessageForm(btn, session); + break; + case 'transcodinginfo': + showPlaybackInfo(btn, session); + break; + } + }); + }); +} - if (elem) { - DashboardPage.updateSession(elem, session); - } else { - const nowPlayingItem = session.NowPlayingItem; - const className = 'scalableCard card activeSession backdropCard backdropCard-scalable'; - const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem); +function onActiveDevicesClick(evt) { + const btn = dom.parentWithClass(evt.target, 'sessionCardButton'); - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += `
`; + if (btn) { + const card = dom.parentWithClass(btn, 'card'); - if (imgUrl) { - html += '
"; - } else { - html += '
'; + if (card) { + const sessionId = card.id; + const session = (DashboardPage.sessionsList || []).filter(function (dashboardSession) { + return 'session' + dashboardSession.Id === sessionId; + })[0]; + + if (session) { + if (btn.classList.contains('btnCardOptions')) { + showOptionsMenu(btn, session); + } else if (btn.classList.contains('btnSessionInfo')) { + showPlaybackInfo(btn, session); + } else if (btn.classList.contains('btnSessionSendMessage')) { + showSendMessageForm(btn, session); + } else if (btn.classList.contains('btnSessionStop')) { + ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'Stop'); + } else if (btn.classList.contains('btnSessionPlayPause') && session.PlayState) { + ServerConnections.getApiClient(session.ServerId).sendPlayStateCommand(session.Id, 'PlayPause'); } - - html += `
`; - html += '
'; - const clientImage = DashboardPage.getClientImage(session); - - if (clientImage) { - html += clientImage; - } - - html += '
'; - html += '
' + escapeHtml(session.DeviceName) + '
'; - html += '
' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '
'; - html += '
'; - html += '
'; - - html += '
'; - const nowPlayingName = DashboardPage.getNowPlayingName(session); - html += '
'; - html += '' + nowPlayingName.html + ''; - html += '
'; - html += '
' + escapeHtml(DashboardPage.getSessionNowPlayingTime(session)) + '
'; - html += '
'; - - let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; - html += indicators.getProgressHtml(percent || 0, { - containerClass: 'playbackProgress' - }); - - percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); - html += indicators.getProgressHtml(percent || 0, { - containerClass: 'transcodingProgress' - }); - - html += indicators.getProgressHtml(100, { - containerClass: 'backgroundProgress' - }); - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - - let btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? '' : ' hide'; - const playIcon = session.PlayState.IsPaused ? 'pause' : 'play_arrow'; - - html += ''; - html += ''; - html += ''; - - btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== ServerConnections.deviceId() ? '' : ' hide'; - html += ''; - html += '
'; - - html += '
'; - const userImage = DashboardPage.getUserImage(session); - html += userImage ? '
" : '
'; - html += '
'; - html += DashboardPage.getUsersHtml(session); - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; } } + } +} - parentElement.insertAdjacentHTML('beforeend', html); - const deadSessionElem = parentElement.querySelector('.deadSession'); +function filterSessions(sessions) { + const list = []; + const minActiveDate = new Date().getTime() - 9e5; - if (deadSessionElem) { - deadSessionElem.parentNode.removeChild(deadSessionElem); + for (let i = 0, length = sessions.length; i < length; i++) { + const session = sessions[i]; + + if (!session.NowPlayingItem && !session.UserId) { + continue; + } + + if (datetime.parseISO8601Date(session.LastActivityDate, true).getTime() >= minActiveDate) { + list.push(session); } } - function renderRunningTasks(view, tasks) { - let html = ''; - tasks = tasks.filter(function (task) { - if (task.State != 'Idle') { - return !task.IsHidden; - } + return list; +} - return false; +function refreshActiveRecordings(view, apiClient) { + apiClient.getLiveTvRecordings({ + UserId: Dashboard.getCurrentUserId(), + IsInProgress: true, + Fields: 'CanDelete,PrimaryImageAspectRatio', + EnableTotalRecordCount: false, + EnableImageTypes: 'Primary,Thumb,Backdrop' + }).then(function (result) { + const itemsContainer = view.querySelector('.activeRecordingItems'); + + if (!result.Items.length) { + view.querySelector('.activeRecordingsSection').classList.add('hide'); + itemsContainer.innerHTML = ''; + return; + } + + view.querySelector('.activeRecordingsSection').classList.remove('hide'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + defaultShape: 'backdrop', + showTitle: true, + showParentTitle: true, + coverImage: true, + cardLayout: false, + centerText: true, + preferThumb: 'auto', + overlayText: false, + overlayMoreButton: true, + action: 'none', + centerPlayButton: true }); + imageLoader.lazyChildren(itemsContainer); + }); +} - if (tasks.length) { - view.querySelector('.runningTasksContainer').classList.remove('hide'); +function reloadSystemInfo(view, apiClient) { + apiClient.getSystemInfo().then(function (systemInfo) { + view.querySelector('#serverName').innerText = globalize.translate('DashboardServerName', systemInfo.ServerName); + view.querySelector('#versionNumber').innerText = globalize.translate('DashboardVersionNumber', systemInfo.Version); + + if (systemInfo.CanSelfRestart) { + view.querySelector('#btnRestartServer').classList.remove('hide'); } else { - view.querySelector('.runningTasksContainer').classList.add('hide'); + view.querySelector('#btnRestartServer').classList.add('hide'); } - for (let i = 0, length = tasks.length; i < length; i++) { - const task = tasks[i]; - html += '

'; - html += task.Name + '
'; + view.querySelector('#cachePath').innerText = systemInfo.CachePath; + view.querySelector('#logPath').innerText = systemInfo.LogPath; + view.querySelector('#transcodePath').innerText = systemInfo.TranscodingTempPath; + view.querySelector('#metadataPath').innerText = systemInfo.InternalMetadataPath; + view.querySelector('#webPath').innerText = systemInfo.WebPath; + }); +} - if (task.State === 'Running') { - const progress = (task.CurrentProgressPercentage || 0).toFixed(1); - html += ''; - html += progress + '%'; - html += ''; - html += "" + progress + '%'; - html += ''; - } else if (task.State === 'Cancelling') { - html += '' + globalize.translate('LabelStopping') + ''; - } +function renderInfo(view, sessions) { + sessions = filterSessions(sessions); + renderActiveConnections(view, sessions); + loading.hide(); +} - html += '

'; - } +function pollForInfo(view, apiClient) { + apiClient.getSessions({ + ActiveWithinSeconds: 960 + }).then(function (sessions) { + renderInfo(view, sessions); + }); + apiClient.getScheduledTasks().then(function (tasks) { + renderRunningTasks(view, tasks); + }); +} - view.querySelector('#divRunningTasks').innerHTML = html; +function renderActiveConnections(view, sessions) { + let html = ''; + DashboardPage.sessionsList = sessions; + const parentElement = view.querySelector('.activeDevices'); + const cardElem = parentElement.querySelector('.card'); + + if (cardElem) { + cardElem.classList.add('deadSession'); } - window.DashboardPage = { - startInterval: function (apiClient) { - apiClient.sendMessage('SessionsStart', '0,1500'); - apiClient.sendMessage('ScheduledTasksInfoStart', '0,1000'); - }, - stopInterval: function (apiClient) { - apiClient.sendMessage('SessionsStop'); - apiClient.sendMessage('ScheduledTasksInfoStop'); - }, - getSessionNowPlayingStreamInfo: function (session) { - let html = ''; - let showTranscodingInfo = false; - const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + for (let i = 0, length = sessions.length; i < length; i++) { + const session = sessions[i]; + const rowId = 'session' + session.Id; + const elem = view.querySelector('#' + rowId); - if (displayPlayMethod === 'DirectPlay') { - html += globalize.translate('DirectPlaying'); - } else if (displayPlayMethod === 'Remux') { - html += globalize.translate('Remuxing'); - } else if (displayPlayMethod === 'DirectStream') { - html += globalize.translate('DirectStreaming'); - } else if (displayPlayMethod === 'Transcode') { - if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { - html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`; - } - - showTranscodingInfo = true; - } - - if (showTranscodingInfo) { - const line = []; - - if (session.TranscodingInfo) { - if (session.TranscodingInfo.Bitrate) { - if (session.TranscodingInfo.Bitrate > 1e6) { - line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + ' Mbps'); - } else { - line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + ' Kbps'); - } - } - - if (session.TranscodingInfo.Container) { - line.push(session.TranscodingInfo.Container.toUpperCase()); - } - - if (session.TranscodingInfo.VideoCodec) { - line.push(session.TranscodingInfo.VideoCodec.toUpperCase()); - } - - if (session.TranscodingInfo.AudioCodec && session.TranscodingInfo.AudioCodec != session.TranscodingInfo.Container) { - line.push(session.TranscodingInfo.AudioCodec.toUpperCase()); - } - } - - if (line.length) { - html += '

' + line.join(' '); - } - } - - return html; - }, - getSessionNowPlayingTime: function (session) { + if (elem) { + DashboardPage.updateSession(elem, session); + } else { const nowPlayingItem = session.NowPlayingItem; - let html = ''; + const className = 'scalableCard card activeSession backdropCard backdropCard-scalable'; + const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem); - if (nowPlayingItem) { - if (session.PlayState.PositionTicks) { - html += datetime.getDisplayRunningTime(session.PlayState.PositionTicks); - } else { - html += '0:00'; - } - - html += ' / '; - - if (nowPlayingItem.RunTimeTicks) { - html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); - } else { - html += '0:00'; - } - } - - return html; - }, - getAppSecondaryText: function (session) { - return session.Client + ' ' + session.ApplicationVersion; - }, - getNowPlayingName: function (session) { - let imgUrl = ''; - const nowPlayingItem = session.NowPlayingItem; - // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix - // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences - if (!nowPlayingItem) { - return { - html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())), - image: imgUrl - }; - } - - let topText = escapeHtml(itemHelper.getDisplayName(nowPlayingItem)); - let bottomText = ''; - - if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { - bottomText = topText; - topText = escapeHtml(nowPlayingItem.Artists[0]); - } else { - if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { - bottomText = topText; - topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); - } else if (nowPlayingItem.ProductionYear) { - bottomText = nowPlayingItem.ProductionYear; - } - } - - if (nowPlayingItem.ImageTags && nowPlayingItem.ImageTags.Logo) { - imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.Id, { - tag: nowPlayingItem.ImageTags.Logo, - maxHeight: 24, - maxWidth: 130, - type: 'Logo' - }); - } else if (nowPlayingItem.ParentLogoImageTag) { - imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.ParentLogoItemId, { - tag: nowPlayingItem.ParentLogoImageTag, - maxHeight: 24, - maxWidth: 130, - type: 'Logo' - }); - } + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += `
`; if (imgUrl) { - topText = ''; - } - - return { - html: bottomText ? topText + '
' + bottomText : topText, - image: imgUrl - }; - }, - getUsersHtml: function (session) { - const html = []; - - if (session.UserId) { - html.push(escapeHtml(session.UserName)); - } - - for (let i = 0, length = session.AdditionalUsers.length; i < length; i++) { - html.push(escapeHtml(session.AdditionalUsers[i].UserName)); - } - - return html.join(', '); - }, - getUserImage: function (session) { - if (session.UserId && session.UserPrimaryImageTag) { - return ApiClient.getUserImageUrl(session.UserId, { - tag: session.UserPrimaryImageTag, - type: 'Primary' - }); - } - - return null; - }, - updateSession: function (row, session) { - row.classList.remove('deadSession'); - const nowPlayingItem = session.NowPlayingItem; - - if (nowPlayingItem) { - row.classList.add('playingSession'); - row.querySelector('.btnSessionInfo').classList.remove('hide'); + html += '
"; } else { - row.classList.remove('playingSession'); - row.querySelector('.btnSessionInfo').classList.add('hide'); + html += '
'; } - if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1) { - row.querySelector('.btnSessionSendMessage').classList.remove('hide'); - } else { - row.querySelector('.btnSessionSendMessage').classList.add('hide'); + html += `
`; + html += '
'; + const clientImage = DashboardPage.getClientImage(session); + + if (clientImage) { + html += clientImage; } - const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); + html += '
'; + html += '
' + escapeHtml(session.DeviceName) + '
'; + html += '
' + escapeHtml(DashboardPage.getAppSecondaryText(session)) + '
'; + html += '
'; + html += '
'; - if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl) { - btnSessionPlayPause.classList.remove('hide'); - row.querySelector('.btnSessionStop').classList.remove('hide'); - } else { - btnSessionPlayPause.classList.add('hide'); - row.querySelector('.btnSessionStop').classList.add('hide'); - } - - const btnSessionPlayPauseIcon = btnSessionPlayPause.querySelector('.material-icons'); - btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause'); - btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause'); - - row.querySelector('.sessionNowPlayingTime').innerText = DashboardPage.getSessionNowPlayingTime(session); - row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); - row.querySelector('.sessionAppSecondaryText').innerText = DashboardPage.getAppSecondaryText(session); + html += '
'; const nowPlayingName = DashboardPage.getNowPlayingName(session); - const nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); - - if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute('data-imgsrc'))) { - nowPlayingInfoElem.innerHTML = nowPlayingName.html; - nowPlayingInfoElem.setAttribute('data-imgsrc', nowPlayingName.image || ''); - } - - const playbackProgressElem = row.querySelector('.playbackProgress'); - const transcodingProgress = row.querySelector('.transcodingProgress'); + html += '
'; + html += '' + nowPlayingName.html + ''; + html += '
'; + html += '
' + escapeHtml(DashboardPage.getSessionNowPlayingTime(session)) + '
'; + html += '
'; let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; - playbackProgressElem.outerHTML = indicators.getProgressHtml(percent || 0, { + html += indicators.getProgressHtml(percent || 0, { containerClass: 'playbackProgress' }); percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); - transcodingProgress.outerHTML = indicators.getProgressHtml(percent || 0, { + html += indicators.getProgressHtml(percent || 0, { containerClass: 'transcodingProgress' }); - const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ''; - const imgElem = row.querySelector('.sessionNowPlayingContent'); - - if (imgUrl != imgElem.getAttribute('data-src')) { - imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ''; - imgElem.setAttribute('data-src', imgUrl); - - if (imgUrl) { - imgElem.classList.add('sessionNowPlayingContent-withbackground'); - row.querySelector('.sessionNowPlayingInnerContent').classList.add('darkenContent'); - } else { - imgElem.classList.remove('sessionNowPlayingContent-withbackground'); - row.querySelector('.sessionNowPlayingInnerContent').classList.remove('darkenContent'); - } - } - }, - getClientImage: function (connection) { - const iconUrl = imageHelper.getDeviceIcon(connection); - return ""; - }, - getNowPlayingImageUrl: function (item) { - /* Screen width is multiplied by 0.2, as the there is currently no way to get the width of - elements that aren't created yet. */ - if (item && item.BackdropImageTags && item.BackdropImageTags.length) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.BackdropImageTags[0] - }); - } - - if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { - return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.ParentBackdropImageTags[0] - }); - } - - if (item && item.BackdropImageTag) { - return ApiClient.getScaledImageUrl(item.BackdropItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Backdrop', - tag: item.BackdropImageTag - }); - } - - const imageTags = (item || {}).ImageTags || {}; - - if (item && imageTags.Thumb) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: imageTags.Thumb - }); - } - - if (item && item.ParentThumbImageTag) { - return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: item.ParentThumbImageTag - }); - } - - if (item && item.ThumbImageTag) { - return ApiClient.getScaledImageUrl(item.ThumbItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Thumb', - tag: item.ThumbImageTag - }); - } - - if (item && imageTags.Primary) { - return ApiClient.getScaledImageUrl(item.Id, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: imageTags.Primary - }); - } - - if (item && item.PrimaryImageTag) { - return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: item.PrimaryImageTag - }); - } - - if (item && item.AlbumPrimaryImageTag) { - return ApiClient.getScaledImageUrl(item.AlbumId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.20), - type: 'Primary', - tag: item.AlbumPrimaryImageTag - }); - } - - return null; - }, - systemUpdateTaskKey: 'SystemUpdateTask', - stopTask: function (btn, id) { - const page = dom.parentWithClass(btn, 'page'); - ApiClient.stopScheduledTask(id).then(function () { - pollForInfo(page, ApiClient); - }); - }, - restart: function (btn) { - confirm({ - title: globalize.translate('Restart'), - text: globalize.translate('MessageConfirmRestart'), - confirmText: globalize.translate('Restart'), - primary: 'delete' - }).then(function () { - const page = dom.parentWithClass(btn, 'page'); - page.querySelector('#btnRestartServer').disabled = true; - page.querySelector('#btnShutdown').disabled = true; - ApiClient.restartServer(); - }); - }, - shutdown: function (btn) { - confirm({ - title: globalize.translate('ButtonShutdown'), - text: globalize.translate('MessageConfirmShutdown'), - confirmText: globalize.translate('ButtonShutdown'), - primary: 'delete' - }).then(function () { - const page = dom.parentWithClass(btn, 'page'); - page.querySelector('#btnRestartServer').disabled = true; - page.querySelector('#btnShutdown').disabled = true; - ApiClient.shutdownServer(); + html += indicators.getProgressHtml(100, { + containerClass: 'backgroundProgress' }); + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + + let btnCssClass = session.ServerId && session.NowPlayingItem && session.SupportsRemoteControl ? '' : ' hide'; + const playIcon = session.PlayState.IsPaused ? 'pause' : 'play_arrow'; + + html += ''; + html += ''; + html += ''; + + btnCssClass = session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1 && session.DeviceId !== ServerConnections.deviceId() ? '' : ' hide'; + html += ''; + html += '
'; + + html += '
'; + const userImage = DashboardPage.getUserImage(session); + html += userImage ? '
" : '
'; + html += '
'; + html += DashboardPage.getUsersHtml(session); + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; } - }; - export default function (view) { - function onRestartRequired(evt, apiClient) { - console.debug('onRestartRequired not implemented', evt, apiClient); - } - - function onServerShuttingDown(evt, apiClient) { - console.debug('onServerShuttingDown not implemented', evt, apiClient); - } - - function onServerRestarting(evt, apiClient) { - console.debug('onServerRestarting not implemented', evt, apiClient); - } - - function onPackageInstall(_, apiClient) { - if (apiClient.serverId() === serverId) { - pollForInfo(view, apiClient); - reloadSystemInfo(view, apiClient); - } - } - - function onSessionsUpdate(evt, apiClient, info) { - if (apiClient.serverId() === serverId) { - renderInfo(view, info); - } - } - - function onScheduledTasksUpdate(evt, apiClient, info) { - if (apiClient.serverId() === serverId) { - renderRunningTasks(view, info); - } - } - - const serverId = ApiClient.serverId(); - view.querySelector('.activeDevices').addEventListener('click', onActiveDevicesClick); - view.addEventListener('viewshow', function () { - const page = this; - const apiClient = ApiClient; - - if (apiClient) { - loading.show(); - pollForInfo(page, apiClient); - DashboardPage.startInterval(apiClient); - Events.on(serverNotifications, 'RestartRequired', onRestartRequired); - Events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); - Events.on(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.on(serverNotifications, 'PackageInstalling', onPackageInstall); - Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); - Events.on(serverNotifications, 'Sessions', onSessionsUpdate); - Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - DashboardPage.lastAppUpdateCheck = null; - reloadSystemInfo(page, ApiClient); - - if (!page.userActivityLog) { - page.userActivityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: page.querySelector('.userActivityItems') - }); - } - - if (!page.serverActivityLog) { - page.serverActivityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: page.querySelector('.serverActivityItems') - }); - } - - refreshActiveRecordings(view, apiClient); - loading.hide(); - } - - taskButton({ - mode: 'on', - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - view.addEventListener('viewbeforehide', function () { - const apiClient = ApiClient; - const page = this; - - Events.off(serverNotifications, 'RestartRequired', onRestartRequired); - Events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); - Events.off(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.off(serverNotifications, 'PackageInstalling', onPackageInstall); - Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); - Events.off(serverNotifications, 'Sessions', onSessionsUpdate); - Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - - if (apiClient) { - DashboardPage.stopInterval(apiClient); - } - - taskButton({ - mode: 'off', - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - view.addEventListener('viewdestroy', function () { - const page = this; - const userActivityLog = page.userActivityLog; - - if (userActivityLog) { - userActivityLog.destroy(); - } - - const serverActivityLog = page.serverActivityLog; - - if (serverActivityLog) { - serverActivityLog.destroy(); - } - }); } -/* eslint-enable indent */ + parentElement.insertAdjacentHTML('beforeend', html); + const deadSessionElem = parentElement.querySelector('.deadSession'); + + if (deadSessionElem) { + deadSessionElem.parentNode.removeChild(deadSessionElem); + } +} + +function renderRunningTasks(view, tasks) { + let html = ''; + tasks = tasks.filter(function (task) { + if (task.State != 'Idle') { + return !task.IsHidden; + } + + return false; + }); + + if (tasks.length) { + view.querySelector('.runningTasksContainer').classList.remove('hide'); + } else { + view.querySelector('.runningTasksContainer').classList.add('hide'); + } + + for (let i = 0, length = tasks.length; i < length; i++) { + const task = tasks[i]; + html += '

'; + html += task.Name + '
'; + + if (task.State === 'Running') { + const progress = (task.CurrentProgressPercentage || 0).toFixed(1); + html += ''; + html += progress + '%'; + html += ''; + html += "" + progress + '%'; + html += ''; + } else if (task.State === 'Cancelling') { + html += '' + globalize.translate('LabelStopping') + ''; + } + + html += '

'; + } + + view.querySelector('#divRunningTasks').innerHTML = html; +} + +window.DashboardPage = { + startInterval: function (apiClient) { + apiClient.sendMessage('SessionsStart', '0,1500'); + apiClient.sendMessage('ScheduledTasksInfoStart', '0,1000'); + }, + stopInterval: function (apiClient) { + apiClient.sendMessage('SessionsStop'); + apiClient.sendMessage('ScheduledTasksInfoStop'); + }, + getSessionNowPlayingStreamInfo: function (session) { + let html = ''; + let showTranscodingInfo = false; + const displayPlayMethod = playMethodHelper.getDisplayPlayMethod(session); + + if (displayPlayMethod === 'DirectPlay') { + html += globalize.translate('DirectPlaying'); + } else if (displayPlayMethod === 'Remux') { + html += globalize.translate('Remuxing'); + } else if (displayPlayMethod === 'DirectStream') { + html += globalize.translate('DirectStreaming'); + } else if (displayPlayMethod === 'Transcode') { + if (session.TranscodingInfo && session.TranscodingInfo.Framerate) { + html += `${globalize.translate('Framerate')}: ${session.TranscodingInfo.Framerate}fps`; + } + + showTranscodingInfo = true; + } + + if (showTranscodingInfo) { + const line = []; + + if (session.TranscodingInfo) { + if (session.TranscodingInfo.Bitrate) { + if (session.TranscodingInfo.Bitrate > 1e6) { + line.push((session.TranscodingInfo.Bitrate / 1e6).toFixed(1) + ' Mbps'); + } else { + line.push(Math.floor(session.TranscodingInfo.Bitrate / 1e3) + ' Kbps'); + } + } + + if (session.TranscodingInfo.Container) { + line.push(session.TranscodingInfo.Container.toUpperCase()); + } + + if (session.TranscodingInfo.VideoCodec) { + line.push(session.TranscodingInfo.VideoCodec.toUpperCase()); + } + + if (session.TranscodingInfo.AudioCodec && session.TranscodingInfo.AudioCodec != session.TranscodingInfo.Container) { + line.push(session.TranscodingInfo.AudioCodec.toUpperCase()); + } + } + + if (line.length) { + html += '

' + line.join(' '); + } + } + + return html; + }, + getSessionNowPlayingTime: function (session) { + const nowPlayingItem = session.NowPlayingItem; + let html = ''; + + if (nowPlayingItem) { + if (session.PlayState.PositionTicks) { + html += datetime.getDisplayRunningTime(session.PlayState.PositionTicks); + } else { + html += '0:00'; + } + + html += ' / '; + + if (nowPlayingItem.RunTimeTicks) { + html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); + } else { + html += '0:00'; + } + } + + return html; + }, + getAppSecondaryText: function (session) { + return session.Client + ' ' + session.ApplicationVersion; + }, + getNowPlayingName: function (session) { + let imgUrl = ''; + const nowPlayingItem = session.NowPlayingItem; + // FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix + // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences + if (!nowPlayingItem) { + return { + html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())), + image: imgUrl + }; + } + + let topText = escapeHtml(itemHelper.getDisplayName(nowPlayingItem)); + let bottomText = ''; + + if (nowPlayingItem.Artists && nowPlayingItem.Artists.length) { + bottomText = topText; + topText = escapeHtml(nowPlayingItem.Artists[0]); + } else { + if (nowPlayingItem.SeriesName || nowPlayingItem.Album) { + bottomText = topText; + topText = escapeHtml(nowPlayingItem.SeriesName || nowPlayingItem.Album); + } else if (nowPlayingItem.ProductionYear) { + bottomText = nowPlayingItem.ProductionYear; + } + } + + if (nowPlayingItem.ImageTags && nowPlayingItem.ImageTags.Logo) { + imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.Id, { + tag: nowPlayingItem.ImageTags.Logo, + maxHeight: 24, + maxWidth: 130, + type: 'Logo' + }); + } else if (nowPlayingItem.ParentLogoImageTag) { + imgUrl = ApiClient.getScaledImageUrl(nowPlayingItem.ParentLogoItemId, { + tag: nowPlayingItem.ParentLogoImageTag, + maxHeight: 24, + maxWidth: 130, + type: 'Logo' + }); + } + + if (imgUrl) { + topText = ''; + } + + return { + html: bottomText ? topText + '
' + bottomText : topText, + image: imgUrl + }; + }, + getUsersHtml: function (session) { + const html = []; + + if (session.UserId) { + html.push(escapeHtml(session.UserName)); + } + + for (let i = 0, length = session.AdditionalUsers.length; i < length; i++) { + html.push(escapeHtml(session.AdditionalUsers[i].UserName)); + } + + return html.join(', '); + }, + getUserImage: function (session) { + if (session.UserId && session.UserPrimaryImageTag) { + return ApiClient.getUserImageUrl(session.UserId, { + tag: session.UserPrimaryImageTag, + type: 'Primary' + }); + } + + return null; + }, + updateSession: function (row, session) { + row.classList.remove('deadSession'); + const nowPlayingItem = session.NowPlayingItem; + + if (nowPlayingItem) { + row.classList.add('playingSession'); + row.querySelector('.btnSessionInfo').classList.remove('hide'); + } else { + row.classList.remove('playingSession'); + row.querySelector('.btnSessionInfo').classList.add('hide'); + } + + if (session.ServerId && session.SupportedCommands.indexOf('DisplayMessage') !== -1) { + row.querySelector('.btnSessionSendMessage').classList.remove('hide'); + } else { + row.querySelector('.btnSessionSendMessage').classList.add('hide'); + } + + const btnSessionPlayPause = row.querySelector('.btnSessionPlayPause'); + + if (session.ServerId && nowPlayingItem && session.SupportsRemoteControl) { + btnSessionPlayPause.classList.remove('hide'); + row.querySelector('.btnSessionStop').classList.remove('hide'); + } else { + btnSessionPlayPause.classList.add('hide'); + row.querySelector('.btnSessionStop').classList.add('hide'); + } + + const btnSessionPlayPauseIcon = btnSessionPlayPause.querySelector('.material-icons'); + btnSessionPlayPauseIcon.classList.remove('play_arrow', 'pause'); + btnSessionPlayPauseIcon.classList.add(session.PlayState && session.PlayState.IsPaused ? 'play_arrow' : 'pause'); + + row.querySelector('.sessionNowPlayingTime').innerText = DashboardPage.getSessionNowPlayingTime(session); + row.querySelector('.sessionUserName').innerHTML = DashboardPage.getUsersHtml(session); + row.querySelector('.sessionAppSecondaryText').innerText = DashboardPage.getAppSecondaryText(session); + const nowPlayingName = DashboardPage.getNowPlayingName(session); + const nowPlayingInfoElem = row.querySelector('.sessionNowPlayingInfo'); + + if (!(nowPlayingName.image && nowPlayingName.image == nowPlayingInfoElem.getAttribute('data-imgsrc'))) { + nowPlayingInfoElem.innerHTML = nowPlayingName.html; + nowPlayingInfoElem.setAttribute('data-imgsrc', nowPlayingName.image || ''); + } + + const playbackProgressElem = row.querySelector('.playbackProgress'); + const transcodingProgress = row.querySelector('.transcodingProgress'); + + let percent = 100 * session?.PlayState?.PositionTicks / nowPlayingItem?.RunTimeTicks; + playbackProgressElem.outerHTML = indicators.getProgressHtml(percent || 0, { + containerClass: 'playbackProgress' + }); + + percent = session?.TranscodingInfo?.CompletionPercentage?.toFixed(1); + transcodingProgress.outerHTML = indicators.getProgressHtml(percent || 0, { + containerClass: 'transcodingProgress' + }); + + const imgUrl = DashboardPage.getNowPlayingImageUrl(nowPlayingItem) || ''; + const imgElem = row.querySelector('.sessionNowPlayingContent'); + + if (imgUrl != imgElem.getAttribute('data-src')) { + imgElem.style.backgroundImage = imgUrl ? "url('" + imgUrl + "')" : ''; + imgElem.setAttribute('data-src', imgUrl); + + if (imgUrl) { + imgElem.classList.add('sessionNowPlayingContent-withbackground'); + row.querySelector('.sessionNowPlayingInnerContent').classList.add('darkenContent'); + } else { + imgElem.classList.remove('sessionNowPlayingContent-withbackground'); + row.querySelector('.sessionNowPlayingInnerContent').classList.remove('darkenContent'); + } + } + }, + getClientImage: function (connection) { + const iconUrl = imageHelper.getDeviceIcon(connection); + return ""; + }, + getNowPlayingImageUrl: function (item) { + /* Screen width is multiplied by 0.2, as the there is currently no way to get the width of + elements that aren't created yet. */ + if (item && item.BackdropImageTags && item.BackdropImageTags.length) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.BackdropImageTags[0] + }); + } + + if (item && item.ParentBackdropImageTags && item.ParentBackdropImageTags.length) { + return ApiClient.getScaledImageUrl(item.ParentBackdropItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.ParentBackdropImageTags[0] + }); + } + + if (item && item.BackdropImageTag) { + return ApiClient.getScaledImageUrl(item.BackdropItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Backdrop', + tag: item.BackdropImageTag + }); + } + + const imageTags = (item || {}).ImageTags || {}; + + if (item && imageTags.Thumb) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: imageTags.Thumb + }); + } + + if (item && item.ParentThumbImageTag) { + return ApiClient.getScaledImageUrl(item.ParentThumbItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: item.ParentThumbImageTag + }); + } + + if (item && item.ThumbImageTag) { + return ApiClient.getScaledImageUrl(item.ThumbItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Thumb', + tag: item.ThumbImageTag + }); + } + + if (item && imageTags.Primary) { + return ApiClient.getScaledImageUrl(item.Id, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: imageTags.Primary + }); + } + + if (item && item.PrimaryImageTag) { + return ApiClient.getScaledImageUrl(item.PrimaryImageItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: item.PrimaryImageTag + }); + } + + if (item && item.AlbumPrimaryImageTag) { + return ApiClient.getScaledImageUrl(item.AlbumId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.20), + type: 'Primary', + tag: item.AlbumPrimaryImageTag + }); + } + + return null; + }, + systemUpdateTaskKey: 'SystemUpdateTask', + stopTask: function (btn, id) { + const page = dom.parentWithClass(btn, 'page'); + ApiClient.stopScheduledTask(id).then(function () { + pollForInfo(page, ApiClient); + }); + }, + restart: function (btn) { + confirm({ + title: globalize.translate('Restart'), + text: globalize.translate('MessageConfirmRestart'), + confirmText: globalize.translate('Restart'), + primary: 'delete' + }).then(function () { + const page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; + ApiClient.restartServer(); + }); + }, + shutdown: function (btn) { + confirm({ + title: globalize.translate('ButtonShutdown'), + text: globalize.translate('MessageConfirmShutdown'), + confirmText: globalize.translate('ButtonShutdown'), + primary: 'delete' + }).then(function () { + const page = dom.parentWithClass(btn, 'page'); + page.querySelector('#btnRestartServer').disabled = true; + page.querySelector('#btnShutdown').disabled = true; + ApiClient.shutdownServer(); + }); + } +}; +export default function (view) { + function onRestartRequired(evt, apiClient) { + console.debug('onRestartRequired not implemented', evt, apiClient); + } + + function onServerShuttingDown(evt, apiClient) { + console.debug('onServerShuttingDown not implemented', evt, apiClient); + } + + function onServerRestarting(evt, apiClient) { + console.debug('onServerRestarting not implemented', evt, apiClient); + } + + function onPackageInstall(_, apiClient) { + if (apiClient.serverId() === serverId) { + pollForInfo(view, apiClient); + reloadSystemInfo(view, apiClient); + } + } + + function onSessionsUpdate(evt, apiClient, info) { + if (apiClient.serverId() === serverId) { + renderInfo(view, info); + } + } + + function onScheduledTasksUpdate(evt, apiClient, info) { + if (apiClient.serverId() === serverId) { + renderRunningTasks(view, info); + } + } + + const serverId = ApiClient.serverId(); + view.querySelector('.activeDevices').addEventListener('click', onActiveDevicesClick); + view.addEventListener('viewshow', function () { + const page = this; + const apiClient = ApiClient; + + if (apiClient) { + loading.show(); + pollForInfo(page, apiClient); + DashboardPage.startInterval(apiClient); + Events.on(serverNotifications, 'RestartRequired', onRestartRequired); + Events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + Events.on(serverNotifications, 'ServerRestarting', onServerRestarting); + Events.on(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); + Events.on(serverNotifications, 'Sessions', onSessionsUpdate); + Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + DashboardPage.lastAppUpdateCheck = null; + reloadSystemInfo(page, ApiClient); + + if (!page.userActivityLog) { + page.userActivityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: page.querySelector('.userActivityItems') + }); + } + + if (!page.serverActivityLog) { + page.serverActivityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: page.querySelector('.serverActivityItems') + }); + } + + refreshActiveRecordings(view, apiClient); + loading.hide(); + } + + taskButton({ + mode: 'on', + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); + }); + view.addEventListener('viewbeforehide', function () { + const apiClient = ApiClient; + const page = this; + + Events.off(serverNotifications, 'RestartRequired', onRestartRequired); + Events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); + Events.off(serverNotifications, 'ServerRestarting', onServerRestarting); + Events.off(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); + Events.off(serverNotifications, 'Sessions', onSessionsUpdate); + Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + + if (apiClient) { + DashboardPage.stopInterval(apiClient); + } + + taskButton({ + mode: 'off', + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); + }); + view.addEventListener('viewdestroy', function () { + const page = this; + const userActivityLog = page.userActivityLog; + + if (userActivityLog) { + userActivityLog.destroy(); + } + + const serverActivityLog = page.serverActivityLog; + + if (serverActivityLog) { + serverActivityLog.destroy(); + } + }); +} + diff --git a/src/controllers/dashboard/devices/device.js b/src/controllers/dashboard/devices/device.js index 4a4da4bd7e..e911b361af 100644 --- a/src/controllers/dashboard/devices/device.js +++ b/src/controllers/dashboard/devices/device.js @@ -5,53 +5,50 @@ import '../../../elements/emby-button/emby-button'; import Dashboard from '../../../utils/dashboard'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ +function load(page, device, deviceOptions) { + page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; + page.querySelector('.reportedName', page).innerText = device.Name || ''; +} - function load(page, device, deviceOptions) { - page.querySelector('#txtCustomName', page).value = deviceOptions.CustomName || ''; - page.querySelector('.reportedName', page).innerText = device.Name || ''; - } +function loadData() { + const page = this; + loading.show(); + const id = getParameterByName('id'); + const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', { + Id: id + })); + const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', { + Id: id + })); + Promise.all([promise1, promise2]).then(function (responses) { + load(page, responses[0], responses[1]); + loading.hide(); + }); +} - function loadData() { - const page = this; - loading.show(); - const id = getParameterByName('id'); - const promise1 = ApiClient.getJSON(ApiClient.getUrl('Devices/Info', { +function save(page) { + const id = getParameterByName('id'); + ApiClient.ajax({ + url: ApiClient.getUrl('Devices/Options', { Id: id - })); - const promise2 = ApiClient.getJSON(ApiClient.getUrl('Devices/Options', { - Id: id - })); - Promise.all([promise1, promise2]).then(function (responses) { - load(page, responses[0], responses[1]); - loading.hide(); - }); - } + }), + type: 'POST', + data: JSON.stringify({ + CustomName: page.querySelector('#txtCustomName').value + }), + contentType: 'application/json' + }).then(Dashboard.processServerConfigurationUpdateResult); +} - function save(page) { - const id = getParameterByName('id'); - ApiClient.ajax({ - url: ApiClient.getUrl('Devices/Options', { - Id: id - }), - type: 'POST', - data: JSON.stringify({ - CustomName: page.querySelector('#txtCustomName').value - }), - contentType: 'application/json' - }).then(Dashboard.processServerConfigurationUpdateResult); - } +function onSubmit(e) { + const form = this; + save(dom.parentWithClass(form, 'page')); + e.preventDefault(); + return false; +} - function onSubmit(e) { - const form = this; - save(dom.parentWithClass(form, 'page')); - e.preventDefault(); - return false; - } +export default function (view) { + view.querySelector('form').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', loadData); +} - export default function (view) { - view.querySelector('form').addEventListener('submit', onSubmit); - view.addEventListener('viewshow', loadData); - } - -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index 348d6d2be1..8413c42dee 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -12,159 +12,157 @@ import '../../../components/cardbuilder/card.scss'; import Dashboard from '../../../utils/dashboard'; import confirm from '../../../components/confirm/confirm'; -/* eslint-disable indent */ +// Local cache of loaded +let deviceIds = []; - // Local cache of loaded - let deviceIds = []; +function canDelete(deviceId) { + return deviceId !== ApiClient.deviceId(); +} - function canDelete(deviceId) { - return deviceId !== ApiClient.deviceId(); - } +function deleteAllDevices(page) { + const msg = globalize.translate('DeleteDevicesConfirmation'); - function deleteAllDevices(page) { - const msg = globalize.translate('DeleteDevicesConfirmation'); + confirm({ + text: msg, + title: globalize.translate('HeaderDeleteDevices'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(async () => { + loading.show(); + await Promise.all( + deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) + ); + loadData(page); + }); +} - confirm({ - text: msg, - title: globalize.translate('HeaderDeleteDevices'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(async () => { - loading.show(); - await Promise.all( - deviceIds.filter(canDelete).map((id) => ApiClient.deleteDevice(id)) - ); - loadData(page); +function deleteDevice(page, id) { + const msg = globalize.translate('DeleteDeviceConfirmation'); + + confirm({ + text: msg, + title: globalize.translate('HeaderDeleteDevice'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(async () => { + loading.show(); + await ApiClient.deleteDevice(id); + loadData(page); + }); +} + +function showDeviceMenu(view, btn, deviceId) { + const menuItems = [{ + name: globalize.translate('Edit'), + id: 'open', + icon: 'mode_edit' + }]; + + if (canDelete(deviceId)) { + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' }); } - function deleteDevice(page, id) { - const msg = globalize.translate('DeleteDeviceConfirmation'); + import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: btn, + callback: function (id) { + switch (id) { + case 'open': + Dashboard.navigate('device.html?id=' + deviceId); + break; - confirm({ - text: msg, - title: globalize.translate('HeaderDeleteDevice'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(async () => { - loading.show(); - await ApiClient.deleteDevice(id); - loadData(page); + case 'delete': + deleteDevice(view, deviceId); + } + } }); - } + }); +} - function showDeviceMenu(view, btn, deviceId) { - const menuItems = [{ - name: globalize.translate('Edit'), - id: 'open', - icon: 'mode_edit' - }]; +function load(page, devices) { + const localeWithSuffix = getLocaleWithSuffix(); - if (canDelete(deviceId)) { - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete', - icon: 'delete' - }); + let html = ''; + html += devices.map(function (device) { + let deviceHtml = ''; + deviceHtml += "
"; + deviceHtml += '
'; + deviceHtml += ''; + deviceHtml += '
'; - case 'delete': - deleteDevice(view, deviceId); - } - } - }); - }); - } - - function load(page, devices) { - const localeWithSuffix = getLocaleWithSuffix(); - - let html = ''; - html += devices.map(function (device) { - let deviceHtml = ''; - deviceHtml += "
"; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += ``; - const iconUrl = imageHelper.getDeviceIcon(device); - - if (iconUrl) { - deviceHtml += '
"; - deviceHtml += '
'; - } else { - deviceHtml += ''; - } - - deviceHtml += '
'; + if (canDelete(device.Id)) { + if (globalize.getIsRTL()) + deviceHtml += '
'; + else + deviceHtml += '
'; + deviceHtml += ''; deviceHtml += '
'; - deviceHtml += '
'; + } - if (canDelete(device.Id)) { - if (globalize.getIsRTL()) - deviceHtml += '
'; - else - deviceHtml += '
'; - deviceHtml += ''; - deviceHtml += '
'; - } + deviceHtml += "
"; + deviceHtml += escapeHtml(device.Name); + deviceHtml += '
'; + deviceHtml += "
"; + deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion); + deviceHtml += '
'; + deviceHtml += "
"; - deviceHtml += "
"; - deviceHtml += escapeHtml(device.Name); - deviceHtml += '
'; - deviceHtml += "
"; - deviceHtml += escapeHtml(device.AppName + ' ' + device.AppVersion); - deviceHtml += '
'; - deviceHtml += "
"; + if (device.LastUserName) { + deviceHtml += escapeHtml(device.LastUserName); + deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix); + } - if (device.LastUserName) { - deviceHtml += escapeHtml(device.LastUserName); - deviceHtml += ', ' + formatDistanceToNow(Date.parse(device.DateLastActivity), localeWithSuffix); - } + deviceHtml += ' '; + deviceHtml += '
'; + deviceHtml += '
'; + deviceHtml += '
'; + deviceHtml += '
'; + return deviceHtml; + }).join(''); + page.querySelector('.devicesList').innerHTML = html; +} - deviceHtml += ' '; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - deviceHtml += '
'; - return deviceHtml; - }).join(''); - page.querySelector('.devicesList').innerHTML = html; - } +function loadData(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { + load(page, result.Items); + deviceIds = result.Items.map((device) => device.Id); + loading.hide(); + }); +} - function loadData(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Devices')).then(function (result) { - load(page, result.Items); - deviceIds = result.Items.map((device) => device.Id); - loading.hide(); - }); - } +export default function (view) { + view.querySelector('.devicesList').addEventListener('click', function (e) { + const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); - export default function (view) { - view.querySelector('.devicesList').addEventListener('click', function (e) { - const btnDeviceMenu = dom.parentWithClass(e.target, 'btnDeviceMenu'); + if (btnDeviceMenu) { + showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); + } + }); + view.addEventListener('viewshow', function () { + loadData(this); + }); - if (btnDeviceMenu) { - showDeviceMenu(view, btnDeviceMenu, btnDeviceMenu.getAttribute('data-id')); - } - }); - view.addEventListener('viewshow', function () { - loadData(this); - }); + view.querySelector('#deviceDeleteAll').addEventListener('click', function() { + deleteAllDevices(view); + }); +} - view.querySelector('#deviceDeleteAll').addEventListener('click', function() { - deleteAllDevices(view); - }); - } -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 4d942f7cdc..dd3e3ad957 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -11,829 +11,826 @@ import Dashboard from '../../../utils/dashboard'; import toast from '../../../components/toast/toast'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ - - function loadProfile(page) { - loading.show(); - const promise1 = getProfile(); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - currentProfile = responses[0]; - renderProfile(page, currentProfile, responses[1]); - loading.hide(); - }); - } - - function getProfile() { - const id = getParameterByName('id'); - const url = id ? 'Dlna/Profiles/' + id : 'Dlna/Profiles/Default'; - return ApiClient.getJSON(ApiClient.getUrl(url)); - } - - function renderProfile(page, profile, users) { - $('#txtName', page).val(profile.Name); - $('.chkMediaType', page).each(function () { - this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; - }); - $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); - $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); - renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); - const idInfo = profile.Identification || {}; - renderIdentificationHeaders(page, idInfo.Headers || []); - renderSubtitleProfiles(page, profile.SubtitleProfiles || []); - $('#txtInfoFriendlyName', page).val(profile.FriendlyName || ''); - $('#txtInfoModelName', page).val(profile.ModelName || ''); - $('#txtInfoModelNumber', page).val(profile.ModelNumber || ''); - $('#txtInfoModelDescription', page).val(profile.ModelDescription || ''); - $('#txtInfoModelUrl', page).val(profile.ModelUrl || ''); - $('#txtInfoManufacturer', page).val(profile.Manufacturer || ''); - $('#txtInfoManufacturerUrl', page).val(profile.ManufacturerUrl || ''); - $('#txtInfoSerialNumber', page).val(profile.SerialNumber || ''); - $('#txtIdFriendlyName', page).val(idInfo.FriendlyName || ''); - $('#txtIdModelName', page).val(idInfo.ModelName || ''); - $('#txtIdModelNumber', page).val(idInfo.ModelNumber || ''); - $('#txtIdModelDescription', page).val(idInfo.ModelDescription || ''); - $('#txtIdModelUrl', page).val(idInfo.ModelUrl || ''); - $('#txtIdManufacturer', page).val(idInfo.Manufacturer || ''); - $('#txtIdManufacturerUrl', page).val(idInfo.ManufacturerUrl || ''); - $('#txtIdSerialNumber', page).val(idInfo.SerialNumber || ''); - $('#txtIdDeviceDescription', page).val(idInfo.DeviceDescription || ''); - $('#txtAlbumArtPn', page).val(profile.AlbumArtPn || ''); - $('#txtAlbumArtMaxWidth', page).val(profile.MaxAlbumArtWidth || ''); - $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); - $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); - $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); - $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); - $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); - $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); - $('#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 || ''); - $('#txtSonyAggregationFlags', page).val(profile.SonyAggregationFlags || ''); - profile.DirectPlayProfiles = profile.DirectPlayProfiles || []; - profile.TranscodingProfiles = profile.TranscodingProfiles || []; - profile.ContainerProfiles = profile.ContainerProfiles || []; - profile.CodecProfiles = profile.CodecProfiles || []; - profile.ResponseProfiles = profile.ResponseProfiles || []; - const usersHtml = '' + users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); - renderSubProfiles(page, profile); - } - - function renderIdentificationHeaders(page, headers) { - let index = 0; - const html = '
' + headers.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ': ' + (h.Value || '')) + '

'; - li += '
' + escapeHtml(h.Match || '') + '
'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); - $('.btnDeleteIdentificationHeader', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.Identification.Headers.splice(itemIndex, 1); - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - }); - } - - function openPopup(elem) { - elem.classList.remove('hide'); - } - - function closePopup(elem) { - elem.classList.add('hide'); - } - - function editIdentificationHeader(page, header) { - isSubProfileNew = header == null; - header = header || {}; - currentSubProfile = header; - const popup = $('#identificationHeaderPopup', page); - $('#txtIdentificationHeaderName', popup).val(header.Name || ''); - $('#txtIdentificationHeaderValue', popup).val(header.Value || ''); - $('#selectMatchType', popup).val(header.Match || 'Equals'); - openPopup(popup[0]); - } - - function saveIdentificationHeader(page) { - currentSubProfile.Name = $('#txtIdentificationHeaderName', page).val(); - currentSubProfile.Value = $('#txtIdentificationHeaderValue', page).val(); - currentSubProfile.Match = $('#selectMatchType', page).val(); - - if (isSubProfileNew) { - currentProfile.Identification = currentProfile.Identification || {}; - currentProfile.Identification.Headers = currentProfile.Identification.Headers || []; - currentProfile.Identification.Headers.push(currentSubProfile); - } - - renderIdentificationHeaders(page, currentProfile.Identification.Headers); - currentSubProfile = null; - closePopup($('#identificationHeaderPopup', page)[0]); - } - - function renderXmlDocumentAttributes(page, attribute) { - const html = '
' + attribute.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; - li += '
'; - li += ''; - li += '
'; - return li; - }).join('') + '
'; - const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); - $('.btnDeleteXmlAttribute', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.XmlRootAttributes.splice(itemIndex, 1); - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - }); - } - - function editXmlDocumentAttribute(page, attribute) { - isSubProfileNew = attribute == null; - attribute = attribute || {}; - currentSubProfile = attribute; - const popup = $('#xmlAttributePopup', page); - $('#txtXmlAttributeName', popup).val(attribute.Name || ''); - $('#txtXmlAttributeValue', popup).val(attribute.Value || ''); - openPopup(popup[0]); - } - - function saveXmlDocumentAttribute(page) { - currentSubProfile.Name = $('#txtXmlAttributeName', page).val(); - currentSubProfile.Value = $('#txtXmlAttributeValue', page).val(); - - if (isSubProfileNew) { - currentProfile.XmlRootAttributes.push(currentSubProfile); - } - - renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); - currentSubProfile = null; - closePopup($('#xmlAttributePopup', page)[0]); - } - - function renderSubtitleProfiles(page, profiles) { - let index = 0; - const html = '
' + profiles.map(function (h) { - let li = '
'; - li += ''; - li += '
'; - li += '

' + escapeHtml(h.Format || '') + '

'; - li += '
'; - li += ''; - li += '
'; - index++; - return li; - }).join('') + '
'; - const elem = $('.subtitleProfileList', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - currentProfile.SubtitleProfiles.splice(itemIndex, 1); - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index'), 10); - editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); - }); - } - - function editSubtitleProfile(page, profile) { - isSubProfileNew = profile == null; - profile = profile || {}; - currentSubProfile = profile; - const popup = $('#subtitleProfilePopup', page); - $('#txtSubtitleProfileFormat', popup).val(profile.Format || ''); - $('#selectSubtitleProfileMethod', popup).val(profile.Method || ''); - $('#selectSubtitleProfileDidlMode', popup).val(profile.DidlMode || ''); - openPopup(popup[0]); - } - - function saveSubtitleProfile(page) { - currentSubProfile.Format = $('#txtSubtitleProfileFormat', page).val(); - currentSubProfile.Method = $('#selectSubtitleProfileMethod', page).val(); - currentSubProfile.DidlMode = $('#selectSubtitleProfileDidlMode', page).val(); - - if (isSubProfileNew) { - currentProfile.SubtitleProfiles.push(currentSubProfile); - } - - renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); - currentSubProfile = null; - closePopup($('#subtitleProfilePopup', page)[0]); - } - - function renderSubProfiles(page, profile) { - renderDirectPlayProfiles(page, profile.DirectPlayProfiles); - renderTranscodingProfiles(page, profile.TranscodingProfiles); - renderContainerProfiles(page, profile.ContainerProfiles); - renderCodecProfiles(page, profile.CodecProfiles); - renderResponseProfiles(page, profile.ResponseProfiles); - } - - function saveDirectPlayProfile(page) { - currentSubProfile.Type = $('#selectDirectPlayProfileType', page).val(); - currentSubProfile.Container = $('#txtDirectPlayContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtDirectPlayAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtDirectPlayVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.DirectPlayProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#popupEditDirectPlayProfile', page)[0]); - } - - function renderDirectPlayProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.directPlayProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteDirectPlayProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); - }); - } - - function deleteDirectPlayProfile(page, index) { - currentProfile.DirectPlayProfiles.splice(index, 1); - renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); - } - - function editDirectPlayProfile(page, directPlayProfile) { - isSubProfileNew = directPlayProfile == null; - directPlayProfile = directPlayProfile || {}; - currentSubProfile = directPlayProfile; - const popup = $('#popupEditDirectPlayProfile', page); - $('#selectDirectPlayProfileType', popup).val(directPlayProfile.Type || 'Video').trigger('change'); - $('#txtDirectPlayContainer', popup).val(directPlayProfile.Container || ''); - $('#txtDirectPlayAudioCodec', popup).val(directPlayProfile.AudioCodec || ''); - $('#txtDirectPlayVideoCodec', popup).val(directPlayProfile.VideoCodec || ''); - openPopup(popup[0]); - } - - function renderTranscodingProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.transcodingProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteTranscodingProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); - }); - } - - function editTranscodingProfile(page, transcodingProfile) { - isSubProfileNew = transcodingProfile == null; - transcodingProfile = transcodingProfile || {}; - currentSubProfile = transcodingProfile; - const popup = $('#transcodingProfilePopup', page); - $('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').trigger('change'); - $('#txtTranscodingContainer', popup).val(transcodingProfile.Container || ''); - $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); - $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); - $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); - $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); - $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); - $('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes'); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function deleteTranscodingProfile(page, index) { - currentProfile.TranscodingProfiles.splice(index, 1); - renderTranscodingProfiles(page, currentProfile.TranscodingProfiles); - } - - function saveTranscodingProfile(page) { - currentSubProfile.Type = $('#selectTranscodingProfileType', page).val(); - currentSubProfile.Container = $('#txtTranscodingContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); - currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); - currentSubProfile.Context = 'Streaming'; - 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); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#transcodingProfilePopup', page)[0]); - } - - function renderContainerProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.containerProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteContainerProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editContainerProfile(page, currentProfile.ContainerProfiles[index]); - }); - } - - function deleteContainerProfile(page, index) { - currentProfile.ContainerProfiles.splice(index, 1); - renderContainerProfiles(page, currentProfile.ContainerProfiles); - } - - function editContainerProfile(page, containerProfile) { - isSubProfileNew = containerProfile == null; - containerProfile = containerProfile || {}; - currentSubProfile = containerProfile; - const popup = $('#containerProfilePopup', page); - $('#selectContainerProfileType', popup).val(containerProfile.Type || 'Video').trigger('change'); - $('#txtContainerProfileContainer', popup).val(containerProfile.Container || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveContainerProfile(page) { - currentSubProfile.Type = $('#selectContainerProfileType', page).val(); - currentSubProfile.Container = $('#txtContainerProfileContainer', page).val(); - - if (isSubProfileNew) { - currentProfile.ContainerProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#containerProfilePopup', page)[0]); - } - - function renderCodecProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.codecProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteCodecProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editCodecProfile(page, currentProfile.CodecProfiles[index]); - }); - } - - function deleteCodecProfile(page, index) { - currentProfile.CodecProfiles.splice(index, 1); - renderCodecProfiles(page, currentProfile.CodecProfiles); - } - - function editCodecProfile(page, codecProfile) { - isSubProfileNew = codecProfile == null; - codecProfile = codecProfile || {}; - currentSubProfile = codecProfile; - const popup = $('#codecProfilePopup', page); - $('#selectCodecProfileType', popup).val(codecProfile.Type || 'Video').trigger('change'); - $('#txtCodecProfileCodec', popup).val(codecProfile.Codec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveCodecProfile(page) { - currentSubProfile.Type = $('#selectCodecProfileType', page).val(); - currentSubProfile.Codec = $('#txtCodecProfileCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.CodecProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#codecProfilePopup', page)[0]); - } - - function renderResponseProfiles(page, profiles) { - let html = ''; - html += ''; - const elem = $('.mediaProfiles', page).html(html).trigger('create'); - $('.btnDeleteProfile', elem).on('click', function () { - const index = this.getAttribute('data-profileindex'); - deleteResponseProfile(page, index); - }); - $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex'), 10); - editResponseProfile(page, currentProfile.ResponseProfiles[index]); - }); - } - - function deleteResponseProfile(page, index) { - currentProfile.ResponseProfiles.splice(index, 1); - renderResponseProfiles(page, currentProfile.ResponseProfiles); - } - - function editResponseProfile(page, responseProfile) { - isSubProfileNew = responseProfile == null; - responseProfile = responseProfile || {}; - currentSubProfile = responseProfile; - const popup = $('#responseProfilePopup', page); - $('#selectResponseProfileType', popup).val(responseProfile.Type || 'Video').trigger('change'); - $('#txtResponseProfileContainer', popup).val(responseProfile.Container || ''); - $('#txtResponseProfileAudioCodec', popup).val(responseProfile.AudioCodec || ''); - $('#txtResponseProfileVideoCodec', popup).val(responseProfile.VideoCodec || ''); - $('.radioTabButton:first', popup).trigger('click'); - openPopup(popup[0]); - } - - function saveResponseProfile(page) { - currentSubProfile.Type = $('#selectResponseProfileType', page).val(); - currentSubProfile.Container = $('#txtResponseProfileContainer', page).val(); - currentSubProfile.AudioCodec = $('#txtResponseProfileAudioCodec', page).val(); - currentSubProfile.VideoCodec = $('#txtResponseProfileVideoCodec', page).val(); - - if (isSubProfileNew) { - currentProfile.ResponseProfiles.push(currentSubProfile); - } - - renderSubProfiles(page, currentProfile); - currentSubProfile = null; - closePopup($('#responseProfilePopup', page)[0]); - } - - function saveProfile(page, profile) { - updateProfile(page, profile); - const id = getParameterByName('id'); - - if (id) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles/' + id), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - toast(globalize.translate('SettingsSaved')); - }, Dashboard.processErrorResponse); - } else { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Dlna/Profiles'), - data: JSON.stringify(profile), - contentType: 'application/json' - }).then(function () { - Dashboard.navigate('dlnaprofiles.html'); - }, Dashboard.processErrorResponse); - } - +function loadProfile(page) { + loading.show(); + const promise1 = getProfile(); + const promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + currentProfile = responses[0]; + renderProfile(page, currentProfile, responses[1]); loading.hide(); - } - - function updateProfile(page, profile) { - profile.Name = $('#txtName', page).val(); - 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(','); - profile.Identification = profile.Identification || {}; - profile.FriendlyName = $('#txtInfoFriendlyName', page).val(); - profile.ModelName = $('#txtInfoModelName', page).val(); - profile.ModelNumber = $('#txtInfoModelNumber', page).val(); - profile.ModelDescription = $('#txtInfoModelDescription', page).val(); - profile.ModelUrl = $('#txtInfoModelUrl', page).val(); - profile.Manufacturer = $('#txtInfoManufacturer', page).val(); - profile.ManufacturerUrl = $('#txtInfoManufacturerUrl', page).val(); - profile.SerialNumber = $('#txtInfoSerialNumber', page).val(); - profile.Identification.FriendlyName = $('#txtIdFriendlyName', page).val(); - profile.Identification.ModelName = $('#txtIdModelName', page).val(); - profile.Identification.ModelNumber = $('#txtIdModelNumber', page).val(); - profile.Identification.ModelDescription = $('#txtIdModelDescription', page).val(); - profile.Identification.ModelUrl = $('#txtIdModelUrl', page).val(); - profile.Identification.Manufacturer = $('#txtIdManufacturer', page).val(); - profile.Identification.ManufacturerUrl = $('#txtIdManufacturerUrl', page).val(); - profile.Identification.SerialNumber = $('#txtIdSerialNumber', page).val(); - profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); - profile.AlbumArtPn = $('#txtAlbumArtPn', page).val(); - profile.MaxAlbumArtWidth = $('#txtAlbumArtMaxWidth', page).val(); - profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); - profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); - profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); - 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(); - profile.XDlnaCap = $('#txtXDlnaCap', page).val(); - profile.XDlnaDoc = $('#txtXDlnaDoc', page).val(); - profile.SonyAggregationFlags = $('#txtSonyAggregationFlags', page).val(); - profile.UserId = $('#selectUser', page).val(); - } - - let currentProfile; - let currentSubProfile; - let isSubProfileNew; - const allText = globalize.translate('All'); - - $(document).on('pageinit', '#dlnaProfilePage', function () { - const page = this; - $('.radioTabButton', page).on('click', function () { - $(this).siblings().removeClass('ui-btn-active'); - $(this).addClass('ui-btn-active'); - const value = this.tagName == 'A' ? this.getAttribute('data-value') : this.value; - const elem = $('.' + value, page); - elem.siblings('.tabContent').hide(); - elem.show(); - }); - $('#selectDirectPlayProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldDirectPlayVideoCodec', page).show(); - } else { - $('#fldDirectPlayVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldDirectPlayAudioCodec', page).hide(); - } else { - $('#fldDirectPlayAudioCodec', page).show(); - } - }); - $('#selectTranscodingProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldTranscodingVideoCodec', page).show(); - $('#fldTranscodingProtocol', page).show(); - $('#fldEnableMpegtsM2TsMode', page).show(); - } else { - $('#fldTranscodingVideoCodec', page).hide(); - $('#fldTranscodingProtocol', page).hide(); - $('#fldEnableMpegtsM2TsMode', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldTranscodingAudioCodec', page).hide(); - $('#fldEstimateContentLength', page).hide(); - $('#fldReportByteRangeRequests', page).hide(); - } else { - $('#fldTranscodingAudioCodec', page).show(); - $('#fldEstimateContentLength', page).show(); - $('#fldReportByteRangeRequests', page).show(); - } - }); - $('#selectResponseProfileType', page).on('change', function () { - if (this.value == 'Video') { - $('#fldResponseProfileVideoCodec', page).show(); - } else { - $('#fldResponseProfileVideoCodec', page).hide(); - } - - if (this.value == 'Photo') { - $('#fldResponseProfileAudioCodec', page).hide(); - } else { - $('#fldResponseProfileAudioCodec', page).show(); - } - }); - $('.btnAddDirectPlayProfile', page).on('click', function () { - editDirectPlayProfile(page); - }); - $('.btnAddTranscodingProfile', page).on('click', function () { - editTranscodingProfile(page); - }); - $('.btnAddContainerProfile', page).on('click', function () { - editContainerProfile(page); - }); - $('.btnAddCodecProfile', page).on('click', function () { - editCodecProfile(page); - }); - $('.btnAddResponseProfile', page).on('click', function () { - editResponseProfile(page); - }); - $('.btnAddIdentificationHttpHeader', page).on('click', function () { - editIdentificationHeader(page); - }); - $('.btnAddXmlDocumentAttribute', page).on('click', function () { - editXmlDocumentAttribute(page); - }); - $('.btnAddSubtitleProfile', page).on('click', function () { - editSubtitleProfile(page); - }); - $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); - $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); - $('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit); - $('.containerProfileForm').off('submit', DlnaProfilePage.onContainerProfileFormSubmit).on('submit', DlnaProfilePage.onContainerProfileFormSubmit); - $('.codecProfileForm').off('submit', DlnaProfilePage.onCodecProfileFormSubmit).on('submit', DlnaProfilePage.onCodecProfileFormSubmit); - $('.editResponseProfileForm').off('submit', DlnaProfilePage.onResponseProfileFormSubmit).on('submit', DlnaProfilePage.onResponseProfileFormSubmit); - $('.identificationHeaderForm').off('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit).on('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit); - $('.xmlAttributeForm').off('submit', DlnaProfilePage.onXmlAttributeFormSubmit).on('submit', DlnaProfilePage.onXmlAttributeFormSubmit); - $('.subtitleProfileForm').off('submit', DlnaProfilePage.onSubtitleProfileFormSubmit).on('submit', DlnaProfilePage.onSubtitleProfileFormSubmit); - }).on('pageshow', '#dlnaProfilePage', function () { - const page = this; - $('#radioInfo', page).trigger('click'); - loadProfile(page); }); - window.DlnaProfilePage = { - onSubmit: function () { - loading.show(); - saveProfile($(this).parents('.page'), currentProfile); - return false; - }, - onDirectPlayFormSubmit: function () { - saveDirectPlayProfile($(this).parents('.page')); - return false; - }, - onTranscodingProfileFormSubmit: function () { - saveTranscodingProfile($(this).parents('.page')); - return false; - }, - onContainerProfileFormSubmit: function () { - saveContainerProfile($(this).parents('.page')); - return false; - }, - onCodecProfileFormSubmit: function () { - saveCodecProfile($(this).parents('.page')); - return false; - }, - onResponseProfileFormSubmit: function () { - saveResponseProfile($(this).parents('.page')); - return false; - }, - onIdentificationHeaderFormSubmit: function () { - saveIdentificationHeader($(this).parents('.page')); - return false; - }, - onXmlAttributeFormSubmit: function () { - saveXmlDocumentAttribute($(this).parents('.page')); - return false; - }, - onSubtitleProfileFormSubmit: function () { - saveSubtitleProfile($(this).parents('.page')); - return false; - } - }; +} + +function getProfile() { + const id = getParameterByName('id'); + const url = id ? 'Dlna/Profiles/' + id : 'Dlna/Profiles/Default'; + return ApiClient.getJSON(ApiClient.getUrl(url)); +} + +function renderProfile(page, profile, users) { + $('#txtName', page).val(profile.Name); + $('.chkMediaType', page).each(function () { + this.checked = (profile.SupportedMediaTypes || '').split(',').indexOf(this.getAttribute('data-value')) != -1; + }); + $('#chkEnableAlbumArtInDidl', page).prop('checked', profile.EnableAlbumArtInDidl); + $('#chkEnableSingleImageLimit', page).prop('checked', profile.EnableSingleAlbumArtLimit); + renderXmlDocumentAttributes(page, profile.XmlRootAttributes || []); + const idInfo = profile.Identification || {}; + renderIdentificationHeaders(page, idInfo.Headers || []); + renderSubtitleProfiles(page, profile.SubtitleProfiles || []); + $('#txtInfoFriendlyName', page).val(profile.FriendlyName || ''); + $('#txtInfoModelName', page).val(profile.ModelName || ''); + $('#txtInfoModelNumber', page).val(profile.ModelNumber || ''); + $('#txtInfoModelDescription', page).val(profile.ModelDescription || ''); + $('#txtInfoModelUrl', page).val(profile.ModelUrl || ''); + $('#txtInfoManufacturer', page).val(profile.Manufacturer || ''); + $('#txtInfoManufacturerUrl', page).val(profile.ManufacturerUrl || ''); + $('#txtInfoSerialNumber', page).val(profile.SerialNumber || ''); + $('#txtIdFriendlyName', page).val(idInfo.FriendlyName || ''); + $('#txtIdModelName', page).val(idInfo.ModelName || ''); + $('#txtIdModelNumber', page).val(idInfo.ModelNumber || ''); + $('#txtIdModelDescription', page).val(idInfo.ModelDescription || ''); + $('#txtIdModelUrl', page).val(idInfo.ModelUrl || ''); + $('#txtIdManufacturer', page).val(idInfo.Manufacturer || ''); + $('#txtIdManufacturerUrl', page).val(idInfo.ManufacturerUrl || ''); + $('#txtIdSerialNumber', page).val(idInfo.SerialNumber || ''); + $('#txtIdDeviceDescription', page).val(idInfo.DeviceDescription || ''); + $('#txtAlbumArtPn', page).val(profile.AlbumArtPn || ''); + $('#txtAlbumArtMaxWidth', page).val(profile.MaxAlbumArtWidth || ''); + $('#txtAlbumArtMaxHeight', page).val(profile.MaxAlbumArtHeight || ''); + $('#txtIconMaxWidth', page).val(profile.MaxIconWidth || ''); + $('#txtIconMaxHeight', page).val(profile.MaxIconHeight || ''); + $('#chkIgnoreTranscodeByteRangeRequests', page).prop('checked', profile.IgnoreTranscodeByteRangeRequests); + $('#txtMaxAllowedBitrate', page).val(profile.MaxStreamingBitrate || ''); + $('#txtMusicStreamingTranscodingBitrate', page).val(profile.MusicStreamingTranscodingBitrate || ''); + $('#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 || ''); + $('#txtSonyAggregationFlags', page).val(profile.SonyAggregationFlags || ''); + profile.DirectPlayProfiles = profile.DirectPlayProfiles || []; + profile.TranscodingProfiles = profile.TranscodingProfiles || []; + profile.ContainerProfiles = profile.ContainerProfiles || []; + profile.CodecProfiles = profile.CodecProfiles || []; + profile.ResponseProfiles = profile.ResponseProfiles || []; + const usersHtml = '' + users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(profile.UserId || ''); + renderSubProfiles(page, profile); +} + +function renderIdentificationHeaders(page, headers) { + let index = 0; + const html = '
' + headers.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Name + ': ' + (h.Value || '')) + '

'; + li += '
' + escapeHtml(h.Match || '') + '
'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); + $('.btnDeleteIdentificationHeader', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.Identification.Headers.splice(itemIndex, 1); + renderIdentificationHeaders(page, currentProfile.Identification.Headers); + }); +} + +function openPopup(elem) { + elem.classList.remove('hide'); +} + +function closePopup(elem) { + elem.classList.add('hide'); +} + +function editIdentificationHeader(page, header) { + isSubProfileNew = header == null; + header = header || {}; + currentSubProfile = header; + const popup = $('#identificationHeaderPopup', page); + $('#txtIdentificationHeaderName', popup).val(header.Name || ''); + $('#txtIdentificationHeaderValue', popup).val(header.Value || ''); + $('#selectMatchType', popup).val(header.Match || 'Equals'); + openPopup(popup[0]); +} + +function saveIdentificationHeader(page) { + currentSubProfile.Name = $('#txtIdentificationHeaderName', page).val(); + currentSubProfile.Value = $('#txtIdentificationHeaderValue', page).val(); + currentSubProfile.Match = $('#selectMatchType', page).val(); + + if (isSubProfileNew) { + currentProfile.Identification = currentProfile.Identification || {}; + currentProfile.Identification.Headers = currentProfile.Identification.Headers || []; + currentProfile.Identification.Headers.push(currentSubProfile); + } + + renderIdentificationHeaders(page, currentProfile.Identification.Headers); + currentSubProfile = null; + closePopup($('#identificationHeaderPopup', page)[0]); +} + +function renderXmlDocumentAttributes(page, attribute) { + const html = '
' + attribute.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; + li += '
'; + li += ''; + li += '
'; + return li; + }).join('') + '
'; + const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); + $('.btnDeleteXmlAttribute', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.XmlRootAttributes.splice(itemIndex, 1); + renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); + }); +} + +function editXmlDocumentAttribute(page, attribute) { + isSubProfileNew = attribute == null; + attribute = attribute || {}; + currentSubProfile = attribute; + const popup = $('#xmlAttributePopup', page); + $('#txtXmlAttributeName', popup).val(attribute.Name || ''); + $('#txtXmlAttributeValue', popup).val(attribute.Value || ''); + openPopup(popup[0]); +} + +function saveXmlDocumentAttribute(page) { + currentSubProfile.Name = $('#txtXmlAttributeName', page).val(); + currentSubProfile.Value = $('#txtXmlAttributeValue', page).val(); + + if (isSubProfileNew) { + currentProfile.XmlRootAttributes.push(currentSubProfile); + } + + renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); + currentSubProfile = null; + closePopup($('#xmlAttributePopup', page)[0]); +} + +function renderSubtitleProfiles(page, profiles) { + let index = 0; + const html = '
' + profiles.map(function (h) { + let li = '
'; + li += ''; + li += '
'; + li += '

' + escapeHtml(h.Format || '') + '

'; + li += '
'; + li += ''; + li += '
'; + index++; + return li; + }).join('') + '
'; + const elem = $('.subtitleProfileList', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + currentProfile.SubtitleProfiles.splice(itemIndex, 1); + renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const itemIndex = parseInt(this.getAttribute('data-index'), 10); + editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); + }); +} + +function editSubtitleProfile(page, profile) { + isSubProfileNew = profile == null; + profile = profile || {}; + currentSubProfile = profile; + const popup = $('#subtitleProfilePopup', page); + $('#txtSubtitleProfileFormat', popup).val(profile.Format || ''); + $('#selectSubtitleProfileMethod', popup).val(profile.Method || ''); + $('#selectSubtitleProfileDidlMode', popup).val(profile.DidlMode || ''); + openPopup(popup[0]); +} + +function saveSubtitleProfile(page) { + currentSubProfile.Format = $('#txtSubtitleProfileFormat', page).val(); + currentSubProfile.Method = $('#selectSubtitleProfileMethod', page).val(); + currentSubProfile.DidlMode = $('#selectSubtitleProfileDidlMode', page).val(); + + if (isSubProfileNew) { + currentProfile.SubtitleProfiles.push(currentSubProfile); + } + + renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); + currentSubProfile = null; + closePopup($('#subtitleProfilePopup', page)[0]); +} + +function renderSubProfiles(page, profile) { + renderDirectPlayProfiles(page, profile.DirectPlayProfiles); + renderTranscodingProfiles(page, profile.TranscodingProfiles); + renderContainerProfiles(page, profile.ContainerProfiles); + renderCodecProfiles(page, profile.CodecProfiles); + renderResponseProfiles(page, profile.ResponseProfiles); +} + +function saveDirectPlayProfile(page) { + currentSubProfile.Type = $('#selectDirectPlayProfileType', page).val(); + currentSubProfile.Container = $('#txtDirectPlayContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtDirectPlayAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtDirectPlayVideoCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.DirectPlayProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#popupEditDirectPlayProfile', page)[0]); +} + +function renderDirectPlayProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.directPlayProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteDirectPlayProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); + }); +} + +function deleteDirectPlayProfile(page, index) { + currentProfile.DirectPlayProfiles.splice(index, 1); + renderDirectPlayProfiles(page, currentProfile.DirectPlayProfiles); +} + +function editDirectPlayProfile(page, directPlayProfile) { + isSubProfileNew = directPlayProfile == null; + directPlayProfile = directPlayProfile || {}; + currentSubProfile = directPlayProfile; + const popup = $('#popupEditDirectPlayProfile', page); + $('#selectDirectPlayProfileType', popup).val(directPlayProfile.Type || 'Video').trigger('change'); + $('#txtDirectPlayContainer', popup).val(directPlayProfile.Container || ''); + $('#txtDirectPlayAudioCodec', popup).val(directPlayProfile.AudioCodec || ''); + $('#txtDirectPlayVideoCodec', popup).val(directPlayProfile.VideoCodec || ''); + openPopup(popup[0]); +} + +function renderTranscodingProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.transcodingProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteTranscodingProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); + }); +} + +function editTranscodingProfile(page, transcodingProfile) { + isSubProfileNew = transcodingProfile == null; + transcodingProfile = transcodingProfile || {}; + currentSubProfile = transcodingProfile; + const popup = $('#transcodingProfilePopup', page); + $('#selectTranscodingProfileType', popup).val(transcodingProfile.Type || 'Video').trigger('change'); + $('#txtTranscodingContainer', popup).val(transcodingProfile.Container || ''); + $('#txtTranscodingAudioCodec', popup).val(transcodingProfile.AudioCodec || ''); + $('#txtTranscodingVideoCodec', popup).val(transcodingProfile.VideoCodec || ''); + $('#selectTranscodingProtocol', popup).val(transcodingProfile.Protocol || 'Http'); + $('#chkEnableMpegtsM2TsMode', popup).prop('checked', transcodingProfile.EnableMpegtsM2TsMode || false); + $('#chkEstimateContentLength', popup).prop('checked', transcodingProfile.EstimateContentLength || false); + $('#chkReportByteRangeRequests', popup).prop('checked', transcodingProfile.TranscodeSeekInfo == 'Bytes'); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function deleteTranscodingProfile(page, index) { + currentProfile.TranscodingProfiles.splice(index, 1); + renderTranscodingProfiles(page, currentProfile.TranscodingProfiles); +} + +function saveTranscodingProfile(page) { + currentSubProfile.Type = $('#selectTranscodingProfileType', page).val(); + currentSubProfile.Container = $('#txtTranscodingContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtTranscodingAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtTranscodingVideoCodec', page).val(); + currentSubProfile.Protocol = $('#selectTranscodingProtocol', page).val(); + currentSubProfile.Context = 'Streaming'; + 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); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#transcodingProfilePopup', page)[0]); +} + +function renderContainerProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.containerProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteContainerProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editContainerProfile(page, currentProfile.ContainerProfiles[index]); + }); +} + +function deleteContainerProfile(page, index) { + currentProfile.ContainerProfiles.splice(index, 1); + renderContainerProfiles(page, currentProfile.ContainerProfiles); +} + +function editContainerProfile(page, containerProfile) { + isSubProfileNew = containerProfile == null; + containerProfile = containerProfile || {}; + currentSubProfile = containerProfile; + const popup = $('#containerProfilePopup', page); + $('#selectContainerProfileType', popup).val(containerProfile.Type || 'Video').trigger('change'); + $('#txtContainerProfileContainer', popup).val(containerProfile.Container || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveContainerProfile(page) { + currentSubProfile.Type = $('#selectContainerProfileType', page).val(); + currentSubProfile.Container = $('#txtContainerProfileContainer', page).val(); + + if (isSubProfileNew) { + currentProfile.ContainerProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#containerProfilePopup', page)[0]); +} + +function renderCodecProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.codecProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteCodecProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editCodecProfile(page, currentProfile.CodecProfiles[index]); + }); +} + +function deleteCodecProfile(page, index) { + currentProfile.CodecProfiles.splice(index, 1); + renderCodecProfiles(page, currentProfile.CodecProfiles); +} + +function editCodecProfile(page, codecProfile) { + isSubProfileNew = codecProfile == null; + codecProfile = codecProfile || {}; + currentSubProfile = codecProfile; + const popup = $('#codecProfilePopup', page); + $('#selectCodecProfileType', popup).val(codecProfile.Type || 'Video').trigger('change'); + $('#txtCodecProfileCodec', popup).val(codecProfile.Codec || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveCodecProfile(page) { + currentSubProfile.Type = $('#selectCodecProfileType', page).val(); + currentSubProfile.Codec = $('#txtCodecProfileCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.CodecProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#codecProfilePopup', page)[0]); +} + +function renderResponseProfiles(page, profiles) { + let html = ''; + html += ''; + const elem = $('.mediaProfiles', page).html(html).trigger('create'); + $('.btnDeleteProfile', elem).on('click', function () { + const index = this.getAttribute('data-profileindex'); + deleteResponseProfile(page, index); + }); + $('.lnkEditSubProfile', elem).on('click', function () { + const index = parseInt(this.getAttribute('data-profileindex'), 10); + editResponseProfile(page, currentProfile.ResponseProfiles[index]); + }); +} + +function deleteResponseProfile(page, index) { + currentProfile.ResponseProfiles.splice(index, 1); + renderResponseProfiles(page, currentProfile.ResponseProfiles); +} + +function editResponseProfile(page, responseProfile) { + isSubProfileNew = responseProfile == null; + responseProfile = responseProfile || {}; + currentSubProfile = responseProfile; + const popup = $('#responseProfilePopup', page); + $('#selectResponseProfileType', popup).val(responseProfile.Type || 'Video').trigger('change'); + $('#txtResponseProfileContainer', popup).val(responseProfile.Container || ''); + $('#txtResponseProfileAudioCodec', popup).val(responseProfile.AudioCodec || ''); + $('#txtResponseProfileVideoCodec', popup).val(responseProfile.VideoCodec || ''); + $('.radioTabButton:first', popup).trigger('click'); + openPopup(popup[0]); +} + +function saveResponseProfile(page) { + currentSubProfile.Type = $('#selectResponseProfileType', page).val(); + currentSubProfile.Container = $('#txtResponseProfileContainer', page).val(); + currentSubProfile.AudioCodec = $('#txtResponseProfileAudioCodec', page).val(); + currentSubProfile.VideoCodec = $('#txtResponseProfileVideoCodec', page).val(); + + if (isSubProfileNew) { + currentProfile.ResponseProfiles.push(currentSubProfile); + } + + renderSubProfiles(page, currentProfile); + currentSubProfile = null; + closePopup($('#responseProfilePopup', page)[0]); +} + +function saveProfile(page, profile) { + updateProfile(page, profile); + const id = getParameterByName('id'); + + if (id) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Dlna/Profiles/' + id), + data: JSON.stringify(profile), + contentType: 'application/json' + }).then(function () { + toast(globalize.translate('SettingsSaved')); + }, Dashboard.processErrorResponse); + } else { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Dlna/Profiles'), + data: JSON.stringify(profile), + contentType: 'application/json' + }).then(function () { + Dashboard.navigate('dlnaprofiles.html'); + }, Dashboard.processErrorResponse); + } + + loading.hide(); +} + +function updateProfile(page, profile) { + profile.Name = $('#txtName', page).val(); + 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(','); + profile.Identification = profile.Identification || {}; + profile.FriendlyName = $('#txtInfoFriendlyName', page).val(); + profile.ModelName = $('#txtInfoModelName', page).val(); + profile.ModelNumber = $('#txtInfoModelNumber', page).val(); + profile.ModelDescription = $('#txtInfoModelDescription', page).val(); + profile.ModelUrl = $('#txtInfoModelUrl', page).val(); + profile.Manufacturer = $('#txtInfoManufacturer', page).val(); + profile.ManufacturerUrl = $('#txtInfoManufacturerUrl', page).val(); + profile.SerialNumber = $('#txtInfoSerialNumber', page).val(); + profile.Identification.FriendlyName = $('#txtIdFriendlyName', page).val(); + profile.Identification.ModelName = $('#txtIdModelName', page).val(); + profile.Identification.ModelNumber = $('#txtIdModelNumber', page).val(); + profile.Identification.ModelDescription = $('#txtIdModelDescription', page).val(); + profile.Identification.ModelUrl = $('#txtIdModelUrl', page).val(); + profile.Identification.Manufacturer = $('#txtIdManufacturer', page).val(); + profile.Identification.ManufacturerUrl = $('#txtIdManufacturerUrl', page).val(); + profile.Identification.SerialNumber = $('#txtIdSerialNumber', page).val(); + profile.Identification.DeviceDescription = $('#txtIdDeviceDescription', page).val(); + profile.AlbumArtPn = $('#txtAlbumArtPn', page).val(); + profile.MaxAlbumArtWidth = $('#txtAlbumArtMaxWidth', page).val(); + profile.MaxAlbumArtHeight = $('#txtAlbumArtMaxHeight', page).val(); + profile.MaxIconWidth = $('#txtIconMaxWidth', page).val(); + profile.MaxIconHeight = $('#txtIconMaxHeight', page).val(); + 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(); + profile.XDlnaCap = $('#txtXDlnaCap', page).val(); + profile.XDlnaDoc = $('#txtXDlnaDoc', page).val(); + profile.SonyAggregationFlags = $('#txtSonyAggregationFlags', page).val(); + profile.UserId = $('#selectUser', page).val(); +} + +let currentProfile; +let currentSubProfile; +let isSubProfileNew; +const allText = globalize.translate('All'); + +$(document).on('pageinit', '#dlnaProfilePage', function () { + const page = this; + $('.radioTabButton', page).on('click', function () { + $(this).siblings().removeClass('ui-btn-active'); + $(this).addClass('ui-btn-active'); + const value = this.tagName == 'A' ? this.getAttribute('data-value') : this.value; + const elem = $('.' + value, page); + elem.siblings('.tabContent').hide(); + elem.show(); + }); + $('#selectDirectPlayProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldDirectPlayVideoCodec', page).show(); + } else { + $('#fldDirectPlayVideoCodec', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldDirectPlayAudioCodec', page).hide(); + } else { + $('#fldDirectPlayAudioCodec', page).show(); + } + }); + $('#selectTranscodingProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldTranscodingVideoCodec', page).show(); + $('#fldTranscodingProtocol', page).show(); + $('#fldEnableMpegtsM2TsMode', page).show(); + } else { + $('#fldTranscodingVideoCodec', page).hide(); + $('#fldTranscodingProtocol', page).hide(); + $('#fldEnableMpegtsM2TsMode', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldTranscodingAudioCodec', page).hide(); + $('#fldEstimateContentLength', page).hide(); + $('#fldReportByteRangeRequests', page).hide(); + } else { + $('#fldTranscodingAudioCodec', page).show(); + $('#fldEstimateContentLength', page).show(); + $('#fldReportByteRangeRequests', page).show(); + } + }); + $('#selectResponseProfileType', page).on('change', function () { + if (this.value == 'Video') { + $('#fldResponseProfileVideoCodec', page).show(); + } else { + $('#fldResponseProfileVideoCodec', page).hide(); + } + + if (this.value == 'Photo') { + $('#fldResponseProfileAudioCodec', page).hide(); + } else { + $('#fldResponseProfileAudioCodec', page).show(); + } + }); + $('.btnAddDirectPlayProfile', page).on('click', function () { + editDirectPlayProfile(page); + }); + $('.btnAddTranscodingProfile', page).on('click', function () { + editTranscodingProfile(page); + }); + $('.btnAddContainerProfile', page).on('click', function () { + editContainerProfile(page); + }); + $('.btnAddCodecProfile', page).on('click', function () { + editCodecProfile(page); + }); + $('.btnAddResponseProfile', page).on('click', function () { + editResponseProfile(page); + }); + $('.btnAddIdentificationHttpHeader', page).on('click', function () { + editIdentificationHeader(page); + }); + $('.btnAddXmlDocumentAttribute', page).on('click', function () { + editXmlDocumentAttribute(page); + }); + $('.btnAddSubtitleProfile', page).on('click', function () { + editSubtitleProfile(page); + }); + $('.dlnaProfileForm').off('submit', DlnaProfilePage.onSubmit).on('submit', DlnaProfilePage.onSubmit); + $('.editDirectPlayProfileForm').off('submit', DlnaProfilePage.onDirectPlayFormSubmit).on('submit', DlnaProfilePage.onDirectPlayFormSubmit); + $('.transcodingProfileForm').off('submit', DlnaProfilePage.onTranscodingProfileFormSubmit).on('submit', DlnaProfilePage.onTranscodingProfileFormSubmit); + $('.containerProfileForm').off('submit', DlnaProfilePage.onContainerProfileFormSubmit).on('submit', DlnaProfilePage.onContainerProfileFormSubmit); + $('.codecProfileForm').off('submit', DlnaProfilePage.onCodecProfileFormSubmit).on('submit', DlnaProfilePage.onCodecProfileFormSubmit); + $('.editResponseProfileForm').off('submit', DlnaProfilePage.onResponseProfileFormSubmit).on('submit', DlnaProfilePage.onResponseProfileFormSubmit); + $('.identificationHeaderForm').off('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit).on('submit', DlnaProfilePage.onIdentificationHeaderFormSubmit); + $('.xmlAttributeForm').off('submit', DlnaProfilePage.onXmlAttributeFormSubmit).on('submit', DlnaProfilePage.onXmlAttributeFormSubmit); + $('.subtitleProfileForm').off('submit', DlnaProfilePage.onSubtitleProfileFormSubmit).on('submit', DlnaProfilePage.onSubtitleProfileFormSubmit); +}).on('pageshow', '#dlnaProfilePage', function () { + const page = this; + $('#radioInfo', page).trigger('click'); + loadProfile(page); +}); +window.DlnaProfilePage = { + onSubmit: function () { + loading.show(); + saveProfile($(this).parents('.page'), currentProfile); + return false; + }, + onDirectPlayFormSubmit: function () { + saveDirectPlayProfile($(this).parents('.page')); + return false; + }, + onTranscodingProfileFormSubmit: function () { + saveTranscodingProfile($(this).parents('.page')); + return false; + }, + onContainerProfileFormSubmit: function () { + saveContainerProfile($(this).parents('.page')); + return false; + }, + onCodecProfileFormSubmit: function () { + saveCodecProfile($(this).parents('.page')); + return false; + }, + onResponseProfileFormSubmit: function () { + saveResponseProfile($(this).parents('.page')); + return false; + }, + onIdentificationHeaderFormSubmit: function () { + saveIdentificationHeader($(this).parents('.page')); + return false; + }, + onXmlAttributeFormSubmit: function () { + saveXmlDocumentAttribute($(this).parents('.page')); + return false; + }, + onSubtitleProfileFormSubmit: function () { + saveSubtitleProfile($(this).parents('.page')); + return false; + } +}; -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/profiles.js b/src/controllers/dashboard/dlna/profiles.js index 9a79883e9e..a7c8f045c4 100644 --- a/src/controllers/dashboard/dlna/profiles.js +++ b/src/controllers/dashboard/dlna/profiles.js @@ -7,90 +7,87 @@ import '../../../components/listview/listview.scss'; import '../../../elements/emby-button/emby-button'; import confirm from '../../../components/confirm/confirm'; -/* eslint-disable indent */ - - function loadProfiles(page) { - loading.show(); - ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { - renderUserProfiles(page, result); - renderSystemProfiles(page, result); - loading.hide(); - }); - } - - function renderUserProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) { - return p.Type == 'User'; - })); - } - - function renderSystemProfiles(page, profiles) { - renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { - return p.Type == 'System'; - })); - } - - function renderProfiles(page, element, profiles) { - let html = ''; - - if (profiles.length) { - html += '
'; - } - - for (let i = 0, length = profiles.length; i < length; i++) { - const profile = profiles[i]; - html += '
'; - html += ''; - html += ''; - - if (profile.Type == 'User') { - html += ''; - } - - html += '
'; - } - - if (profiles.length) { - html += '
'; - } - - element.innerHTML = html; - $('.btnDeleteProfile', element).on('click', function () { - const id = this.getAttribute('data-profileid'); - deleteProfile(page, id); - }); - } - - function deleteProfile(page, id) { - confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () { - loading.show(); - ApiClient.ajax({ - type: 'DELETE', - url: ApiClient.getUrl('Dlna/Profiles/' + id) - }).then(function () { - loading.hide(); - loadProfiles(page); - }); - }); - } - - function getTabs() { - return [{ - href: '#/dlnasettings.html', - name: globalize.translate('Settings') - }, { - href: '#/dlnaprofiles.html', - name: globalize.translate('TabProfiles') - }]; - } - - $(document).on('pageshow', '#dlnaProfilesPage', function () { - libraryMenu.setTabs('dlna', 1, getTabs); - loadProfiles(this); +function loadProfiles(page) { + loading.show(); + ApiClient.getJSON(ApiClient.getUrl('Dlna/ProfileInfos')).then(function (result) { + renderUserProfiles(page, result); + renderSystemProfiles(page, result); + loading.hide(); }); +} + +function renderUserProfiles(page, profiles) { + renderProfiles(page, page.querySelector('.customProfiles'), profiles.filter(function (p) { + return p.Type == 'User'; + })); +} + +function renderSystemProfiles(page, profiles) { + renderProfiles(page, page.querySelector('.systemProfiles'), profiles.filter(function (p) { + return p.Type == 'System'; + })); +} + +function renderProfiles(page, element, profiles) { + let html = ''; + + if (profiles.length) { + html += '
'; + } + + for (let i = 0, length = profiles.length; i < length; i++) { + const profile = profiles[i]; + html += '
'; + html += ''; + html += ''; + + if (profile.Type == 'User') { + html += ''; + } + + html += '
'; + } + + if (profiles.length) { + html += '
'; + } + + element.innerHTML = html; + $('.btnDeleteProfile', element).on('click', function () { + const id = this.getAttribute('data-profileid'); + deleteProfile(page, id); + }); +} + +function deleteProfile(page, id) { + confirm(globalize.translate('MessageConfirmProfileDeletion'), globalize.translate('HeaderConfirmProfileDeletion')).then(function () { + loading.show(); + ApiClient.ajax({ + type: 'DELETE', + url: ApiClient.getUrl('Dlna/Profiles/' + id) + }).then(function () { + loading.hide(); + loadProfiles(page); + }); + }); +} + +function getTabs() { + return [{ + href: '#/dlnasettings.html', + name: globalize.translate('Settings') + }, { + href: '#/dlnaprofiles.html', + name: globalize.translate('TabProfiles') + }]; +} + +$(document).on('pageshow', '#dlnaProfilesPage', function () { + libraryMenu.setTabs('dlna', 1, getTabs); + loadProfiles(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/dlna/settings.js b/src/controllers/dashboard/dlna/settings.js index 9988d7d610..fcc7d7d100 100644 --- a/src/controllers/dashboard/dlna/settings.js +++ b/src/controllers/dashboard/dlna/settings.js @@ -5,59 +5,56 @@ import libraryMenu from '../../../scripts/libraryMenu'; import globalize from '../../../scripts/globalize'; import Dashboard from '../../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config, users) { + page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; + page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; + $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); + $('#chkEnableServer', page).prop('checked', config.EnableServer); + $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); + $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); + const usersHtml = users.map(function (u) { + return ''; + }).join(''); + $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); + loading.hide(); +} - function loadPage(page, config, users) { - page.querySelector('#chkEnablePlayTo').checked = config.EnablePlayTo; - page.querySelector('#chkEnableDlnaDebugLogging').checked = config.EnableDebugLog; - $('#txtClientDiscoveryInterval', page).val(config.ClientDiscoveryIntervalSeconds); - $('#chkEnableServer', page).prop('checked', config.EnableServer); - $('#chkBlastAliveMessages', page).prop('checked', config.BlastAliveMessages); - $('#txtBlastInterval', page).val(config.BlastAliveMessageIntervalSeconds); - const usersHtml = users.map(function (u) { - return ''; - }).join(''); - $('#selectUser', page).html(usersHtml).val(config.DefaultUserId || ''); - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration('dlna').then(function (config) { - config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; - config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; - config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); - 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); - }); - return false; - } - - function getTabs() { - return [{ - href: '#/dlnasettings.html', - name: globalize.translate('Settings') - }, { - href: '#/dlnaprofiles.html', - name: globalize.translate('TabProfiles') - }]; - } - - $(document).on('pageinit', '#dlnaSettingsPage', function () { - $('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#dlnaSettingsPage', function () { - libraryMenu.setTabs('dlna', 0, getTabs); - loading.show(); - const page = this; - const promise1 = ApiClient.getNamedConfiguration('dlna'); - const promise2 = ApiClient.getUsers(); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[0], responses[1]); - }); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getNamedConfiguration('dlna').then(function (config) { + config.EnablePlayTo = form.querySelector('#chkEnablePlayTo').checked; + config.EnableDebugLog = form.querySelector('#chkEnableDlnaDebugLogging').checked; + config.ClientDiscoveryIntervalSeconds = $('#txtClientDiscoveryInterval', form).val(); + 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); }); + return false; +} + +function getTabs() { + return [{ + href: '#/dlnasettings.html', + name: globalize.translate('Settings') + }, { + href: '#/dlnaprofiles.html', + name: globalize.translate('TabProfiles') + }]; +} + +$(document).on('pageinit', '#dlnaSettingsPage', function () { + $('.dlnaSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#dlnaSettingsPage', function () { + libraryMenu.setTabs('dlna', 0, getTabs); + loading.show(); + const page = this; + const promise1 = ApiClient.getNamedConfiguration('dlna'); + const promise2 = ApiClient.getUsers(); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[0], responses[1]); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/encodingsettings.html b/src/controllers/dashboard/encodingsettings.html index f9790189e6..759c19d7d4 100644 --- a/src/controllers/dashboard/encodingsettings.html +++ b/src/controllers/dashboard/encodingsettings.html @@ -111,7 +111,7 @@ ${EnableIntelLowPowerHevcHwEncoder}
diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index f122d6da2a..38ca954d0e 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -6,297 +6,294 @@ import libraryMenu from '../../scripts/libraryMenu'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, systemInfo) { + Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { + c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1; + }); + page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; + page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; + page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder; + page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder; + page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder; + page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder; + page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; + page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; + $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); + $('#selectThreadCount', page).val(config.EncodingThreadCount); + page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; + $('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost); + $('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None'); + page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || ''; + page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || ''; + $('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || ''); + page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || ''; + page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont; + $('#txtVaapiDevice', page).val(config.VaapiDevice || ''); + page.querySelector('#chkTonemapping').checked = config.EnableTonemapping; + page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping; + page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm; + page.querySelector('#selectTonemappingRange').value = config.TonemappingRange; + page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat; + page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold; + page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak; + page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || ''; + page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness; + page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast; + page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || ''; + page.querySelector('#txtH264Crf').value = config.H264Crf || ''; + page.querySelector('#txtH265Crf').value = config.H265Crf || ''; + page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || ''; + page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate; + page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false; + page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false; + page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', { + bubbles: true + })); + loading.hide(); +} - function loadPage(page, config, systemInfo) { - Array.prototype.forEach.call(page.querySelectorAll('.chkDecodeCodec'), function (c) { - c.checked = (config.HardwareDecodingCodecs || []).indexOf(c.getAttribute('data-codec')) !== -1; - }); - page.querySelector('#chkDecodingColorDepth10Hevc').checked = config.EnableDecodingColorDepth10Hevc; - page.querySelector('#chkDecodingColorDepth10Vp9').checked = config.EnableDecodingColorDepth10Vp9; - page.querySelector('#chkEnhancedNvdecDecoder').checked = config.EnableEnhancedNvdecDecoder; - page.querySelector('#chkSystemNativeHwDecoder').checked = config.PreferSystemNativeHwDecoder; - page.querySelector('#chkIntelLpH264HwEncoder').checked = config.EnableIntelLowPowerH264HwEncoder; - page.querySelector('#chkIntelLpHevcHwEncoder').checked = config.EnableIntelLowPowerHevcHwEncoder; - page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; - page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; - $('#selectVideoDecoder', page).val(config.HardwareAccelerationType); - $('#selectThreadCount', page).val(config.EncodingThreadCount); - page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; - $('#txtDownMixAudioBoost', page).val(config.DownMixAudioBoost); - $('#selectStereoDownmixAlgorithm').val(config.DownMixStereoAlgorithm || 'None'); - page.querySelector('#txtMaxMuxingQueueSize').value = config.MaxMuxingQueueSize || ''; - page.querySelector('.txtEncoderPath').value = config.EncoderAppPathDisplay || ''; - $('#txtTranscodingTempPath', page).val(systemInfo.TranscodingTempPath || ''); - page.querySelector('#txtFallbackFontPath').value = config.FallbackFontPath || ''; - page.querySelector('#chkEnableFallbackFont').checked = config.EnableFallbackFont; - $('#txtVaapiDevice', page).val(config.VaapiDevice || ''); - page.querySelector('#chkTonemapping').checked = config.EnableTonemapping; - page.querySelector('#chkVppTonemapping').checked = config.EnableVppTonemapping; - page.querySelector('#selectTonemappingAlgorithm').value = config.TonemappingAlgorithm; - page.querySelector('#selectTonemappingRange').value = config.TonemappingRange; - page.querySelector('#txtTonemappingDesat').value = config.TonemappingDesat; - page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold; - page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak; - page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || ''; - page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness; - page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast; - page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || ''; - page.querySelector('#txtH264Crf').value = config.H264Crf || ''; - page.querySelector('#txtH265Crf').value = config.H265Crf || ''; - page.querySelector('#selectDeinterlaceMethod').value = config.DeinterlaceMethod || ''; - page.querySelector('#chkDoubleRateDeinterlacing').checked = config.DeinterlaceDoubleRate; - page.querySelector('#chkEnableSubtitleExtraction').checked = config.EnableSubtitleExtraction || false; - page.querySelector('#chkEnableThrottling').checked = config.EnableThrottling || false; - page.querySelector('#selectVideoDecoder').dispatchEvent(new CustomEvent('change', { - bubbles: true - })); - loading.hide(); - } +function onSaveEncodingPathFailure() { + loading.hide(); + alert(globalize.translate('FFmpegSavePathNotFound')); +} - function onSaveEncodingPathFailure() { - loading.hide(); - alert(globalize.translate('FFmpegSavePathNotFound')); - } +function updateEncoder(form) { + return ApiClient.getSystemInfo().then(function () { + return ApiClient.ajax({ + url: ApiClient.getUrl('System/MediaEncoder/Path'), + type: 'POST', + data: JSON.stringify({ + Path: form.querySelector('.txtEncoderPath').value, + PathType: 'Custom' + }), + contentType: 'application/json' + }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); + }); +} - function updateEncoder(form) { - return ApiClient.getSystemInfo().then(function () { - return ApiClient.ajax({ - url: ApiClient.getUrl('System/MediaEncoder/Path'), - type: 'POST', - data: JSON.stringify({ - Path: form.querySelector('.txtEncoderPath').value, - PathType: 'Custom' - }), - contentType: 'application/json' - }).then(Dashboard.processServerConfigurationUpdateResult, onSaveEncodingPathFailure); - }); - } +function onSubmit() { + const form = this; - function onSubmit() { - const form = this; - - const onDecoderConfirmed = function () { - loading.show(); - ApiClient.getNamedConfiguration('encoding').then(function (config) { - config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked; - config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val(); - config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None'; - config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value; - config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val(); - config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value; - config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false; - config.EncodingThreadCount = $('#selectThreadCount', form).val(); - config.HardwareAccelerationType = $('#selectVideoDecoder', form).val(); - config.VaapiDevice = $('#txtVaapiDevice', form).val(); - config.EnableTonemapping = form.querySelector('#chkTonemapping').checked; - config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked; - config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value; - config.TonemappingRange = form.querySelector('#selectTonemappingRange').value; - config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value; - config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value; - config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value; - config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0'; - config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; - config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; - config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; - config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10); - config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10); - config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; - config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; - config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; - config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked; - config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) { - return c.checked; - }), function (c) { - return c.getAttribute('data-codec'); - }); - config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked; - config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked; - config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked; - config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked; - config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked; - config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked; - config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; - config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; - ApiClient.updateNamedConfiguration('encoding', config).then(function () { - updateEncoder(form); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - }; - - if ($('#selectVideoDecoder', form).val()) { - alert({ - title: globalize.translate('TitleHardwareAcceleration'), - text: globalize.translate('HardwareAccelerationWarning') - }).then(onDecoderConfirmed); - } else { - onDecoderConfirmed(); - } - - return false; - } - - function setDecodingCodecsVisible(context, value) { - value = value || ''; - let any; - Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) { - if (c.getAttribute('data-types').split(',').indexOf(value) === -1) { - dom.parentWithTag(c, 'LABEL').classList.add('hide'); - } else { - dom.parentWithTag(c, 'LABEL').classList.remove('hide'); - any = true; - } - }); - - if (any) { - context.querySelector('.decodingCodecsList').classList.remove('hide'); - } else { - context.querySelector('.decodingCodecsList').classList.add('hide'); - } - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - let systemInfo; - function getSystemInfo() { - return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then( - info => { - systemInfo = info; - return info; - } - ); - } - - $(document).on('pageinit', '#encodingSettingsPage', function () { - const page = this; - getSystemInfo(); - page.querySelector('#selectVideoDecoder').addEventListener('change', function () { - if (this.value == 'vaapi') { - page.querySelector('.fldVaapiDevice').classList.remove('hide'); - page.querySelector('#txtVaapiDevice').setAttribute('required', 'required'); - } else { - page.querySelector('.fldVaapiDevice').classList.add('hide'); - page.querySelector('#txtVaapiDevice').removeAttribute('required'); - } - - if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') { - page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide'); - } else { - page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide'); - } - - if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') { - page.querySelector('.tonemappingOptions').classList.remove('hide'); - } else { - page.querySelector('.tonemappingOptions').classList.add('hide'); - } - - if (this.value == 'qsv' || this.value == 'vaapi') { - page.querySelector('.fldIntelLp').classList.remove('hide'); - } else { - page.querySelector('.fldIntelLp').classList.add('hide'); - } - - if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) { - page.querySelector('.vppTonemappingOptions').classList.remove('hide'); - } else { - page.querySelector('.vppTonemappingOptions').classList.add('hide'); - } - - if (this.value == 'qsv') { - page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide'); - } else { - page.querySelector('.fldSysNativeHwDecoder').classList.add('hide'); - } - - if (this.value == 'nvenc') { - page.querySelector('.fldEnhancedNvdec').classList.remove('hide'); - } else { - page.querySelector('.fldEnhancedNvdec').classList.add('hide'); - } - - if (this.value) { - page.querySelector('.hardwareAccelerationOptions').classList.remove('hide'); - } else { - page.querySelector('.hardwareAccelerationOptions').classList.add('hide'); - } - - setDecodingCodecsVisible(page, this.value); - }); - $('#btnSelectEncoderPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeFiles: true, - callback: function (path) { - if (path) { - $('.txtEncoderPath', page).val(path); - } - - picker.close(); - } - }); - }); - }); - $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - callback: function (path) { - if (path) { - $('#txtTranscodingTempPath', page).val(path); - } - - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectTranscodingPath'), - instruction: globalize.translate('HeaderSelectTranscodingPathHelp') - }); - }); - }); - $('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeDirectories: true, - callback: function (path) { - if (path) { - page.querySelector('#txtFallbackFontPath').value = path; - } - - picker.close(); - }, - header: globalize.translate('HeaderSelectFallbackFontPath'), - instruction: globalize.translate('HeaderSelectFallbackFontPathHelp') - }); - }); - }); - $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#encodingSettingsPage', function () { + const onDecoderConfirmed = function () { loading.show(); - libraryMenu.setTabs('playback', 0, getTabs); - const page = this; ApiClient.getNamedConfiguration('encoding').then(function (config) { - ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { - loadPage(page, config, fetchedSystemInfo); + config.EnableAudioVbr = form.querySelector('#chkEnableAudioVbr').checked; + config.DownMixAudioBoost = $('#txtDownMixAudioBoost', form).val(); + config.DownMixStereoAlgorithm = $('#selectStereoDownmixAlgorithm', form).val() || 'None'; + config.MaxMuxingQueueSize = form.querySelector('#txtMaxMuxingQueueSize').value; + config.TranscodingTempPath = $('#txtTranscodingTempPath', form).val(); + config.FallbackFontPath = form.querySelector('#txtFallbackFontPath').value; + config.EnableFallbackFont = form.querySelector('#txtFallbackFontPath').value ? form.querySelector('#chkEnableFallbackFont').checked : false; + config.EncodingThreadCount = $('#selectThreadCount', form).val(); + config.HardwareAccelerationType = $('#selectVideoDecoder', form).val(); + config.VaapiDevice = $('#txtVaapiDevice', form).val(); + config.EnableTonemapping = form.querySelector('#chkTonemapping').checked; + config.EnableVppTonemapping = form.querySelector('#chkVppTonemapping').checked; + config.TonemappingAlgorithm = form.querySelector('#selectTonemappingAlgorithm').value; + config.TonemappingRange = form.querySelector('#selectTonemappingRange').value; + config.TonemappingDesat = form.querySelector('#txtTonemappingDesat').value; + config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value; + config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value; + config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0'; + config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; + config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; + config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; + config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10); + config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10); + config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; + config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; + config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; + config.EnableThrottling = form.querySelector('#chkEnableThrottling').checked; + config.HardwareDecodingCodecs = Array.prototype.map.call(Array.prototype.filter.call(form.querySelectorAll('.chkDecodeCodec'), function (c) { + return c.checked; + }), function (c) { + return c.getAttribute('data-codec'); + }); + config.EnableDecodingColorDepth10Hevc = form.querySelector('#chkDecodingColorDepth10Hevc').checked; + config.EnableDecodingColorDepth10Vp9 = form.querySelector('#chkDecodingColorDepth10Vp9').checked; + config.EnableEnhancedNvdecDecoder = form.querySelector('#chkEnhancedNvdecDecoder').checked; + config.PreferSystemNativeHwDecoder = form.querySelector('#chkSystemNativeHwDecoder').checked; + config.EnableIntelLowPowerH264HwEncoder = form.querySelector('#chkIntelLpH264HwEncoder').checked; + config.EnableIntelLowPowerHevcHwEncoder = form.querySelector('#chkIntelLpHevcHwEncoder').checked; + config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; + config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; + ApiClient.updateNamedConfiguration('encoding', config).then(function () { + updateEncoder(form); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); + }); + }); + }; + + if ($('#selectVideoDecoder', form).val()) { + alert({ + title: globalize.translate('TitleHardwareAcceleration'), + text: globalize.translate('HardwareAccelerationWarning') + }).then(onDecoderConfirmed); + } else { + onDecoderConfirmed(); + } + + return false; +} + +function setDecodingCodecsVisible(context, value) { + value = value || ''; + let any; + Array.prototype.forEach.call(context.querySelectorAll('.chkDecodeCodec'), function (c) { + if (c.getAttribute('data-types').split(',').indexOf(value) === -1) { + dom.parentWithTag(c, 'LABEL').classList.add('hide'); + } else { + dom.parentWithTag(c, 'LABEL').classList.remove('hide'); + any = true; + } + }); + + if (any) { + context.querySelector('.decodingCodecsList').classList.remove('hide'); + } else { + context.querySelector('.decodingCodecsList').classList.add('hide'); + } +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +let systemInfo; +function getSystemInfo() { + return systemInfo ? Promise.resolve(systemInfo) : ApiClient.getPublicSystemInfo().then( + info => { + systemInfo = info; + return info; + } + ); +} + +$(document).on('pageinit', '#encodingSettingsPage', function () { + const page = this; + getSystemInfo(); + page.querySelector('#selectVideoDecoder').addEventListener('change', function () { + if (this.value == 'vaapi') { + page.querySelector('.fldVaapiDevice').classList.remove('hide'); + page.querySelector('#txtVaapiDevice').setAttribute('required', 'required'); + } else { + page.querySelector('.fldVaapiDevice').classList.add('hide'); + page.querySelector('#txtVaapiDevice').removeAttribute('required'); + } + + if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi' || this.value == 'videotoolbox') { + page.querySelector('.fld10bitHevcVp9HwDecoding').classList.remove('hide'); + } else { + page.querySelector('.fld10bitHevcVp9HwDecoding').classList.add('hide'); + } + + if (this.value == 'amf' || this.value == 'nvenc' || this.value == 'qsv' || this.value == 'vaapi') { + page.querySelector('.tonemappingOptions').classList.remove('hide'); + } else { + page.querySelector('.tonemappingOptions').classList.add('hide'); + } + + if (this.value == 'qsv' || this.value == 'vaapi') { + page.querySelector('.fldIntelLp').classList.remove('hide'); + } else { + page.querySelector('.fldIntelLp').classList.add('hide'); + } + + if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) { + page.querySelector('.vppTonemappingOptions').classList.remove('hide'); + } else { + page.querySelector('.vppTonemappingOptions').classList.add('hide'); + } + + if (this.value == 'qsv') { + page.querySelector('.fldSysNativeHwDecoder').classList.remove('hide'); + } else { + page.querySelector('.fldSysNativeHwDecoder').classList.add('hide'); + } + + if (this.value == 'nvenc') { + page.querySelector('.fldEnhancedNvdec').classList.remove('hide'); + } else { + page.querySelector('.fldEnhancedNvdec').classList.add('hide'); + } + + if (this.value) { + page.querySelector('.hardwareAccelerationOptions').classList.remove('hide'); + } else { + page.querySelector('.hardwareAccelerationOptions').classList.add('hide'); + } + + setDecodingCodecsVisible(page, this.value); + }); + $('#btnSelectEncoderPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeFiles: true, + callback: function (path) { + if (path) { + $('.txtEncoderPath', page).val(path); + } + + picker.close(); + } }); }); }); + $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + callback: function (path) { + if (path) { + $('#txtTranscodingTempPath', page).val(path); + } + + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectTranscodingPath'), + instruction: globalize.translate('HeaderSelectTranscodingPathHelp') + }); + }); + }); + $('#btnSelectFallbackFontPath', page).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeDirectories: true, + callback: function (path) { + if (path) { + page.querySelector('#txtFallbackFontPath').value = path; + } + + picker.close(); + }, + header: globalize.translate('HeaderSelectFallbackFontPath'), + instruction: globalize.translate('HeaderSelectFallbackFontPathHelp') + }); + }); + }); + $('.encodingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#encodingSettingsPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 0, getTabs); + const page = this; + ApiClient.getNamedConfiguration('encoding').then(function (config) { + ApiClient.getSystemInfo().then(function (fetchedSystemInfo) { + loadPage(page, config, fetchedSystemInfo); + }); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/general.js b/src/controllers/dashboard/general.js index ed7268748f..098d2244bd 100644 --- a/src/controllers/dashboard/general.js +++ b/src/controllers/dashboard/general.js @@ -10,110 +10,107 @@ import '../../elements/emby-button/emby-button'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, languageOptions, systemInfo) { + page.querySelector('#txtServerName').value = systemInfo.ServerName; + page.querySelector('#txtCachePath').value = systemInfo.CachePath || ''; + page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true; + $('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || ''); + $('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || ''); + $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { + return ''; + })).val(config.UICulture); + page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || ''; - function loadPage(page, config, languageOptions, systemInfo) { - page.querySelector('#txtServerName').value = systemInfo.ServerName; - page.querySelector('#txtCachePath').value = systemInfo.CachePath || ''; - page.querySelector('#chkQuickConnectAvailable').checked = config.QuickConnectAvailable === true; - $('#txtMetadataPath', page).val(systemInfo.InternalMetadataPath || ''); - $('#txtMetadataNetworkPath', page).val(systemInfo.MetadataNetworkPath || ''); - $('#selectLocalizationLanguage', page).html(languageOptions.map(function (language) { - return ''; - })).val(config.UICulture); - page.querySelector('#txtParallelImageEncodingLimit').value = config.ParallelImageEncodingLimit || ''; + loading.hide(); +} - loading.hide(); - } +function onSubmit() { + loading.show(); + const form = this; + $(form).parents('.page'); + ApiClient.getServerConfiguration().then(function (config) { + config.ServerName = $('#txtServerName', form).val(); + config.UICulture = $('#selectLocalizationLanguage', form).val(); + config.CachePath = form.querySelector('#txtCachePath').value; + config.MetadataPath = $('#txtMetadataPath', form).val(); + config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val(); + config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked; + config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10); - function onSubmit() { - loading.show(); - const form = this; - $(form).parents('.page'); - ApiClient.getServerConfiguration().then(function (config) { - config.ServerName = $('#txtServerName', form).val(); - config.UICulture = $('#selectLocalizationLanguage', form).val(); - config.CachePath = form.querySelector('#txtCachePath').value; - config.MetadataPath = $('#txtMetadataPath', form).val(); - config.MetadataNetworkPath = $('#txtMetadataNetworkPath', form).val(); - config.QuickConnectAvailable = form.querySelector('#chkQuickConnectAvailable').checked; - config.ParallelImageEncodingLimit = parseInt(form.querySelector('#txtParallelImageEncodingLimit').value || '0', 10); + ApiClient.updateServerConfiguration(config).then(function() { + ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { + brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value; + brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value; + brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked; - ApiClient.updateServerConfiguration(config).then(function() { - ApiClient.getNamedConfiguration(brandingConfigKey).then(function(brandingConfig) { - brandingConfig.LoginDisclaimer = form.querySelector('#txtLoginDisclaimer').value; - brandingConfig.CustomCss = form.querySelector('#txtCustomCss').value; - brandingConfig.SplashscreenEnabled = form.querySelector('#chkSplashScreenAvailable').checked; - - ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); - }); - return false; - } - - const brandingConfigKey = 'branding'; - export default function (view) { - $('#btnSelectCachePath', view).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - callback: function (path) { - if (path) { - view.querySelector('#txtCachePath').value = path; - } - - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectServerCachePath'), - instruction: globalize.translate('HeaderSelectServerCachePathHelp') + ApiClient.updateNamedConfiguration(brandingConfigKey, brandingConfig).then(function () { + Dashboard.processServerConfigurationUpdateResult(); }); }); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); }); - $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - path: $('#txtMetadataPath', view).val(), - networkSharePath: $('#txtMetadataNetworkPath', view).val(), - callback: function (path, networkPath) { - if (path) { - $('#txtMetadataPath', view).val(path); - } + }); + return false; +} - if (networkPath) { - $('#txtMetadataNetworkPath', view).val(networkPath); - } +const brandingConfigKey = 'branding'; +export default function (view) { + $('#btnSelectCachePath', view).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + callback: function (path) { + if (path) { + view.querySelector('#txtCachePath').value = path; + } - picker.close(); - }, - validateWriteable: true, - header: globalize.translate('HeaderSelectMetadataPath'), - instruction: globalize.translate('HeaderSelectMetadataPathHelp'), - enableNetworkSharePath: true - }); + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectServerCachePath'), + instruction: globalize.translate('HeaderSelectServerCachePathHelp') }); }); - $('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); - view.addEventListener('viewshow', function () { - const promiseConfig = ApiClient.getServerConfiguration(); - const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); - const promiseSystemInfo = ApiClient.getSystemInfo(); - Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) { - loadPage(view, responses[0], responses[1], responses[2]); - }); - ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) { - view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; - view.querySelector('#txtCustomCss').value = config.CustomCss || ''; - view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true; + }); + $('#btnSelectMetadataPath', view).on('click.selectDirectory', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + path: $('#txtMetadataPath', view).val(), + networkSharePath: $('#txtMetadataNetworkPath', view).val(), + callback: function (path, networkPath) { + if (path) { + $('#txtMetadataPath', view).val(path); + } + + if (networkPath) { + $('#txtMetadataNetworkPath', view).val(networkPath); + } + + picker.close(); + }, + validateWriteable: true, + header: globalize.translate('HeaderSelectMetadataPath'), + instruction: globalize.translate('HeaderSelectMetadataPathHelp'), + enableNetworkSharePath: true }); }); - } + }); + $('.dashboardGeneralForm', view).off('submit', onSubmit).on('submit', onSubmit); + view.addEventListener('viewshow', function () { + const promiseConfig = ApiClient.getServerConfiguration(); + const promiseLanguageOptions = ApiClient.getJSON(ApiClient.getUrl('Localization/Options')); + const promiseSystemInfo = ApiClient.getSystemInfo(); + Promise.all([promiseConfig, promiseLanguageOptions, promiseSystemInfo]).then(function (responses) { + loadPage(view, responses[0], responses[1], responses[2]); + }); + ApiClient.getNamedConfiguration(brandingConfigKey).then(function (config) { + view.querySelector('#txtLoginDisclaimer').value = config.LoginDisclaimer || ''; + view.querySelector('#txtCustomCss').value = config.CustomCss || ''; + view.querySelector('#chkSplashScreenAvailable').checked = config.SplashscreenEnabled === true; + }); + }); +} -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 62cb3edf42..f9d80fa3fc 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -12,399 +12,396 @@ import Dashboard, { pageClassOn, pageIdOn } from '../../utils/dashboard'; import confirm from '../../components/confirm/confirm'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; -/* eslint-disable indent */ - - function addVirtualFolder(page) { - import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => { - new medialibrarycreator({ - collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { - return !f.hidden; - }), - refresh: shouldRefreshLibraryAfterChanges(page) - }).then(function (hasChanges) { - if (hasChanges) { - reloadLibrary(page); - } - }); - }); - } - - function editVirtualFolder(page, virtualFolder) { - import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => { - new medialibraryeditor({ - refresh: shouldRefreshLibraryAfterChanges(page), - library: virtualFolder - }).then(function (hasChanges) { - if (hasChanges) { - reloadLibrary(page); - } - }); - }); - } - - function deleteVirtualFolder(page, virtualFolder) { - let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder'); - - if (virtualFolder.Locations.length) { - msg += '

' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '

'; - msg += virtualFolder.Locations.join('
'); - } - - confirm({ - text: msg, - title: globalize.translate('HeaderRemoveMediaFolder'), - confirmText: globalize.translate('Delete'), - primary: 'delete' - }).then(function () { - const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); - ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () { +function addVirtualFolder(page) { + import('../../components/mediaLibraryCreator/mediaLibraryCreator').then(({ default: medialibrarycreator }) => { + new medialibrarycreator({ + collectionTypeOptions: getCollectionTypeOptions().filter(function (f) { + return !f.hidden; + }), + refresh: shouldRefreshLibraryAfterChanges(page) + }).then(function (hasChanges) { + if (hasChanges) { reloadLibrary(page); - }); + } }); + }); +} + +function editVirtualFolder(page, virtualFolder) { + import('../../components/mediaLibraryEditor/mediaLibraryEditor').then(({ default: medialibraryeditor }) => { + new medialibraryeditor({ + refresh: shouldRefreshLibraryAfterChanges(page), + library: virtualFolder + }).then(function (hasChanges) { + if (hasChanges) { + reloadLibrary(page); + } + }); + }); +} + +function deleteVirtualFolder(page, virtualFolder) { + let msg = globalize.translate('MessageAreYouSureYouWishToRemoveMediaFolder'); + + if (virtualFolder.Locations.length) { + msg += '

' + globalize.translate('MessageTheFollowingLocationWillBeRemovedFromLibrary') + '

'; + msg += virtualFolder.Locations.join('
'); } - function refreshVirtualFolder(page, virtualFolder) { - import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { - new refreshDialog({ - itemIds: [virtualFolder.ItemId], - serverId: ApiClient.serverId(), - mode: 'scan' - }).show(); + confirm({ + text: msg, + title: globalize.translate('HeaderRemoveMediaFolder'), + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(function () { + const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); + ApiClient.removeVirtualFolder(virtualFolder.Name, refreshAfterChange).then(function () { + reloadLibrary(page); }); - } + }); +} - function renameVirtualFolder(page, virtualFolder) { - import('../../components/prompt/prompt').then(({ default: prompt }) => { - prompt({ - label: globalize.translate('LabelNewName'), - description: globalize.translate('MessageRenameMediaFolder'), - confirmText: globalize.translate('ButtonRename') - }).then(function (newName) { - if (newName && newName != virtualFolder.Name) { - const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); - ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () { - reloadLibrary(page); - }); +function refreshVirtualFolder(page, virtualFolder) { + import('../../components/refreshdialog/refreshdialog').then(({ default: refreshDialog }) => { + new refreshDialog({ + itemIds: [virtualFolder.ItemId], + serverId: ApiClient.serverId(), + mode: 'scan' + }).show(); + }); +} + +function renameVirtualFolder(page, virtualFolder) { + import('../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + label: globalize.translate('LabelNewName'), + description: globalize.translate('MessageRenameMediaFolder'), + confirmText: globalize.translate('ButtonRename') + }).then(function (newName) { + if (newName && newName != virtualFolder.Name) { + const refreshAfterChange = shouldRefreshLibraryAfterChanges(page); + ApiClient.renameVirtualFolder(virtualFolder.Name, newName, refreshAfterChange).then(function () { + reloadLibrary(page); + }); + } + }); + }); +} + +function showCardMenu(page, elem, virtualFolders) { + const card = dom.parentWithClass(elem, 'card'); + const index = parseInt(card.getAttribute('data-index'), 10); + const virtualFolder = virtualFolders[index]; + const menuItems = []; + menuItems.push({ + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'photo' + }); + menuItems.push({ + name: globalize.translate('ManageLibrary'), + id: 'edit', + icon: 'folder' + }); + menuItems.push({ + name: globalize.translate('ButtonRename'), + id: 'rename', + icon: 'mode_edit' + }); + menuItems.push({ + name: globalize.translate('ScanLibrary'), + id: 'refresh', + icon: 'refresh' + }); + menuItems.push({ + name: globalize.translate('ButtonRemove'), + id: 'delete', + icon: 'delete' + }); + + import('../../components/actionSheet/actionSheet').then((actionsheet) => { + actionsheet.show({ + items: menuItems, + positionTo: elem, + callback: function (resultId) { + switch (resultId) { + case 'edit': + editVirtualFolder(page, virtualFolder); + break; + + case 'editimages': + editImages(page, virtualFolder); + break; + + case 'rename': + renameVirtualFolder(page, virtualFolder); + break; + + case 'delete': + deleteVirtualFolder(page, virtualFolder); + break; + + case 'refresh': + refreshVirtualFolder(page, virtualFolder); } - }); + } }); + }); +} + +function reloadLibrary(page) { + loading.show(); + ApiClient.getVirtualFolders().then(function (result) { + reloadVirtualFolders(page, result); + }); +} + +function shouldRefreshLibraryAfterChanges(page) { + return page.id === 'mediaLibraryPage'; +} + +function reloadVirtualFolders(page, virtualFolders) { + let html = ''; + virtualFolders.push({ + Name: globalize.translate('ButtonAddMediaLibrary'), + icon: 'add_circle', + Locations: [], + showType: false, + showLocations: false, + showMenu: false, + showNameWithIcon: false, + elementId: 'addLibrary' + }); + + for (let i = 0; i < virtualFolders.length; i++) { + const virtualFolder = virtualFolders[i]; + html += getVirtualFolderHtml(page, virtualFolder, i); } - function showCardMenu(page, elem, virtualFolders) { - const card = dom.parentWithClass(elem, 'card'); + const divVirtualFolders = page.querySelector('#divVirtualFolders'); + divVirtualFolders.innerHTML = html; + divVirtualFolders.classList.add('itemsContainer'); + divVirtualFolders.classList.add('vertical-wrap'); + $('.btnCardMenu', divVirtualFolders).on('click', function () { + showCardMenu(page, this, virtualFolders); + }); + divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { + addVirtualFolder(page); + }); + $('.editLibrary', divVirtualFolders).on('click', function () { + const card = $(this).parents('.card')[0]; const index = parseInt(card.getAttribute('data-index'), 10); const virtualFolder = virtualFolders[index]; - const menuItems = []; - menuItems.push({ - name: globalize.translate('EditImages'), - id: 'editimages', - icon: 'photo' - }); - menuItems.push({ - name: globalize.translate('ManageLibrary'), - id: 'edit', - icon: 'folder' - }); - menuItems.push({ - name: globalize.translate('ButtonRename'), - id: 'rename', - icon: 'mode_edit' - }); - menuItems.push({ - name: globalize.translate('ScanLibrary'), - id: 'refresh', - icon: 'refresh' - }); - menuItems.push({ - name: globalize.translate('ButtonRemove'), - id: 'delete', - icon: 'delete' - }); - import('../../components/actionSheet/actionSheet').then((actionsheet) => { - actionsheet.show({ - items: menuItems, - positionTo: elem, - callback: function (resultId) { - switch (resultId) { - case 'edit': - editVirtualFolder(page, virtualFolder); - break; - - case 'editimages': - editImages(page, virtualFolder); - break; - - case 'rename': - renameVirtualFolder(page, virtualFolder); - break; - - case 'delete': - deleteVirtualFolder(page, virtualFolder); - break; - - case 'refresh': - refreshVirtualFolder(page, virtualFolder); - } - } - }); - }); - } - - function reloadLibrary(page) { - loading.show(); - ApiClient.getVirtualFolders().then(function (result) { - reloadVirtualFolders(page, result); - }); - } - - function shouldRefreshLibraryAfterChanges(page) { - return page.id === 'mediaLibraryPage'; - } - - function reloadVirtualFolders(page, virtualFolders) { - let html = ''; - virtualFolders.push({ - Name: globalize.translate('ButtonAddMediaLibrary'), - icon: 'add_circle', - Locations: [], - showType: false, - showLocations: false, - showMenu: false, - showNameWithIcon: false, - elementId: 'addLibrary' - }); - - for (let i = 0; i < virtualFolders.length; i++) { - const virtualFolder = virtualFolders[i]; - html += getVirtualFolderHtml(page, virtualFolder, i); + if (virtualFolder.ItemId) { + editVirtualFolder(page, virtualFolder); } + }); + loading.hide(); +} - const divVirtualFolders = page.querySelector('#divVirtualFolders'); - divVirtualFolders.innerHTML = html; - divVirtualFolders.classList.add('itemsContainer'); - divVirtualFolders.classList.add('vertical-wrap'); - $('.btnCardMenu', divVirtualFolders).on('click', function () { - showCardMenu(page, this, virtualFolders); +function editImages(page, virtualFolder) { + import('../../components/imageeditor/imageeditor').then((imageEditor) => { + imageEditor.show({ + itemId: virtualFolder.ItemId, + serverId: ApiClient.serverId() + }).then(function () { + reloadLibrary(page); }); - divVirtualFolders.querySelector('#addLibrary').addEventListener('click', function () { - addVirtualFolder(page); - }); - $('.editLibrary', divVirtualFolders).on('click', function () { - const card = $(this).parents('.card')[0]; - const index = parseInt(card.getAttribute('data-index'), 10); - const virtualFolder = virtualFolders[index]; + }); +} - if (virtualFolder.ItemId) { - editVirtualFolder(page, virtualFolder); - } - }); - loading.hide(); +function getLink(text, url) { + return globalize.translate(text, '', ''); +} + +function getCollectionTypeOptions() { + return [{ + name: '', + value: '' + }, { + name: globalize.translate('Movies'), + value: 'movies', + message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies') + }, { + name: globalize.translate('TabMusic'), + value: 'music', + message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music') + }, { + name: globalize.translate('Shows'), + value: 'tvshows', + message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows') + }, { + name: globalize.translate('Books'), + value: 'books', + message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books') + }, { + name: globalize.translate('HomeVideosPhotos'), + value: 'homevideos' + }, { + name: globalize.translate('MusicVideos'), + value: 'musicvideos' + }, { + name: globalize.translate('MixedMoviesShows'), + value: 'mixed', + message: globalize.translate('MessageUnsetContentHelp') + }]; +} + +function getVirtualFolderHtml(page, virtualFolder, index) { + let html = ''; + let style = ''; + + if (page.classList.contains('wizardPage')) { + style += 'min-width:33.3%;'; } - function editImages(page, virtualFolder) { - import('../../components/imageeditor/imageeditor').then((imageEditor) => { - imageEditor.show({ - itemId: virtualFolder.ItemId, - serverId: ApiClient.serverId() - }).then(function () { - reloadLibrary(page); - }); + const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : ''; + html += '
'; + + html += '
'; + html += '
'; + html += '
'; + html += '
'; + let imgUrl = ''; + + if (virtualFolder.PrimaryImageItemId) { + imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, { + maxWidth: Math.round(dom.getScreenWidth() * 0.40), + type: 'Primary' }); } - function getLink(text, url) { - return globalize.translate(text, '', ''); + let hasCardImageContainer; + + if (imgUrl) { + html += `
`; + html += ``; + hasCardImageContainer = true; + } else if (!virtualFolder.showNameWithIcon) { + html += `
`; + html += ''; + hasCardImageContainer = true; } - function getCollectionTypeOptions() { - return [{ - name: '', - value: '' - }, { - name: globalize.translate('Movies'), - value: 'movies', - message: getLink('MovieLibraryHelp', 'https://jellyfin.org/docs/general/server/media/movies') - }, { - name: globalize.translate('TabMusic'), - value: 'music', - message: getLink('MusicLibraryHelp', 'https://jellyfin.org/docs/general/server/media/music') - }, { - name: globalize.translate('Shows'), - value: 'tvshows', - message: getLink('TvLibraryHelp', 'https://jellyfin.org/docs/general/server/media/shows') - }, { - name: globalize.translate('Books'), - value: 'books', - message: getLink('BookLibraryHelp', 'https://jellyfin.org/docs/general/server/media/books') - }, { - name: globalize.translate('HomeVideosPhotos'), - value: 'homevideos' - }, { - name: globalize.translate('MusicVideos'), - value: 'musicvideos' - }, { - name: globalize.translate('MixedMoviesShows'), - value: 'mixed', - message: globalize.translate('MessageUnsetContentHelp') - }]; - } - - function getVirtualFolderHtml(page, virtualFolder, index) { - let html = ''; - let style = ''; - - if (page.classList.contains('wizardPage')) { - style += 'min-width:33.3%;'; - } - - const elementId = virtualFolder.elementId ? `id="${virtualFolder.elementId}" ` : ''; - html += '
'; - - html += '
'; - html += '
'; - html += '
'; - html += '
'; - let imgUrl = ''; - - if (virtualFolder.PrimaryImageItemId) { - imgUrl = ApiClient.getScaledImageUrl(virtualFolder.PrimaryImageItemId, { - maxWidth: Math.round(dom.getScreenWidth() * 0.40), - type: 'Primary' - }); - } - - let hasCardImageContainer; - - if (imgUrl) { - html += `
`; - html += ``; - hasCardImageContainer = true; - } else if (!virtualFolder.showNameWithIcon) { - html += `
`; - html += ''; - hasCardImageContainer = true; - } - - if (hasCardImageContainer) { - html += '
'; - html += '
'; - html += '
'; - html += '
'; - } - - if (!imgUrl && virtualFolder.showNameWithIcon) { - html += '

'; - html += ''; - - if (virtualFolder.showNameWithIcon) { - html += '
'; - html += escapeHtml(virtualFolder.Name); - html += '
'; - } - - html += '

'; - } - + if (hasCardImageContainer) { + html += '
'; + html += '
'; html += '
'; html += '
'; - html += '
'; // always show menu unless explicitly hidden + } - if (virtualFolder.showMenu !== false) { - let dirTextAlign = 'right'; - if (globalize.getIsRTL()) - dirTextAlign = 'left'; - html += '
'; - html += ''; - html += '
'; - } - - html += "
"; + if (!imgUrl && virtualFolder.showNameWithIcon) { + html += '

'; + html += ''; if (virtualFolder.showNameWithIcon) { - html += ' '; - } else { + html += '
'; html += escapeHtml(virtualFolder.Name); + html += '
'; } + html += '

'; + } + + html += '
'; + html += '
'; + html += '
'; // always show menu unless explicitly hidden + + if (virtualFolder.showMenu !== false) { + let dirTextAlign = 'right'; + if (globalize.getIsRTL()) + dirTextAlign = 'left'; + html += '
'; + html += ''; html += '
'; - let typeName = getCollectionTypeOptions().filter(function (t) { - return t.value == virtualFolder.CollectionType; - })[0]; - typeName = typeName ? typeName.name : globalize.translate('Other'); + } + + html += "
"; + + if (virtualFolder.showNameWithIcon) { + html += ' '; + } else { + html += escapeHtml(virtualFolder.Name); + } + + html += '
'; + let typeName = getCollectionTypeOptions().filter(function (t) { + return t.value == virtualFolder.CollectionType; + })[0]; + typeName = typeName ? typeName.name : globalize.translate('Other'); + html += "
"; + + if (virtualFolder.showType === false) { + html += ' '; + } else { + html += typeName; + } + + html += '
'; + + if (virtualFolder.showLocations === false) { html += "
"; - - if (virtualFolder.showType === false) { - html += ' '; - } else { - html += typeName; - } - + html += ' '; html += '
'; - - if (virtualFolder.showLocations === false) { - html += "
"; - html += ' '; - html += '
'; - } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { - html += "
"; - html += virtualFolder.Locations[0]; - html += '
'; - } else { - html += "
"; - html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length); - html += '
'; - } - + } else if (virtualFolder.Locations.length && virtualFolder.Locations.length === 1) { + html += "
"; + html += virtualFolder.Locations[0]; html += '
'; + } else { + html += "
"; + html += globalize.translate('NumLocationsValue', virtualFolder.Locations.length); html += '
'; - html += '
'; - return html; } - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; + html += '
'; + html += '
'; + html += '
'; + return html; +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +window.WizardLibraryPage = { + next: function () { + Dashboard.navigate('wizardsettings.html'); } +}; +pageClassOn('pageshow', 'mediaLibraryPage', function () { + reloadLibrary(this); +}); +pageIdOn('pageshow', 'mediaLibraryPage', function () { + libraryMenu.setTabs('librarysetup', 0, getTabs); - window.WizardLibraryPage = { - next: function () { - Dashboard.navigate('wizardsettings.html'); - } - }; - pageClassOn('pageshow', 'mediaLibraryPage', function () { - reloadLibrary(this); + const page = this; + taskButton({ + mode: 'on', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') }); - pageIdOn('pageshow', 'mediaLibraryPage', function () { - libraryMenu.setTabs('librarysetup', 0, getTabs); +}); +pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { + const page = this; + taskButton({ + mode: 'off', + progressElem: page.querySelector('.refreshProgress'), + taskKey: 'RefreshLibrary', + button: page.querySelector('.btnRefresh') + }); +}); - const page = this; - taskButton({ - mode: 'on', - progressElem: page.querySelector('.refreshProgress'), - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - pageIdOn('pagebeforehide', 'mediaLibraryPage', function () { - const page = this; - taskButton({ - mode: 'off', - progressElem: page.querySelector('.refreshProgress'), - taskKey: 'RefreshLibrary', - button: page.querySelector('.btnRefresh') - }); - }); - -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/librarydisplay.js b/src/controllers/dashboard/librarydisplay.js index 4691158386..b418984fb7 100644 --- a/src/controllers/dashboard/librarydisplay.js +++ b/src/controllers/dashboard/librarydisplay.js @@ -5,70 +5,67 @@ import '../../elements/emby-checkbox/emby-checkbox'; import '../../elements/emby-button/emby-button'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - export default function(view) { - function loadData() { - ApiClient.getServerConfiguration().then(function(config) { - view.querySelector('.chkFolderView').checked = config.EnableFolderView; - view.querySelector('.chkGroupMoviesIntoCollections').checked = config.EnableGroupingIntoCollections; - view.querySelector('.chkDisplaySpecialsWithinSeasons').checked = config.DisplaySpecialsWithinSeasons; - view.querySelector('.chkExternalContentInSuggestions').checked = config.EnableExternalContentInSuggestions; - view.querySelector('#chkSaveMetadataHidden').checked = config.SaveMetadataHidden; - }); - ApiClient.getNamedConfiguration('metadata').then(function(metadata) { - view.querySelector('#selectDateAdded').selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; - }); - } - - view.querySelector('form').addEventListener('submit', function(e) { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function(config) { - config.EnableFolderView = form.querySelector('.chkFolderView').checked; - config.EnableGroupingIntoCollections = form.querySelector('.chkGroupMoviesIntoCollections').checked; - config.DisplaySpecialsWithinSeasons = form.querySelector('.chkDisplaySpecialsWithinSeasons').checked; - config.EnableExternalContentInSuggestions = form.querySelector('.chkExternalContentInSuggestions').checked; - config.SaveMetadataHidden = form.querySelector('#chkSaveMetadataHidden').checked; - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - ApiClient.getNamedConfiguration('metadata').then(function(config) { - config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1'; - ApiClient.updateNamedConfiguration('metadata', config); - }); - - e.preventDefault(); - loading.hide(); - return false; +export default function(view) { + function loadData() { + ApiClient.getServerConfiguration().then(function(config) { + view.querySelector('.chkFolderView').checked = config.EnableFolderView; + view.querySelector('.chkGroupMoviesIntoCollections').checked = config.EnableGroupingIntoCollections; + view.querySelector('.chkDisplaySpecialsWithinSeasons').checked = config.DisplaySpecialsWithinSeasons; + view.querySelector('.chkExternalContentInSuggestions').checked = config.EnableExternalContentInSuggestions; + view.querySelector('#chkSaveMetadataHidden').checked = config.SaveMetadataHidden; }); - - view.addEventListener('viewshow', function() { - libraryMenu.setTabs('librarysetup', 1, getTabs); - loadData(); - ApiClient.getSystemInfo().then(function(info) { - if (info.OperatingSystem === 'Windows') { - view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); - } else { - view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); - } - }); + ApiClient.getNamedConfiguration('metadata').then(function(metadata) { + view.querySelector('#selectDateAdded').selectedIndex = metadata.UseFileCreationTimeForDateAdded ? 1 : 0; }); } -/* eslint-enable indent */ + view.querySelector('form').addEventListener('submit', function(e) { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function(config) { + config.EnableFolderView = form.querySelector('.chkFolderView').checked; + config.EnableGroupingIntoCollections = form.querySelector('.chkGroupMoviesIntoCollections').checked; + config.DisplaySpecialsWithinSeasons = form.querySelector('.chkDisplaySpecialsWithinSeasons').checked; + config.EnableExternalContentInSuggestions = form.querySelector('.chkExternalContentInSuggestions').checked; + config.SaveMetadataHidden = form.querySelector('#chkSaveMetadataHidden').checked; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + ApiClient.getNamedConfiguration('metadata').then(function(config) { + config.UseFileCreationTimeForDateAdded = $('#selectDateAdded', form).val() === '1'; + ApiClient.updateNamedConfiguration('metadata', config); + }); + + e.preventDefault(); + loading.hide(); + return false; + }); + + view.addEventListener('viewshow', function() { + libraryMenu.setTabs('librarysetup', 1, getTabs); + loadData(); + ApiClient.getSystemInfo().then(function(info) { + if (info.OperatingSystem === 'Windows') { + view.querySelector('.fldSaveMetadataHidden').classList.remove('hide'); + } else { + view.querySelector('.fldSaveMetadataHidden').classList.add('hide'); + } + }); + }); +} + diff --git a/src/controllers/dashboard/logs.js b/src/controllers/dashboard/logs.js index 23a6d43853..86dcfccd7e 100644 --- a/src/controllers/dashboard/logs.js +++ b/src/controllers/dashboard/logs.js @@ -7,61 +7,57 @@ import '../../styles/flexstyles.scss'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function onSubmit(event) { + event.preventDefault(); + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.EnableSlowResponseWarning = form.querySelector('#chkSlowResponseWarning').checked; + config.SlowResponseThresholdMs = form.querySelector('#txtSlowResponseWarning').value; + ApiClient.updateServerConfiguration(config).then(function() { + Dashboard.processServerConfigurationUpdateResult(); + }, function () { + alert(globalize.translate('ErrorDefault')); + Dashboard.processServerConfigurationUpdateResult(); + }); + }); + return false; +} - function onSubmit(event) { - event.preventDefault(); +export default function(view) { + view.querySelector('.logsForm').addEventListener('submit', onSubmit); + view.addEventListener('viewbeforeshow', function() { loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.EnableSlowResponseWarning = form.querySelector('#chkSlowResponseWarning').checked; - config.SlowResponseThresholdMs = form.querySelector('#txtSlowResponseWarning').value; - ApiClient.updateServerConfiguration(config).then(function() { - Dashboard.processServerConfigurationUpdateResult(); - }, function () { - alert(globalize.translate('ErrorDefault')); - Dashboard.processServerConfigurationUpdateResult(); - }); + const apiClient = ApiClient; + apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { + let html = ''; + html += '
'; + html += logs.map(function(log) { + let logUrl = apiClient.getUrl('System/Logs/Log', { + name: log.Name + }); + logUrl += '&api_key=' + apiClient.accessToken(); + let logHtml = ''; + logHtml += ''; + logHtml += '
'; + logHtml += "

" + log.Name + '

'; + const date = datetime.parseISO8601Date(log.DateModified, true); + let text = datetime.toLocaleDateString(date); + text += ' ' + datetime.getDisplayTime(date); + logHtml += '
' + text + '
'; + logHtml += '
'; + logHtml += '
'; + return logHtml; + }).join(''); + html += '
'; + view.querySelector('.serverLogs').innerHTML = html; }); - return false; - } - export default function(view) { - view.querySelector('.logsForm').addEventListener('submit', onSubmit); - view.addEventListener('viewbeforeshow', function() { - loading.show(); - const apiClient = ApiClient; - apiClient.getJSON(apiClient.getUrl('System/Logs')).then(function(logs) { - let html = ''; - html += '
'; - html += logs.map(function(log) { - let logUrl = apiClient.getUrl('System/Logs/Log', { - name: log.Name - }); - logUrl += '&api_key=' + apiClient.accessToken(); - let logHtml = ''; - logHtml += ''; - logHtml += '
'; - logHtml += "

" + log.Name + '

'; - const date = datetime.parseISO8601Date(log.DateModified, true); - let text = datetime.toLocaleDateString(date); - text += ' ' + datetime.getDisplayTime(date); - logHtml += '
' + text + '
'; - logHtml += '
'; - logHtml += '
'; - return logHtml; - }).join(''); - html += '
'; - view.querySelector('.serverLogs').innerHTML = html; - }); - - apiClient.getServerConfiguration().then(function (config) { - view.querySelector('#chkSlowResponseWarning').checked = config.EnableSlowResponseWarning; - view.querySelector('#txtSlowResponseWarning').value = config.SlowResponseThresholdMs; - }); - - loading.hide(); + apiClient.getServerConfiguration().then(function (config) { + view.querySelector('#chkSlowResponseWarning').checked = config.EnableSlowResponseWarning; + view.querySelector('#txtSlowResponseWarning').value = config.SlowResponseThresholdMs; }); - } -/* eslint-enable indent */ + loading.hide(); + }); +} diff --git a/src/controllers/dashboard/metadataImages.js b/src/controllers/dashboard/metadataImages.js index 383143715b..15dcbd8812 100644 --- a/src/controllers/dashboard/metadataImages.js +++ b/src/controllers/dashboard/metadataImages.js @@ -30,88 +30,85 @@ function populateImageResolutionOptions(select) { select.innerHTML = html; } -/* eslint-disable indent */ - - function populateLanguages(select) { - return ApiClient.getCultures().then(function(languages) { - let html = ''; - html += ""; - for (let i = 0, length = languages.length; i < length; i++) { - const culture = languages[i]; - html += "'; - } - select.innerHTML = html; - }); - } - - function populateCountries(select) { - return ApiClient.getCountries().then(function(allCountries) { - let html = ''; - html += ""; - for (let i = 0, length = allCountries.length; i < length; i++) { - const culture = allCountries[i]; - html += "'; - } - select.innerHTML = html; - }); - } - - function loadPage(page) { - const promises = [ - ApiClient.getServerConfiguration(), - populateLanguages(page.querySelector('#selectLanguage')), - populateCountries(page.querySelector('#selectCountry')) - ]; - - populateImageResolutionOptions(page.querySelector('#txtChapterImageResolution')); - - Promise.all(promises).then(function(responses) { - const config = responses[0]; - page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; - page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; - page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || ''; - page.querySelector('#valDummyChapterCount').value = config.DummyChapterCount || ''; - page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || ''; - loading.hide(); - }); - } - - function onSubmit() { - const form = this; - loading.show(); - ApiClient.getServerConfiguration().then(function(config) { - config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; - config.MetadataCountryCode = form.querySelector('#selectCountry').value; - config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value; - config.DummyChapterCount = form.querySelector('#valDummyChapterCount').value; - config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value; - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - return false; - } - - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - $(document).on('pageinit', '#metadataImagesConfigurationPage', function() { - $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#metadataImagesConfigurationPage', function() { - libraryMenu.setTabs('metadata', 2, getTabs); - loading.show(); - loadPage(this); +function populateLanguages(select) { + return ApiClient.getCultures().then(function(languages) { + let html = ''; + html += ""; + for (let i = 0, length = languages.length; i < length; i++) { + const culture = languages[i]; + html += "'; + } + select.innerHTML = html; }); +} + +function populateCountries(select) { + return ApiClient.getCountries().then(function(allCountries) { + let html = ''; + html += ""; + for (let i = 0, length = allCountries.length; i < length; i++) { + const culture = allCountries[i]; + html += "'; + } + select.innerHTML = html; + }); +} + +function loadPage(page) { + const promises = [ + ApiClient.getServerConfiguration(), + populateLanguages(page.querySelector('#selectLanguage')), + populateCountries(page.querySelector('#selectCountry')) + ]; + + populateImageResolutionOptions(page.querySelector('#txtChapterImageResolution')); + + Promise.all(promises).then(function(responses) { + const config = responses[0]; + page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; + page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; + page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || ''; + page.querySelector('#valDummyChapterCount').value = config.DummyChapterCount || ''; + page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || ''; + loading.hide(); + }); +} + +function onSubmit() { + const form = this; + loading.show(); + ApiClient.getServerConfiguration().then(function(config) { + config.PreferredMetadataLanguage = form.querySelector('#selectLanguage').value; + config.MetadataCountryCode = form.querySelector('#selectCountry').value; + config.DummyChapterDuration = form.querySelector('#valDummyChapterDuration').value; + config.DummyChapterCount = form.querySelector('#valDummyChapterCount').value; + config.ChapterImageResolution = form.querySelector('#txtChapterImageResolution').value; + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); + }); + return false; +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +$(document).on('pageinit', '#metadataImagesConfigurationPage', function() { + $('.metadataImagesConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#metadataImagesConfigurationPage', function() { + libraryMenu.setTabs('metadata', 2, getTabs); + loading.show(); + loadPage(this); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/metadatanfo.js b/src/controllers/dashboard/metadatanfo.js index 33cad75f7f..38e2ec71fe 100644 --- a/src/controllers/dashboard/metadatanfo.js +++ b/src/controllers/dashboard/metadatanfo.js @@ -6,74 +6,71 @@ import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function loadPage(page, config, users) { + let html = ''; + html += users.map(function (user) { + return ''; + }).join(''); + $('#selectUser', page).html(html).val(config.UserId || ''); + $('#selectReleaseDateFormat', page).val(config.ReleaseDateFormat); + page.querySelector('#chkSaveImagePaths').checked = config.SaveImagePathsInNfo; + page.querySelector('#chkEnablePathSubstitution').checked = config.EnablePathSubstitution; + page.querySelector('#chkEnableExtraThumbs').checked = config.EnableExtraThumbsDuplication; + loading.hide(); +} - function loadPage(page, config, users) { - let html = ''; - html += users.map(function (user) { - return ''; - }).join(''); - $('#selectUser', page).html(html).val(config.UserId || ''); - $('#selectReleaseDateFormat', page).val(config.ReleaseDateFormat); - page.querySelector('#chkSaveImagePaths').checked = config.SaveImagePathsInNfo; - page.querySelector('#chkEnablePathSubstitution').checked = config.EnablePathSubstitution; - page.querySelector('#chkEnableExtraThumbs').checked = config.EnableExtraThumbsDuplication; - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getNamedConfiguration(metadataKey).then(function (config) { - config.UserId = $('#selectUser', form).val() || null; - config.ReleaseDateFormat = $('#selectReleaseDateFormat', form).val(); - config.SaveImagePathsInNfo = form.querySelector('#chkSaveImagePaths').checked; - config.EnablePathSubstitution = form.querySelector('#chkEnablePathSubstitution').checked; - config.EnableExtraThumbsDuplication = form.querySelector('#chkEnableExtraThumbs').checked; - ApiClient.updateNamedConfiguration(metadataKey, config).then(function () { - Dashboard.processServerConfigurationUpdateResult(); - showConfirmMessage(); - }); - }); - return false; - } - - function showConfirmMessage() { - const msg = []; - msg.push(globalize.translate('MetadataSettingChangeHelp')); - alert({ - text: msg.join('

') - }); - } - - function getTabs() { - return [{ - href: '#/library.html', - name: globalize.translate('HeaderLibraries') - }, { - href: '#/librarydisplay.html', - name: globalize.translate('Display') - }, { - href: '#/metadataimages.html', - name: globalize.translate('Metadata') - }, { - href: '#/metadatanfo.html', - name: globalize.translate('TabNfoSettings') - }]; - } - - const metadataKey = 'xbmcmetadata'; - $(document).on('pageinit', '#metadataNfoPage', function () { - $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#metadataNfoPage', function () { - libraryMenu.setTabs('metadata', 3, getTabs); - loading.show(); - const page = this; - const promise1 = ApiClient.getUsers(); - const promise2 = ApiClient.getNamedConfiguration(metadataKey); - Promise.all([promise1, promise2]).then(function (responses) { - loadPage(page, responses[1], responses[0]); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getNamedConfiguration(metadataKey).then(function (config) { + config.UserId = $('#selectUser', form).val() || null; + config.ReleaseDateFormat = $('#selectReleaseDateFormat', form).val(); + config.SaveImagePathsInNfo = form.querySelector('#chkSaveImagePaths').checked; + config.EnablePathSubstitution = form.querySelector('#chkEnablePathSubstitution').checked; + config.EnableExtraThumbsDuplication = form.querySelector('#chkEnableExtraThumbs').checked; + ApiClient.updateNamedConfiguration(metadataKey, config).then(function () { + Dashboard.processServerConfigurationUpdateResult(); + showConfirmMessage(); }); }); + return false; +} + +function showConfirmMessage() { + const msg = []; + msg.push(globalize.translate('MetadataSettingChangeHelp')); + alert({ + text: msg.join('

') + }); +} + +function getTabs() { + return [{ + href: '#/library.html', + name: globalize.translate('HeaderLibraries') + }, { + href: '#/librarydisplay.html', + name: globalize.translate('Display') + }, { + href: '#/metadataimages.html', + name: globalize.translate('Metadata') + }, { + href: '#/metadatanfo.html', + name: globalize.translate('TabNfoSettings') + }]; +} + +const metadataKey = 'xbmcmetadata'; +$(document).on('pageinit', '#metadataNfoPage', function () { + $('.metadataNfoForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#metadataNfoPage', function () { + libraryMenu.setTabs('metadata', 3, getTabs); + loading.show(); + const page = this; + const promise1 = ApiClient.getUsers(); + const promise2 = ApiClient.getNamedConfiguration(metadataKey); + Promise.all([promise1, promise2]).then(function (responses) { + loadPage(page, responses[1], responses[0]); + }); +}); -/* eslint-enable indent */ diff --git a/src/controllers/dashboard/networking.js b/src/controllers/dashboard/networking.js index a98715705c..57a8a51b53 100644 --- a/src/controllers/dashboard/networking.js +++ b/src/controllers/dashboard/networking.js @@ -5,205 +5,202 @@ import '../../elements/emby-select/emby-select'; import Dashboard from '../../utils/dashboard'; import alert from '../../components/alert'; -/* eslint-disable indent */ +function onSubmit(e) { + const form = this; + const localAddress = form.querySelector('#txtLocalAddress').value; + const enableUpnp = form.querySelector('#chkEnableUpnp').checked; + confirmSelections(localAddress, enableUpnp, function () { + const validationResult = getValidationAlert(form); - function onSubmit(e) { - const form = this; - const localAddress = form.querySelector('#txtLocalAddress').value; - const enableUpnp = form.querySelector('#chkEnableUpnp').checked; - confirmSelections(localAddress, enableUpnp, function () { - const validationResult = getValidationAlert(form); - - if (validationResult) { - showAlertText(validationResult); - return; - } - - validateHttps(form).then(function () { - loading.show(); - ApiClient.getNamedConfiguration('network').then(function (config) { - config.LocalNetworkSubnets = form.querySelector('#txtLanNetworks').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.RemoteIPFilter = form.querySelector('#txtExternalAddressFilter').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.KnownProxies = form.querySelector('#txtKnownProxies').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - config.LocalNetworkAddresses = form.querySelector('#txtLocalAddress').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - - config.PublishedServerUriBySubnet = form.querySelector('#txtPublishedServer').value.split(',').map(function (s) { - return s.trim(); - }).filter(function (s) { - return s.length > 0; - }); - - config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; - config.PublicPort = form.querySelector('#txtPublicPort').value; - config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; - config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; - config.HttpsPortNumber = form.querySelector('#txtHttpsPort').value; - config.EnableHttps = form.querySelector('#chkEnableHttps').checked; - config.RequireHttps = form.querySelector('#chkRequireHttps').checked; - config.EnableUPnP = enableUpnp; - config.BaseUrl = form.querySelector('#txtBaseUrl').value; - config.EnableRemoteAccess = form.querySelector('#chkRemoteAccess').checked; - config.CertificatePath = form.querySelector('#txtCertificatePath').value || null; - config.CertificatePassword = form.querySelector('#txtCertPassword').value || null; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; - config.AutoDiscovery = form.querySelector('#chkAutodiscovery').checked; - config.AutoDiscoveryTracing = form.querySelector('#chkAutodiscoveryTracing').checked; - config.EnableIPV6 = form.querySelector('#chkEnableIP6').checked; - config.EnableIPV4 = form.querySelector('#chkEnableIP4').checked; - config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; - config.UDPPortRange = form.querySelector('#txtUDPPortRange').value; - config.HDHomerunPortRange = form.querySelector('#txtHDHomerunPortRange').value; - config.EnableSSDPTracing = form.querySelector('#chkEnableSSDPTracing').checked; - config.SSDPTracingFilter = form.querySelector('#txtSSDPTracingFilter').value; - ApiClient.updateNamedConfiguration('network', config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); - }); - }); - }); - e.preventDefault(); - } - - function triggerChange(select) { - const evt = document.createEvent('HTMLEvents'); - evt.initEvent('change', false, true); - select.dispatchEvent(evt); - } - - function getValidationAlert(form) { - if (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { - return 'The public http and https ports must be different.'; + if (validationResult) { + showAlertText(validationResult); + return; } - if (form.querySelector('#txtPortNumber').value === form.querySelector('#txtHttpsPort').value) { - return 'The http and https ports must be different.'; - } - - if (!form.querySelector('#chkEnableIP6').checked && !form.querySelector('#chkEnableIP4').checked) { - return 'Either IPv4 or IPv6 need to be checked.'; - } - - return null; - } - - function validateHttps(form) { - const certPath = form.querySelector('#txtCertificatePath').value || null; - const httpsEnabled = form.querySelector('#chkEnableHttps').checked; - - if (httpsEnabled && !certPath) { - return showAlertText({ - title: globalize.translate('TitleHostingSettings'), - text: globalize.translate('HttpsRequiresCert') - }).then(Promise.reject); - } - - return Promise.resolve(); - } - - function showAlertText(options) { - return new Promise(function (resolve, reject) { - alert(options).then(resolve, reject); - }); - } - - function confirmSelections(localAddress, enableUpnp, callback) { - if (localAddress || !enableUpnp) { - showAlertText({ - title: globalize.translate('TitleHostingSettings'), - text: globalize.translate('SettingsWarning') - }).then(callback); - } else { - callback(); - } - } - - export default function (view) { - function loadPage(page, config) { - page.querySelector('#txtPortNumber').value = config.HttpServerPortNumber; - page.querySelector('#txtPublicPort').value = config.PublicPort; - page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; - page.querySelector('#txtLocalAddress').value = (config.LocalNetworkAddresses || []).join(', '); - page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); - page.querySelector('#txtKnownProxies').value = (config.KnownProxies || []).join(', '); - page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); - page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; - page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; - page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; - page.querySelector('#chkEnableHttps').checked = config.EnableHttps; - page.querySelector('#chkRequireHttps').checked = config.RequireHttps; - page.querySelector('#txtBaseUrl').value = config.BaseUrl || ''; - const txtCertificatePath = page.querySelector('#txtCertificatePath'); - txtCertificatePath.value = config.CertificatePath || ''; - page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; - page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; - triggerChange(page.querySelector('#chkRemoteAccess')); - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; - page.querySelector('#chkAutodiscovery').checked = config.AutoDiscovery; - page.querySelector('#chkAutodiscoveryTracing').checked = config.AutoDiscoveryTracing; - page.querySelector('#chkEnableIP6').checked = config.EnableIPV6; - page.querySelector('#chkEnableIP4').checked = config.EnableIPV4; - page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; - page.querySelector('#txtUDPPortRange').value = config.UDPPortRange || ''; - page.querySelector('#txtHDHomerunPortRange').checked = config.HDHomerunPortRange || ''; - page.querySelector('#chkEnableSSDPTracing').checked = config.EnableSSDPTracing; - page.querySelector('#txtSSDPTracingFilter').value = config.SSDPTracingFilter || ''; - page.querySelector('#txtPublishedServer').value = (config.PublishedServerUriBySubnet || []).join(', '); - loading.hide(); - } - - view.querySelector('#chkRemoteAccess').addEventListener('change', function () { - if (this.checked) { - view.querySelector('.fldExternalAddressFilter').classList.remove('hide'); - view.querySelector('.fldExternalAddressFilterMode').classList.remove('hide'); - view.querySelector('.fldPublicPort').classList.remove('hide'); - view.querySelector('.fldPublicHttpsPort').classList.remove('hide'); - view.querySelector('.fldEnableUpnp').classList.remove('hide'); - } else { - view.querySelector('.fldExternalAddressFilter').classList.add('hide'); - view.querySelector('.fldExternalAddressFilterMode').classList.add('hide'); - view.querySelector('.fldPublicPort').classList.add('hide'); - view.querySelector('.fldPublicHttpsPort').classList.add('hide'); - view.querySelector('.fldEnableUpnp').classList.add('hide'); - } - }); - view.querySelector('#btnSelectCertPath').addEventListener('click', function () { - import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { - const picker = new DirectoryBrowser(); - picker.show({ - includeFiles: true, - includeDirectories: true, - callback: function (path) { - if (path) { - view.querySelector('#txtCertificatePath').value = path; - } - - picker.close(); - }, - header: globalize.translate('HeaderSelectCertificatePath') - }); - }); - }); - view.querySelector('.dashboardHostingForm').addEventListener('submit', onSubmit); - view.addEventListener('viewshow', function () { + validateHttps(form).then(function () { loading.show(); ApiClient.getNamedConfiguration('network').then(function (config) { - loadPage(view, config); + config.LocalNetworkSubnets = form.querySelector('#txtLanNetworks').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.RemoteIPFilter = form.querySelector('#txtExternalAddressFilter').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.KnownProxies = form.querySelector('#txtKnownProxies').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + config.LocalNetworkAddresses = form.querySelector('#txtLocalAddress').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + + config.PublishedServerUriBySubnet = form.querySelector('#txtPublishedServer').value.split(',').map(function (s) { + return s.trim(); + }).filter(function (s) { + return s.length > 0; + }); + + config.IsRemoteIPFilterBlacklist = form.querySelector('#selectExternalAddressFilterMode').value === 'blacklist'; + config.PublicPort = form.querySelector('#txtPublicPort').value; + config.PublicHttpsPort = form.querySelector('#txtPublicHttpsPort').value; + config.HttpServerPortNumber = form.querySelector('#txtPortNumber').value; + config.HttpsPortNumber = form.querySelector('#txtHttpsPort').value; + config.EnableHttps = form.querySelector('#chkEnableHttps').checked; + config.RequireHttps = form.querySelector('#chkRequireHttps').checked; + config.EnableUPnP = enableUpnp; + config.BaseUrl = form.querySelector('#txtBaseUrl').value; + config.EnableRemoteAccess = form.querySelector('#chkRemoteAccess').checked; + config.CertificatePath = form.querySelector('#txtCertificatePath').value || null; + config.CertificatePassword = form.querySelector('#txtCertPassword').value || null; + config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; + config.AutoDiscovery = form.querySelector('#chkAutodiscovery').checked; + config.AutoDiscoveryTracing = form.querySelector('#chkAutodiscoveryTracing').checked; + config.EnableIPV6 = form.querySelector('#chkEnableIP6').checked; + config.EnableIPV4 = form.querySelector('#chkEnableIP4').checked; + config.UPnPCreateHttpPortMap = form.querySelector('#chkCreateHttpPortMap').checked; + config.UDPPortRange = form.querySelector('#txtUDPPortRange').value; + config.HDHomerunPortRange = form.querySelector('#txtHDHomerunPortRange').value; + config.EnableSSDPTracing = form.querySelector('#chkEnableSSDPTracing').checked; + config.SSDPTracingFilter = form.querySelector('#txtSSDPTracingFilter').value; + ApiClient.updateNamedConfiguration('network', config).then(Dashboard.processServerConfigurationUpdateResult, Dashboard.processErrorResponse); }); }); + }); + e.preventDefault(); +} + +function triggerChange(select) { + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); + select.dispatchEvent(evt); +} + +function getValidationAlert(form) { + if (form.querySelector('#txtPublicPort').value === form.querySelector('#txtPublicHttpsPort').value) { + return 'The public http and https ports must be different.'; } -/* eslint-enable indent */ + if (form.querySelector('#txtPortNumber').value === form.querySelector('#txtHttpsPort').value) { + return 'The http and https ports must be different.'; + } + + if (!form.querySelector('#chkEnableIP6').checked && !form.querySelector('#chkEnableIP4').checked) { + return 'Either IPv4 or IPv6 need to be checked.'; + } + + return null; +} + +function validateHttps(form) { + const certPath = form.querySelector('#txtCertificatePath').value || null; + const httpsEnabled = form.querySelector('#chkEnableHttps').checked; + + if (httpsEnabled && !certPath) { + return showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('HttpsRequiresCert') + }).then(Promise.reject); + } + + return Promise.resolve(); +} + +function showAlertText(options) { + return new Promise(function (resolve, reject) { + alert(options).then(resolve, reject); + }); +} + +function confirmSelections(localAddress, enableUpnp, callback) { + if (localAddress || !enableUpnp) { + showAlertText({ + title: globalize.translate('TitleHostingSettings'), + text: globalize.translate('SettingsWarning') + }).then(callback); + } else { + callback(); + } +} + +export default function (view) { + function loadPage(page, config) { + page.querySelector('#txtPortNumber').value = config.HttpServerPortNumber; + page.querySelector('#txtPublicPort').value = config.PublicPort; + page.querySelector('#txtPublicHttpsPort').value = config.PublicHttpsPort; + page.querySelector('#txtLocalAddress').value = (config.LocalNetworkAddresses || []).join(', '); + page.querySelector('#txtLanNetworks').value = (config.LocalNetworkSubnets || []).join(', '); + page.querySelector('#txtKnownProxies').value = (config.KnownProxies || []).join(', '); + page.querySelector('#txtExternalAddressFilter').value = (config.RemoteIPFilter || []).join(', '); + page.querySelector('#selectExternalAddressFilterMode').value = config.IsRemoteIPFilterBlacklist ? 'blacklist' : 'whitelist'; + page.querySelector('#chkRemoteAccess').checked = config.EnableRemoteAccess == null || config.EnableRemoteAccess; + page.querySelector('#txtHttpsPort').value = config.HttpsPortNumber; + page.querySelector('#chkEnableHttps').checked = config.EnableHttps; + page.querySelector('#chkRequireHttps').checked = config.RequireHttps; + page.querySelector('#txtBaseUrl').value = config.BaseUrl || ''; + const txtCertificatePath = page.querySelector('#txtCertificatePath'); + txtCertificatePath.value = config.CertificatePath || ''; + page.querySelector('#txtCertPassword').value = config.CertificatePassword || ''; + page.querySelector('#chkEnableUpnp').checked = config.EnableUPnP; + triggerChange(page.querySelector('#chkRemoteAccess')); + page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; + page.querySelector('#chkAutodiscovery').checked = config.AutoDiscovery; + page.querySelector('#chkAutodiscoveryTracing').checked = config.AutoDiscoveryTracing; + page.querySelector('#chkEnableIP6').checked = config.EnableIPV6; + page.querySelector('#chkEnableIP4').checked = config.EnableIPV4; + page.querySelector('#chkCreateHttpPortMap').checked = config.UPnPCreateHttpPortMap; + page.querySelector('#txtUDPPortRange').value = config.UDPPortRange || ''; + page.querySelector('#txtHDHomerunPortRange').checked = config.HDHomerunPortRange || ''; + page.querySelector('#chkEnableSSDPTracing').checked = config.EnableSSDPTracing; + page.querySelector('#txtSSDPTracingFilter').value = config.SSDPTracingFilter || ''; + page.querySelector('#txtPublishedServer').value = (config.PublishedServerUriBySubnet || []).join(', '); + loading.hide(); + } + + view.querySelector('#chkRemoteAccess').addEventListener('change', function () { + if (this.checked) { + view.querySelector('.fldExternalAddressFilter').classList.remove('hide'); + view.querySelector('.fldExternalAddressFilterMode').classList.remove('hide'); + view.querySelector('.fldPublicPort').classList.remove('hide'); + view.querySelector('.fldPublicHttpsPort').classList.remove('hide'); + view.querySelector('.fldEnableUpnp').classList.remove('hide'); + } else { + view.querySelector('.fldExternalAddressFilter').classList.add('hide'); + view.querySelector('.fldExternalAddressFilterMode').classList.add('hide'); + view.querySelector('.fldPublicPort').classList.add('hide'); + view.querySelector('.fldPublicHttpsPort').classList.add('hide'); + view.querySelector('.fldEnableUpnp').classList.add('hide'); + } + }); + view.querySelector('#btnSelectCertPath').addEventListener('click', function () { + import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { + const picker = new DirectoryBrowser(); + picker.show({ + includeFiles: true, + includeDirectories: true, + callback: function (path) { + if (path) { + view.querySelector('#txtCertificatePath').value = path; + } + + picker.close(); + }, + header: globalize.translate('HeaderSelectCertificatePath') + }); + }); + }); + view.querySelector('.dashboardHostingForm').addEventListener('submit', onSubmit); + view.addEventListener('viewshow', function () { + loading.show(); + ApiClient.getNamedConfiguration('network').then(function (config) { + loadPage(view, config); + }); + }); +} + diff --git a/src/controllers/dashboard/playback.js b/src/controllers/dashboard/playback.js index 90df298120..f24e77efd9 100644 --- a/src/controllers/dashboard/playback.js +++ b/src/controllers/dashboard/playback.js @@ -4,55 +4,52 @@ import libraryMenu from '../../scripts/libraryMenu'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config) { + $('#txtMinResumePct', page).val(config.MinResumePct); + $('#txtMaxResumePct', page).val(config.MaxResumePct); + $('#txtMinAudiobookResume', page).val(config.MinAudiobookResume); + $('#txtMaxAudiobookResume', page).val(config.MaxAudiobookResume); + $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds); + loading.hide(); +} - function loadPage(page, config) { - $('#txtMinResumePct', page).val(config.MinResumePct); - $('#txtMaxResumePct', page).val(config.MaxResumePct); - $('#txtMinAudiobookResume', page).val(config.MinAudiobookResume); - $('#txtMaxAudiobookResume', page).val(config.MaxAudiobookResume); - $('#txtMinResumeDuration', page).val(config.MinResumeDurationSeconds); - loading.hide(); - } +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.MinResumePct = $('#txtMinResumePct', form).val(); + config.MaxResumePct = $('#txtMaxResumePct', form).val(); + config.MinAudiobookResume = $('#txtMinAudiobookResume', form).val(); + config.MaxAudiobookResume = $('#txtMaxAudiobookResume', form).val(); + config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val(); - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.MinResumePct = $('#txtMinResumePct', form).val(); - config.MaxResumePct = $('#txtMaxResumePct', form).val(); - config.MinAudiobookResume = $('#txtMinAudiobookResume', form).val(); - config.MaxAudiobookResume = $('#txtMaxAudiobookResume', form).val(); - config.MinResumeDurationSeconds = $('#txtMinResumeDuration', form).val(); - - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - $(document).on('pageinit', '#playbackConfigurationPage', function () { - $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#playbackConfigurationPage', function () { - loading.show(); - libraryMenu.setTabs('playback', 1, getTabs); - const page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); -/* eslint-enable indent */ + return false; +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +$(document).on('pageinit', '#playbackConfigurationPage', function () { + $('.playbackConfigurationForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#playbackConfigurationPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 1, getTabs); + const page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); +}); + diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index 64db2e4292..909bbfffe3 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -9,238 +9,235 @@ import '../../../elements/emby-select/emby-select'; import confirm from '../../../components/confirm/confirm'; import { getParameterByName } from '../../../utils/url.ts'; -/* eslint-disable indent */ +function fillTimeOfDay(select) { + const options = []; - function fillTimeOfDay(select) { - const options = []; - - for (let i = 0; i < 86400000; i += 900000) { - options.push({ - name: ScheduledTaskPage.getDisplayTime(i * 10000), - value: i * 10000 - }); - } - - select.innerHTML = options.map(function (o) { - return ''; - }).join(''); + for (let i = 0; i < 86400000; i += 900000) { + options.push({ + name: ScheduledTaskPage.getDisplayTime(i * 10000), + value: i * 10000 + }); } - Array.prototype.remove = function (from, to) { - const rest = this.slice((to || from) + 1 || this.length); - this.length = from < 0 ? this.length + from : from; - return this.push.apply(this, rest); - }; + select.innerHTML = options.map(function (o) { + return ''; + }).join(''); +} - const ScheduledTaskPage = { - refreshScheduledTask: function (view) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - ScheduledTaskPage.loadScheduledTask(view, task); - }); - }, - loadScheduledTask: function (view, task) { - $('.taskName', view).html(task.Name); - $('#pTaskDescription', view).html(task.Description); +Array.prototype.remove = function (from, to) { + const rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); +}; - import('../../../components/listview/listview.scss').then(() => { - ScheduledTaskPage.loadTaskTriggers(view, task); - }); +const ScheduledTaskPage = { + refreshScheduledTask: function (view) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + ScheduledTaskPage.loadScheduledTask(view, task); + }); + }, + loadScheduledTask: function (view, task) { + $('.taskName', view).html(task.Name); + $('#pTaskDescription', view).html(task.Description); - loading.hide(); - }, - loadTaskTriggers: function (context, task) { - let html = ''; - html += '
'; + import('../../../components/listview/listview.scss').then(() => { + ScheduledTaskPage.loadTaskTriggers(view, task); + }); - for (let i = 0, length = task.Triggers.length; i < length; i++) { - const trigger = task.Triggers[i]; + loading.hide(); + }, + loadTaskTriggers: function (context, task) { + let html = ''; + html += '
'; - html += '
'; - html += ''; - if (trigger.MaxRuntimeMs) { - html += '
'; + for (let i = 0, length = task.Triggers.length; i < length; i++) { + const trigger = task.Triggers[i]; + + html += '
'; + html += ''; + if (trigger.MaxRuntimeMs) { + html += '
'; + } else { + html += '
'; + } + html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; + if (trigger.MaxRuntimeMs) { + html += '
'; + const hours = trigger.MaxRuntimeTicks / 36e9; + if (hours == 1) { + html += globalize.translate('ValueTimeLimitSingleHour'); } else { - html += '
'; + html += globalize.translate('ValueTimeLimitMultiHour', hours); } - html += "
" + ScheduledTaskPage.getTriggerFriendlyName(trigger) + '
'; - if (trigger.MaxRuntimeMs) { - html += '
'; - const hours = trigger.MaxRuntimeTicks / 36e9; - if (hours == 1) { - html += globalize.translate('ValueTimeLimitSingleHour'); - } else { - html += globalize.translate('ValueTimeLimitMultiHour', hours); - } - html += '
'; - } - - html += '
'; - html += ''; html += '
'; } html += '
'; - context.querySelector('.taskTriggers').innerHTML = html; - }, - // TODO: Replace this mess with date-fns and remove datetime completely - getTriggerFriendlyName: function (trigger) { - if (trigger.Type == 'DailyTrigger') { - return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'WeeklyTrigger') { - // TODO: The day of week isn't localised as well - return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); - } - - if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { - return globalize.translate('OnWakeFromSleep'); - } - - if (trigger.Type == 'IntervalTrigger') { - const hours = trigger.IntervalTicks / 36e9; - - if (hours == 0.25) { - return globalize.translate('EveryXMinutes', '15'); - } - if (hours == 0.5) { - return globalize.translate('EveryXMinutes', '30'); - } - if (hours == 0.75) { - return globalize.translate('EveryXMinutes', '45'); - } - if (hours == 1) { - return globalize.translate('EveryHour'); - } - - return globalize.translate('EveryXHours', hours); - } - - if (trigger.Type == 'StartupTrigger') { - return globalize.translate('OnApplicationStartup'); - } - - return trigger.Type; - }, - getDisplayTime: function (ticks) { - const ms = ticks / 1e4; - const now = new Date(); - now.setHours(0, 0, 0, 0); - now.setTime(now.getTime() + ms); - return datetime.getDisplayTime(now); - }, - showAddTriggerPopup: function (view) { - $('#selectTriggerType', view).val('DailyTrigger'); - view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); - $('#popupAddTrigger', view).removeClass('hide'); - }, - confirmDeleteTrigger: function (view, index) { - confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { - ScheduledTaskPage.deleteTrigger(view, index); - }); - }, - deleteTrigger: function (view, index) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.remove(index); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - }, - refreshTriggerFields: function (page, triggerType) { - if (triggerType == 'DailyTrigger') { - $('#fldTimeOfDay', page).show(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).attr('required', 'required'); - } else if (triggerType == 'WeeklyTrigger') { - $('#fldTimeOfDay', page).show(); - $('#fldDayOfWeek', page).show(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).attr('required', 'required'); - } else if (triggerType == 'SystemEventTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).show(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).removeAttr('required'); - } else if (triggerType == 'IntervalTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).show(); - $('#selectTimeOfDay', page).removeAttr('required'); - } else if (triggerType == 'StartupTrigger') { - $('#fldTimeOfDay', page).hide(); - $('#fldDayOfWeek', page).hide(); - $('#fldSelectSystemEvent', page).hide(); - $('#fldSelectInterval', page).hide(); - $('#selectTimeOfDay', page).removeAttr('required'); - } - }, - getTriggerToAdd: function (page) { - const trigger = { - Type: $('#selectTriggerType', page).val() - }; - - if (trigger.Type == 'DailyTrigger') { - trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); - } else if (trigger.Type == 'WeeklyTrigger') { - trigger.DayOfWeek = $('#selectDayOfWeek', page).val(); - trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); - } else if (trigger.Type == 'SystemEventTrigger') { - trigger.SystemEvent = $('#selectSystemEvent', page).val(); - } else if (trigger.Type == 'IntervalTrigger') { - trigger.IntervalTicks = $('#selectInterval', page).val(); - } - - let timeLimit = $('#txtTimeLimit', page).val() || '0'; - timeLimit = parseFloat(timeLimit) * 3600000; - - trigger.MaxRuntimeMs = timeLimit || null; - - return trigger; - } - }; - export default function (view) { - function onSubmit(e) { - loading.show(); - const id = getParameterByName('id'); - ApiClient.getScheduledTask(id).then(function (task) { - task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); - ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { - $('#popupAddTrigger').addClass('hide'); - ScheduledTaskPage.refreshScheduledTask(view); - }); - }); - e.preventDefault(); + html += ''; + html += '
'; } - view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit); - fillTimeOfDay(view.querySelector('#selectTimeOfDay')); - $(view.querySelector('#popupAddTrigger').parentNode).trigger('create'); - view.querySelector('.selectTriggerType').addEventListener('change', function () { - ScheduledTaskPage.refreshTriggerFields(view, this.value); - }); - view.querySelector('.btnAddTrigger').addEventListener('click', function () { - ScheduledTaskPage.showAddTriggerPopup(view); - }); - view.addEventListener('click', function (e) { - const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); + html += '
'; + context.querySelector('.taskTriggers').innerHTML = html; + }, + // TODO: Replace this mess with date-fns and remove datetime completely + getTriggerFriendlyName: function (trigger) { + if (trigger.Type == 'DailyTrigger') { + return globalize.translate('DailyAt', ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); + } - if (btnDeleteTrigger) { - ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); + if (trigger.Type == 'WeeklyTrigger') { + // TODO: The day of week isn't localised as well + return globalize.translate('WeeklyAt', trigger.DayOfWeek, ScheduledTaskPage.getDisplayTime(trigger.TimeOfDayTicks)); + } + + if (trigger.Type == 'SystemEventTrigger' && trigger.SystemEvent == 'WakeFromSleep') { + return globalize.translate('OnWakeFromSleep'); + } + + if (trigger.Type == 'IntervalTrigger') { + const hours = trigger.IntervalTicks / 36e9; + + if (hours == 0.25) { + return globalize.translate('EveryXMinutes', '15'); } + if (hours == 0.5) { + return globalize.translate('EveryXMinutes', '30'); + } + if (hours == 0.75) { + return globalize.translate('EveryXMinutes', '45'); + } + if (hours == 1) { + return globalize.translate('EveryHour'); + } + + return globalize.translate('EveryXHours', hours); + } + + if (trigger.Type == 'StartupTrigger') { + return globalize.translate('OnApplicationStartup'); + } + + return trigger.Type; + }, + getDisplayTime: function (ticks) { + const ms = ticks / 1e4; + const now = new Date(); + now.setHours(0, 0, 0, 0); + now.setTime(now.getTime() + ms); + return datetime.getDisplayTime(now); + }, + showAddTriggerPopup: function (view) { + $('#selectTriggerType', view).val('DailyTrigger'); + view.querySelector('#selectTriggerType').dispatchEvent(new CustomEvent('change', {})); + $('#popupAddTrigger', view).removeClass('hide'); + }, + confirmDeleteTrigger: function (view, index) { + confirm(globalize.translate('MessageDeleteTaskTrigger'), globalize.translate('HeaderDeleteTaskTrigger')).then(function () { + ScheduledTaskPage.deleteTrigger(view, index); }); - view.addEventListener('viewshow', function () { - ScheduledTaskPage.refreshScheduledTask(view); + }, + deleteTrigger: function (view, index) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + task.Triggers.remove(index); + ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { + ScheduledTaskPage.refreshScheduledTask(view); + }); }); + }, + refreshTriggerFields: function (page, triggerType) { + if (triggerType == 'DailyTrigger') { + $('#fldTimeOfDay', page).show(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).attr('required', 'required'); + } else if (triggerType == 'WeeklyTrigger') { + $('#fldTimeOfDay', page).show(); + $('#fldDayOfWeek', page).show(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).attr('required', 'required'); + } else if (triggerType == 'SystemEventTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).show(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).removeAttr('required'); + } else if (triggerType == 'IntervalTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).show(); + $('#selectTimeOfDay', page).removeAttr('required'); + } else if (triggerType == 'StartupTrigger') { + $('#fldTimeOfDay', page).hide(); + $('#fldDayOfWeek', page).hide(); + $('#fldSelectSystemEvent', page).hide(); + $('#fldSelectInterval', page).hide(); + $('#selectTimeOfDay', page).removeAttr('required'); + } + }, + getTriggerToAdd: function (page) { + const trigger = { + Type: $('#selectTriggerType', page).val() + }; + + if (trigger.Type == 'DailyTrigger') { + trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); + } else if (trigger.Type == 'WeeklyTrigger') { + trigger.DayOfWeek = $('#selectDayOfWeek', page).val(); + trigger.TimeOfDayTicks = $('#selectTimeOfDay', page).val(); + } else if (trigger.Type == 'SystemEventTrigger') { + trigger.SystemEvent = $('#selectSystemEvent', page).val(); + } else if (trigger.Type == 'IntervalTrigger') { + trigger.IntervalTicks = $('#selectInterval', page).val(); + } + + let timeLimit = $('#txtTimeLimit', page).val() || '0'; + timeLimit = parseFloat(timeLimit) * 3600000; + + trigger.MaxRuntimeMs = timeLimit || null; + + return trigger; + } +}; +export default function (view) { + function onSubmit(e) { + loading.show(); + const id = getParameterByName('id'); + ApiClient.getScheduledTask(id).then(function (task) { + task.Triggers.push(ScheduledTaskPage.getTriggerToAdd(view)); + ApiClient.updateScheduledTaskTriggers(task.Id, task.Triggers).then(function () { + $('#popupAddTrigger').addClass('hide'); + ScheduledTaskPage.refreshScheduledTask(view); + }); + }); + e.preventDefault(); } -/* eslint-enable indent */ + view.querySelector('.addTriggerForm').addEventListener('submit', onSubmit); + fillTimeOfDay(view.querySelector('#selectTimeOfDay')); + $(view.querySelector('#popupAddTrigger').parentNode).trigger('create'); + view.querySelector('.selectTriggerType').addEventListener('change', function () { + ScheduledTaskPage.refreshTriggerFields(view, this.value); + }); + view.querySelector('.btnAddTrigger').addEventListener('click', function () { + ScheduledTaskPage.showAddTriggerPopup(view); + }); + view.addEventListener('click', function (e) { + const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); + + if (btnDeleteTrigger) { + ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); + } + }); + view.addEventListener('viewshow', function () { + ScheduledTaskPage.refreshScheduledTask(view); + }); +} + diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 0c8f0dca37..c074331aa1 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -9,194 +9,191 @@ import Events from '../../../utils/events.ts'; import '../../../components/listview/listview.scss'; import '../../../elements/emby-button/emby-button'; -/* eslint-disable indent */ +function reloadList(page) { + ApiClient.getScheduledTasks({ + isHidden: false + }).then(function(tasks) { + populateList(page, tasks); + loading.hide(); + }); +} - function reloadList(page) { - ApiClient.getScheduledTasks({ - isHidden: false - }).then(function(tasks) { - populateList(page, tasks); - loading.hide(); - }); - } +function populateList(page, tasks) { + tasks = tasks.sort(function(a, b) { + a = a.Category + ' ' + a.Name; + b = b.Category + ' ' + b.Name; + if (a > b) { + return 1; + } else if (a < b) { + return -1; + } else { + return 0; + } + }); - function populateList(page, tasks) { - tasks = tasks.sort(function(a, b) { - a = a.Category + ' ' + a.Name; - b = b.Category + ' ' + b.Name; - if (a > b) { - return 1; - } else if (a < b) { - return -1; - } else { - return 0; + let currentCategory; + let html = ''; + for (let i = 0; i < tasks.length; i++) { + const task = tasks[i]; + if (task.Category != currentCategory) { + currentCategory = task.Category; + if (currentCategory) { + html += '
'; + html += '
'; } - }); + html += '
'; + html += '
'; + html += '

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

'; + if (i === 0) { + html += '' + globalize.translate('Help') + ''; + } + html += '
'; + html += '
'; + } + html += '
'; + html += ""; + html += ''; + html += ''; + html += '
'; + let textAlignStyle = 'left'; + if (globalize.getIsRTL()) + textAlignStyle = 'right'; + html += ""; + html += "

" + task.Name + '

'; + html += "
" + getTaskProgressHtml(task) + '
'; + html += '
'; + html += '
'; + if (task.State === 'Running') { + html += ''; + } else if (task.State === 'Idle') { + html += ''; + } + html += '
'; + } + if (tasks.length) { + html += '
'; + html += '
'; + } + page.querySelector('.divScheduledTasks').innerHTML = html; +} - let currentCategory; - let html = ''; +function getTaskProgressHtml(task) { + let html = ''; + if (task.State === 'Idle') { + if (task.LastExecutionResult) { + const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); + const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); + html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()), + formatDistance(starttime, endtime, { locale: getLocale() })); + if (task.LastExecutionResult.Status === 'Failed') { + html += " (" + globalize.translate('LabelFailed') + ')'; + } else if (task.LastExecutionResult.Status === 'Cancelled') { + html += " (" + globalize.translate('LabelCancelled') + ')'; + } else if (task.LastExecutionResult.Status === 'Aborted') { + html += " " + globalize.translate('LabelAbortedByServerShutdown') + ''; + } + } + } else if (task.State === 'Running') { + const progress = (task.CurrentProgressPercentage || 0).toFixed(1); + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += "" + progress + '%'; + html += '
'; + } else { + html += "" + globalize.translate('LabelStopping') + ''; + } + return html; +} + +function setTaskButtonIcon(button, icon) { + const inner = button.querySelector('.material-icons'); + inner.classList.remove('stop', 'play_arrow'); + inner.classList.add(icon); +} + +function updateTaskButton(elem, state) { + if (state === 'Running') { + elem.classList.remove('btnStartTask'); + elem.classList.add('btnStopTask'); + setTaskButtonIcon(elem, 'stop'); + elem.title = globalize.translate('ButtonStop'); + } else if (state === 'Idle') { + elem.classList.add('btnStartTask'); + elem.classList.remove('btnStopTask'); + setTaskButtonIcon(elem, 'play_arrow'); + elem.title = globalize.translate('ButtonStart'); + } + $(elem).parents('.listItem')[0].setAttribute('data-status', state); +} + +export default function(view) { + function updateTasks(tasks) { for (let i = 0; i < tasks.length; i++) { const task = tasks[i]; - if (task.Category != currentCategory) { - currentCategory = task.Category; - if (currentCategory) { - html += '
'; - html += '
'; - } - html += '
'; - html += '
'; - html += '

'; - html += currentCategory; - html += '

'; - if (i === 0) { - html += '' + globalize.translate('Help') + ''; - } - html += '
'; - html += '
'; - } - html += '
'; - html += ""; - html += ''; - html += ''; - html += '
'; - let textAlignStyle = 'left'; - if (globalize.getIsRTL()) - textAlignStyle = 'right'; - html += ""; - html += "

" + task.Name + '

'; - html += "
" + getTaskProgressHtml(task) + '
'; - html += '
'; - html += '
'; - if (task.State === 'Running') { - html += ''; - } else if (task.State === 'Idle') { - html += ''; - } - html += '
'; + view.querySelector('#taskProgress' + task.Id).innerHTML = getTaskProgressHtml(task); + updateTaskButton(view.querySelector('#btnTask' + task.Id), task.State); } - if (tasks.length) { - html += '
'; - html += '
'; - } - page.querySelector('.divScheduledTasks').innerHTML = html; } - function getTaskProgressHtml(task) { - let html = ''; - if (task.State === 'Idle') { - if (task.LastExecutionResult) { - const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); - const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); - html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()), - formatDistance(starttime, endtime, { locale: getLocale() })); - if (task.LastExecutionResult.Status === 'Failed') { - html += " (" + globalize.translate('LabelFailed') + ')'; - } else if (task.LastExecutionResult.Status === 'Cancelled') { - html += " (" + globalize.translate('LabelCancelled') + ')'; - } else if (task.LastExecutionResult.Status === 'Aborted') { - html += " " + globalize.translate('LabelAbortedByServerShutdown') + ''; - } - } - } else if (task.State === 'Running') { - const progress = (task.CurrentProgressPercentage || 0).toFixed(1); - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += '
'; - html += "" + progress + '%'; - html += '
'; - } else { - html += "" + globalize.translate('LabelStopping') + ''; - } - return html; - } - - function setTaskButtonIcon(button, icon) { - const inner = button.querySelector('.material-icons'); - inner.classList.remove('stop', 'play_arrow'); - inner.classList.add(icon); - } - - function updateTaskButton(elem, state) { - if (state === 'Running') { - elem.classList.remove('btnStartTask'); - elem.classList.add('btnStopTask'); - setTaskButtonIcon(elem, 'stop'); - elem.title = globalize.translate('ButtonStop'); - } else if (state === 'Idle') { - elem.classList.add('btnStartTask'); - elem.classList.remove('btnStopTask'); - setTaskButtonIcon(elem, 'play_arrow'); - elem.title = globalize.translate('ButtonStart'); - } - $(elem).parents('.listItem')[0].setAttribute('data-status', state); - } - - export default function(view) { - function updateTasks(tasks) { - for (let i = 0; i < tasks.length; i++) { - const task = tasks[i]; - view.querySelector('#taskProgress' + task.Id).innerHTML = getTaskProgressHtml(task); - updateTaskButton(view.querySelector('#btnTask' + task.Id), task.State); - } - } - - function onPollIntervalFired() { - if (!ApiClient.isMessageChannelOpen()) { - reloadList(view); - } - } - - function onScheduledTasksUpdate(e, apiClient, info) { - if (apiClient.serverId() === serverId) { - updateTasks(info); - } - } - - function startInterval() { - ApiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); - pollInterval && clearInterval(pollInterval); - pollInterval = setInterval(onPollIntervalFired, 1e4); - } - - function stopInterval() { - ApiClient.sendMessage('ScheduledTasksInfoStop'); - pollInterval && clearInterval(pollInterval); - } - - let pollInterval; - const serverId = ApiClient.serverId(); - - $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { - const button = this; - const id = button.getAttribute('data-taskid'); - ApiClient.startScheduledTask(id).then(function() { - updateTaskButton(button, 'Running'); - reloadList(view); - }); - }); - - $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { - const button = this; - const id = button.getAttribute('data-taskid'); - ApiClient.stopScheduledTask(id).then(function() { - updateTaskButton(button, ''); - reloadList(view); - }); - }); - - view.addEventListener('viewbeforehide', function() { - Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - stopInterval(); - }); - - view.addEventListener('viewshow', function() { - loading.show(); - startInterval(); + function onPollIntervalFired() { + if (!ApiClient.isMessageChannelOpen()) { reloadList(view); - Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); - }); + } } -/* eslint-enable indent */ + function onScheduledTasksUpdate(e, apiClient, info) { + if (apiClient.serverId() === serverId) { + updateTasks(info); + } + } + + function startInterval() { + ApiClient.sendMessage('ScheduledTasksInfoStart', '1000,1000'); + pollInterval && clearInterval(pollInterval); + pollInterval = setInterval(onPollIntervalFired, 1e4); + } + + function stopInterval() { + ApiClient.sendMessage('ScheduledTasksInfoStop'); + pollInterval && clearInterval(pollInterval); + } + + let pollInterval; + const serverId = ApiClient.serverId(); + + $('.divScheduledTasks', view).on('click', '.btnStartTask', function() { + const button = this; + const id = button.getAttribute('data-taskid'); + ApiClient.startScheduledTask(id).then(function() { + updateTaskButton(button, 'Running'); + reloadList(view); + }); + }); + + $('.divScheduledTasks', view).on('click', '.btnStopTask', function() { + const button = this; + const id = button.getAttribute('data-taskid'); + ApiClient.stopScheduledTask(id).then(function() { + updateTaskButton(button, ''); + reloadList(view); + }); + }); + + view.addEventListener('viewbeforehide', function() { + Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + stopInterval(); + }); + + view.addEventListener('viewshow', function() { + loading.show(); + startInterval(); + reloadList(view); + Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); + }); +} + diff --git a/src/controllers/dashboard/serveractivity.js b/src/controllers/dashboard/serveractivity.js index e8471999dd..72e84a89d5 100644 --- a/src/controllers/dashboard/serveractivity.js +++ b/src/controllers/dashboard/serveractivity.js @@ -2,34 +2,31 @@ import ActivityLog from '../../components/activitylog'; import globalize from '../../scripts/globalize'; import { toBoolean } from '../../utils/string.ts'; -/* eslint-disable indent */ +export default function (view, params) { + let activityLog; - export default function (view, params) { - let activityLog; - - if (toBoolean(params.useractivity, true)) { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'true'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('HeaderActivity'); - } else { - view.querySelector('.activityItems').setAttribute('data-useractivity', 'false'); - view.querySelector('.sectionTitle').innerHTML = globalize.translate('Alerts'); - } - - view.addEventListener('viewshow', function () { - if (!activityLog) { - activityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: view.querySelector('.activityItems') - }); - } - }); - view.addEventListener('viewdestroy', function () { - if (activityLog) { - activityLog.destroy(); - } - - activityLog = null; - }); + if (toBoolean(params.useractivity, true)) { + view.querySelector('.activityItems').setAttribute('data-useractivity', 'true'); + view.querySelector('.sectionTitle').innerHTML = globalize.translate('HeaderActivity'); + } else { + view.querySelector('.activityItems').setAttribute('data-useractivity', 'false'); + view.querySelector('.sectionTitle').innerHTML = globalize.translate('Alerts'); } -/* eslint-enable indent */ + view.addEventListener('viewshow', function () { + if (!activityLog) { + activityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: view.querySelector('.activityItems') + }); + } + }); + view.addEventListener('viewdestroy', function () { + if (activityLog) { + activityLog.destroy(); + } + + activityLog = null; + }); +} + diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index 8832e4bff4..ba9d767517 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -4,46 +4,43 @@ import loading from '../../components/loading/loading'; import globalize from '../../scripts/globalize'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function loadPage(page, config) { + $('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || ''); + loading.hide(); +} - function loadPage(page, config) { - $('#txtRemoteClientBitrateLimit', page).val(config.RemoteClientBitrateLimit / 1e6 || ''); - loading.hide(); - } - - function onSubmit() { - loading.show(); - const form = this; - ApiClient.getServerConfiguration().then(function (config) { - config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10); - ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); - }); - - return false; - } - - function getTabs() { - return [{ - href: '#/encodingsettings.html', - name: globalize.translate('Transcoding') - }, { - href: '#/playbackconfiguration.html', - name: globalize.translate('ButtonResume') - }, { - href: '#/streamingsettings.html', - name: globalize.translate('TabStreaming') - }]; - } - - $(document).on('pageinit', '#streamingSettingsPage', function () { - $('.streamingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); - }).on('pageshow', '#streamingSettingsPage', function () { - loading.show(); - libraryMenu.setTabs('playback', 2, getTabs); - const page = this; - ApiClient.getServerConfiguration().then(function (config) { - loadPage(page, config); - }); +function onSubmit() { + loading.show(); + const form = this; + ApiClient.getServerConfiguration().then(function (config) { + config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10); + ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); -/* eslint-enable indent */ + return false; +} + +function getTabs() { + return [{ + href: '#/encodingsettings.html', + name: globalize.translate('Transcoding') + }, { + href: '#/playbackconfiguration.html', + name: globalize.translate('ButtonResume') + }, { + href: '#/streamingsettings.html', + name: globalize.translate('TabStreaming') + }]; +} + +$(document).on('pageinit', '#streamingSettingsPage', function () { + $('.streamingSettingsForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#streamingSettingsPage', function () { + loading.show(); + libraryMenu.setTabs('playback', 2, getTabs); + const page = this; + ApiClient.getServerConfiguration().then(function (config) { + loadPage(page, config); + }); +}); + diff --git a/src/controllers/dashboard/users/useredit.html b/src/controllers/dashboard/users/useredit.html new file mode 100644 index 0000000000..cf7d727ff7 --- /dev/null +++ b/src/controllers/dashboard/users/useredit.html @@ -0,0 +1,198 @@ +
+ +
+
+ +
+
+

+ ${Help} +
+
+ + +

+ ${ButtonEditOtherUserPreferences} +

+
+ + +
+ +
+ +
+ +
${AuthProviderHelp}
+
+ +
+ +
${PasswordResetProviderHelp}
+
+ +
+ +
${AllowRemoteAccessHelp}
+
+ + +
+

${HeaderFeatureAccess}

+
+ + +
+
+
+

${HeaderPlayback}

+
+ + + + + +
+
${OptionAllowMediaPlaybackTranscodingHelp}
+
+
+
+
+ +
${LabelRemoteClientBitrateLimitHelp}
+
${LabelUserRemoteClientBitrateLimitHelp}
+
+
+
+
+ +
${SyncPlayAccessHelp}
+
+
+
+

${HeaderAllowMediaDeletionFrom}

+
+ +
+
+
+
+
+

${HeaderRemoteControl}

+
+ + + +
+
${OptionAllowRemoteSharedDevicesHelp}
+
+

${Other}

+
+ +
${OptionAllowContentDownloadHelp}
+
+
+ +
${OptionDisableUserHelp}
+
+
+ +
${OptionHideUserFromLoginHelp}
+
+
+
+
+ +
${OptionLoginAttemptsBeforeLockout}
+
${OptionLoginAttemptsBeforeLockoutHelp}
+
+
+
+
+
+ +
${OptionMaxActiveSessions}
+
${OptionMaxActiveSessionsHelp}
+
+
+
+
+ + + +
+
+
+
+
diff --git a/src/controllers/dashboard/users/useredit.js b/src/controllers/dashboard/users/useredit.js new file mode 100644 index 0000000000..98aa0dd40e --- /dev/null +++ b/src/controllers/dashboard/users/useredit.js @@ -0,0 +1,196 @@ +import 'jquery'; +import loading from '../../../components/loading/loading'; +import libraryMenu from '../../../scripts/libraryMenu'; +import globalize from '../../../scripts/globalize'; +import Dashboard from '../../../utils/dashboard'; +import toast from '../../../components/toast/toast'; +import { getParameterByName } from '../../../utils/url.ts'; + +function loadDeleteFolders(page, user, mediaFolders) { + ApiClient.getJSON(ApiClient.getUrl('Channels', { + SupportsMediaDeletion: true + })).then(function (channelsResult) { + let isChecked; + let checkedAttribute; + let html = ''; + + for (const folder of mediaFolders) { + isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; + checkedAttribute = isChecked ? ' checked="checked"' : ''; + html += ''; + } + + for (const folder of channelsResult.Items) { + isChecked = user.Policy.EnableContentDeletion || user.Policy.EnableContentDeletionFromFolders.indexOf(folder.Id) != -1; + checkedAttribute = isChecked ? ' checked="checked"' : ''; + html += ''; + } + + $('.deleteAccess', page).html(html).trigger('create'); + $('#chkEnableDeleteAllFolders', page).prop('checked', user.Policy.EnableContentDeletion); + }); +} + +function loadAuthProviders(page, user, providers) { + if (providers.length > 1) { + page.querySelector('.fldSelectLoginProvider').classList.remove('hide'); + } else { + page.querySelector('.fldSelectLoginProvider').classList.add('hide'); + } + + const currentProviderId = user.Policy.AuthenticationProviderId; + page.querySelector('.selectLoginProvider').innerHTML = providers.map(function (provider) { + const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : ''; + return ''; + }); +} + +function loadPasswordResetProviders(page, user, providers) { + if (providers.length > 1) { + page.querySelector('.fldSelectPasswordResetProvider').classList.remove('hide'); + } else { + page.querySelector('.fldSelectPasswordResetProvider').classList.add('hide'); + } + + const currentProviderId = user.Policy.PasswordResetProviderId; + page.querySelector('.selectPasswordResetProvider').innerHTML = providers.map(function (provider) { + const selected = provider.Id === currentProviderId || providers.length < 2 ? ' selected' : ''; + return ''; + }); +} + +function loadUser(page, user) { + ApiClient.getJSON(ApiClient.getUrl('Auth/Providers')).then(function (providers) { + loadAuthProviders(page, user, providers); + }); + ApiClient.getJSON(ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) { + loadPasswordResetProviders(page, user, providers); + }); + ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', { + IsHidden: false + })).then(function (folders) { + loadDeleteFolders(page, user, folders.Items); + }); + + if (user.Policy.IsDisabled) { + $('.disabledUserBanner', page).show(); + } else { + $('.disabledUserBanner', page).hide(); + } + + $('#txtUserName', page).prop('disabled', '').removeAttr('disabled'); + $('#fldConnectInfo', page).show(); + $('.lnkEditUserPreferences', page).attr('href', 'mypreferencesmenu.html?userId=' + user.Id); + libraryMenu.setTitle(user.Name); + page.querySelector('.username').innerHTML = user.Name; + $('#txtUserName', page).val(user.Name); + $('#chkIsAdmin', page).prop('checked', user.Policy.IsAdministrator); + $('#chkDisabled', page).prop('checked', user.Policy.IsDisabled); + $('#chkIsHidden', page).prop('checked', user.Policy.IsHidden); + $('#chkEnableCollectionManagement', page).prop('checked', user.Policy.chkEnableCollectionManagement); + $('#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', user.Policy.EnableRemoteAccess == null || user.Policy.EnableRemoteAccess); + $('#txtRemoteClientBitrateLimit', page).val(user.Policy.RemoteClientBitrateLimit / 1e6 || ''); + $('#txtLoginAttemptsBeforeLockout', page).val(user.Policy.LoginAttemptsBeforeLockout || '0'); + $('#txtMaxActiveSessions', page).val(user.Policy.MaxActiveSessions || '0'); + if (ApiClient.isMinServerVersion('10.6.0')) { + $('#selectSyncPlayAccess').val(user.Policy.SyncPlayAccess); + } + loading.hide(); +} + +function onSaveComplete() { + Dashboard.navigate('userprofiles.html'); + loading.hide(); + toast(globalize.translate('SettingsSaved')); +} + +function saveUser(user, page) { + user.Name = $('#txtUserName', page).val(); + 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.EnableCollectionManagement = $('#chkEnableCollectionManagement', page).is(':checked'); + user.Policy.ForceRemoteSourceTranscoding = $('#chkForceRemoteSourceTranscoding', page).is(':checked'); + user.Policy.EnableContentDownloading = $('#chkEnableDownloading', page).is(':checked'); + user.Policy.EnableRemoteAccess = $('#chkRemoteAccess', page).is(':checked'); + user.Policy.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', page).val() || '0'), 10); + user.Policy.LoginAttemptsBeforeLockout = parseInt($('#txtLoginAttemptsBeforeLockout', page).val() || '0', 10); + user.Policy.MaxActiveSessions = parseInt($('#txtMaxActiveSessions', page).val() || '0', 10); + user.Policy.AuthenticationProviderId = page.querySelector('.selectLoginProvider').value; + user.Policy.PasswordResetProviderId = page.querySelector('.selectPasswordResetProvider').value; + 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) { + return c.getAttribute('data-id'); + }); + if (ApiClient.isMinServerVersion('10.6.0')) { + user.Policy.SyncPlayAccess = page.querySelector('#selectSyncPlayAccess').value; + } + ApiClient.updateUser(user).then(function () { + ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + onSaveComplete(); + }); + }); +} + +function onSubmit() { + const page = $(this).parents('.page')[0]; + loading.show(); + getUser().then(function (result) { + saveUser(result, page); + }); + return false; +} + +function getUser() { + const userId = getParameterByName('userId'); + return ApiClient.getUser(userId); +} + +function loadData(page) { + loading.show(); + getUser().then(function (user) { + loadUser(page, user); + }); +} + +$(document).on('pageinit', '#editUserPage', function () { + $('.editUserProfileForm').off('submit', onSubmit).on('submit', onSubmit); + const page = this; + $('#chkEnableDeleteAllFolders', this).on('change', function () { + if (this.checked) { + $('.deleteAccess', page).hide(); + } else { + $('.deleteAccess', page).show(); + } + }); + ApiClient.getServerConfiguration().then(function (config) { + if (config.EnableRemoteAccess) { + page.querySelector('.fldRemoteAccess').classList.remove('hide'); + } else { + page.querySelector('.fldRemoteAccess').classList.add('hide'); + } + }); +}).on('pagebeforeshow', '#editUserPage', function () { + loadData(this); +}); + diff --git a/src/controllers/dashboard/users/userlibraryaccess.html b/src/controllers/dashboard/users/userlibraryaccess.html new file mode 100644 index 0000000000..bf6ba93408 --- /dev/null +++ b/src/controllers/dashboard/users/userlibraryaccess.html @@ -0,0 +1,68 @@ +
+ +
+
+ +
+
+

+ ${Help} +
+
+ + +
+ +
+

${HeaderLibraryAccess}

+ +
+
+
+
${LibraryAccessHelp}
+
+
+ +
+
+

${HeaderDeviceAccess}

+ +
+
+
+
${DeviceAccessHelp}
+
+
+
+
+
+ +
+
+
+
+
diff --git a/src/controllers/dashboard/users/userlibraryaccess.js b/src/controllers/dashboard/users/userlibraryaccess.js new file mode 100644 index 0000000000..e84638e8e0 --- /dev/null +++ b/src/controllers/dashboard/users/userlibraryaccess.js @@ -0,0 +1,184 @@ +import 'jquery'; +import loading from '../../../components/loading/loading'; +import libraryMenu from '../../../scripts/libraryMenu'; +import globalize from '../../../scripts/globalize'; +import Dashboard from '../../../utils/dashboard'; +import toast from '../../../components/toast/toast'; +import { getParameterByName } from '../../../utils/url.ts'; + +function triggerChange(select) { + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); + select.dispatchEvent(evt); +} + +function loadMediaFolders(page, user, mediaFolders) { + let html = ''; + html += '

' + globalize.translate('HeaderLibraries') + '

'; + html += '
'; + + for (let i = 0, length = mediaFolders.length; i < length; i++) { + const folder = mediaFolders[i]; + const isChecked = user.Policy.EnableAllFolders || user.Policy.EnabledFolders.indexOf(folder.Id) != -1; + const checkedAttribute = isChecked ? ' checked="checked"' : ''; + html += ''; + } + + html += '
'; + page.querySelector('.folderAccess').innerHTML = html; + const chkEnableAllFolders = page.querySelector('#chkEnableAllFolders'); + chkEnableAllFolders.checked = user.Policy.EnableAllFolders; + triggerChange(chkEnableAllFolders); +} + +function loadChannels(page, user, channels) { + let html = ''; + html += '

' + globalize.translate('Channels') + '

'; + html += '
'; + + for (let i = 0, length = channels.length; i < length; i++) { + const folder = channels[i]; + const isChecked = user.Policy.EnableAllChannels || user.Policy.EnabledChannels.indexOf(folder.Id) != -1; + const checkedAttribute = isChecked ? ' checked="checked"' : ''; + html += ''; + } + + html += '
'; + $('.channelAccess', page).show().html(html); + + if (channels.length) { + $('.channelAccessContainer', page).show(); + } else { + $('.channelAccessContainer', page).hide(); + } + + const chkEnableAllChannels = page.querySelector('#chkEnableAllChannels'); + chkEnableAllChannels.checked = user.Policy.EnableAllChannels; + triggerChange(chkEnableAllChannels); +} + +function loadDevices(page, user, devices) { + let html = ''; + html += '

' + globalize.translate('HeaderDevices') + '

'; + html += '
'; + + for (let i = 0, length = devices.length; i < length; i++) { + const device = devices[i]; + const checkedAttribute = user.Policy.EnableAllDevices || user.Policy.EnabledDevices.indexOf(device.Id) != -1 ? ' checked="checked"' : ''; + html += ''; + } + + html += '
'; + $('.deviceAccess', page).show().html(html); + const chkEnableAllDevices = page.querySelector('#chkEnableAllDevices'); + chkEnableAllDevices.checked = user.Policy.EnableAllDevices; + triggerChange(chkEnableAllDevices); + + if (user.Policy.IsAdministrator) { + page.querySelector('.deviceAccessContainer').classList.add('hide'); + } else { + page.querySelector('.deviceAccessContainer').classList.remove('hide'); + } +} + +function loadUser(page, user, loggedInUser, mediaFolders, channels, devices) { + page.querySelector('.username').innerHTML = user.Name; + libraryMenu.setTitle(user.Name); + loadChannels(page, user, channels); + loadMediaFolders(page, user, mediaFolders); + loadDevices(page, user, devices); + loading.hide(); +} + +function onSaveComplete() { + loading.hide(); + toast(globalize.translate('SettingsSaved')); +} + +function saveUser(user, page) { + 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).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).is(':checked'); + user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : $('.chkDevice', page).get().filter(function (c) { + return c.checked; + }).map(function (c) { + return c.getAttribute('data-id'); + }); + user.Policy.BlockedChannels = null; + user.Policy.BlockedMediaFolders = null; + ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + onSaveComplete(); + }); +} + +function onSubmit() { + const page = $(this).parents('.page'); + loading.show(); + const userId = getParameterByName('userId'); + ApiClient.getUser(userId).then(function (result) { + saveUser(result, page); + }); + return false; +} + +$(document).on('pageinit', '#userLibraryAccessPage', function () { + const page = this; + $('#chkEnableAllDevices', page).on('change', function () { + if (this.checked) { + $('.deviceAccessListContainer', page).hide(); + } else { + $('.deviceAccessListContainer', page).show(); + } + }); + $('#chkEnableAllChannels', page).on('change', function () { + if (this.checked) { + $('.channelAccessListContainer', page).hide(); + } else { + $('.channelAccessListContainer', page).show(); + } + }); + page.querySelector('#chkEnableAllFolders').addEventListener('change', function () { + if (this.checked) { + page.querySelector('.folderAccessListContainer').classList.add('hide'); + } else { + page.querySelector('.folderAccessListContainer').classList.remove('hide'); + } + }); + $('.userLibraryAccessForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#userLibraryAccessPage', function () { + const page = this; + loading.show(); + let promise1; + const userId = getParameterByName('userId'); + + if (userId) { + promise1 = ApiClient.getUser(userId); + } else { + const deferred = $.Deferred(); + deferred.resolveWith(null, [{ + Configuration: {} + }]); + promise1 = deferred.promise(); + } + + const promise2 = Dashboard.getCurrentUser(); + const promise4 = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', { + IsHidden: false + })); + const promise5 = ApiClient.getJSON(ApiClient.getUrl('Channels')); + const promise6 = ApiClient.getJSON(ApiClient.getUrl('Devices')); + Promise.all([promise1, promise2, promise4, promise5, promise6]).then(function (responses) { + loadUser(page, responses[0], responses[1], responses[2].Items, responses[3].Items, responses[4].Items); + }); +}); + diff --git a/src/controllers/dashboard/users/usernew.html b/src/controllers/dashboard/users/usernew.html new file mode 100644 index 0000000000..5d50ede80a --- /dev/null +++ b/src/controllers/dashboard/users/usernew.html @@ -0,0 +1,62 @@ +
+
+
+
+
+
+

${ButtonAddUser}

+ ${Help} +
+ +
+ +
+ +
+ +
+
+ +
+

${HeaderLibraryAccess}

+
+ +
${LibraryAccessHelp}
+
+
+
+
+
+
+ + + +
+ + + +
+
+
+
+
diff --git a/src/controllers/dashboard/users/usernew.js b/src/controllers/dashboard/users/usernew.js new file mode 100644 index 0000000000..9477506aca --- /dev/null +++ b/src/controllers/dashboard/users/usernew.js @@ -0,0 +1,128 @@ +import 'jquery'; +import loading from '../../../components/loading/loading'; +import globalize from '../../../scripts/globalize'; +import '../../../elements/emby-checkbox/emby-checkbox'; +import Dashboard from '../../../utils/dashboard'; +import toast from '../../../components/toast/toast'; + +function loadMediaFolders(page, mediaFolders) { + let html = ''; + html += '

' + globalize.translate('HeaderLibraries') + '

'; + html += '
'; + + for (let i = 0; i < mediaFolders.length; i++) { + const folder = mediaFolders[i]; + html += ''; + } + + html += '
'; + $('.folderAccess', page).html(html).trigger('create'); + $('#chkEnableAllFolders', page).prop('checked', false); +} + +function loadChannels(page, channels) { + let html = ''; + html += '

' + globalize.translate('Channels') + '

'; + html += '
'; + + for (let i = 0; i < channels.length; i++) { + const folder = channels[i]; + html += ''; + } + + html += '
'; + $('.channelAccess', page).show().html(html).trigger('create'); + + if (channels.length) { + $('.channelAccessContainer', page).show(); + } else { + $('.channelAccessContainer', page).hide(); + } + + $('#chkEnableAllChannels', page).prop('checked', false); +} + +function loadUser(page) { + $('#txtUsername', page).val(''); + $('#txtPassword', page).val(''); + loading.show(); + const promiseFolders = ApiClient.getJSON(ApiClient.getUrl('Library/MediaFolders', { + IsHidden: false + })); + const promiseChannels = ApiClient.getJSON(ApiClient.getUrl('Channels')); + Promise.all([promiseFolders, promiseChannels]).then(function (responses) { + loadMediaFolders(page, responses[0].Items); + loadChannels(page, responses[1].Items); + loading.hide(); + }); +} + +function saveUser(page) { + const _user = { + Name: $('#txtUsername', page).val(), + Password: $('#txtPassword', page).val() + }; + ApiClient.createUser(_user).then(function (user) { + user.Policy.EnableAllFolders = $('#chkEnableAllFolders', page).is(':checked'); + user.Policy.EnabledFolders = []; + + if (!user.Policy.EnableAllFolders) { + user.Policy.EnabledFolders = $('.chkFolder', page).get().filter(function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-id'); + }); + } + + user.Policy.EnableAllChannels = $('#chkEnableAllChannels', page).is(':checked'); + user.Policy.EnabledChannels = []; + + if (!user.Policy.EnableAllChannels) { + user.Policy.EnabledChannels = $('.chkChannel', page).get().filter(function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-id'); + }); + } + + ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + Dashboard.navigate('useredit.html?userId=' + user.Id); + }); + }, function () { + toast(globalize.translate('ErrorDefault')); + loading.hide(); + }); +} + +function onSubmit() { + const page = $(this).parents('.page')[0]; + loading.show(); + saveUser(page); + return false; +} + +function loadData(page) { + loadUser(page); +} + +$(document).on('pageinit', '#newUserPage', function () { + const page = this; + $('#chkEnableAllChannels', page).on('change', function () { + if (this.checked) { + $('.channelAccessListContainer', page).hide(); + } else { + $('.channelAccessListContainer', page).show(); + } + }); + $('#chkEnableAllFolders', page).on('change', function () { + if (this.checked) { + $('.folderAccessListContainer', page).hide(); + } else { + $('.folderAccessListContainer', page).show(); + } + }); + $('.newUserProfileForm').off('submit', onSubmit).on('submit', onSubmit); +}).on('pageshow', '#newUserPage', function () { + loadData(this); +}); + diff --git a/src/controllers/dashboard/users/userparentalcontrol.html b/src/controllers/dashboard/users/userparentalcontrol.html new file mode 100644 index 0000000000..5b58047c60 --- /dev/null +++ b/src/controllers/dashboard/users/userparentalcontrol.html @@ -0,0 +1,60 @@ +
+
+
+
+
+

+ ${Help} +
+
+ + + +
+
+ +
${MaxParentalRatingHelp}
+
+ +
+
+
+ +
+ +
+
+

${LabelBlockContentWithTags}

+ +
+
+
+ +
+
+

${HeaderAccessSchedule}

+ +
+ +

${HeaderAccessScheduleHelp}

+
+
+ +
+ +
+
+
+
+
diff --git a/src/controllers/dashboard/users/userparentalcontrol.js b/src/controllers/dashboard/users/userparentalcontrol.js new file mode 100644 index 0000000000..0b527e09ef --- /dev/null +++ b/src/controllers/dashboard/users/userparentalcontrol.js @@ -0,0 +1,278 @@ +import 'jquery'; +import datetime from '../../../scripts/datetime'; +import loading from '../../../components/loading/loading'; +import libraryMenu from '../../../scripts/libraryMenu'; +import globalize from '../../../scripts/globalize'; +import '../../../components/listview/listview.scss'; +import '../../../elements/emby-button/paper-icon-button-light'; +import toast from '../../../components/toast/toast'; +import { getParameterByName } from '../../../utils/url.ts'; + +function populateRatings(allParentalRatings, page) { + let html = ''; + html += ""; + let rating; + const ratings = []; + + for (let i = 0, length = allParentalRatings.length; i < length; i++) { + rating = allParentalRatings[i]; + if (ratings.length) { + const lastRating = ratings[ratings.length - 1]; + + if (lastRating.Value === rating.Value) { + lastRating.Name += '/' + rating.Name; + continue; + } + } + + ratings.push({ + Name: rating.Name, + Value: rating.Value + }); + } + + for (let i = 0, length = ratings.length; i < length; i++) { + rating = ratings[i]; + html += "'; + } + + $('#selectMaxParentalRating', page).html(html); +} + +function loadUnratedItems(page, user) { + const items = [{ + name: globalize.translate('Books'), + value: 'Book' + }, { + name: globalize.translate('Channels'), + value: 'ChannelContent' + }, { + name: globalize.translate('LiveTV'), + value: 'LiveTvChannel' + }, { + name: globalize.translate('Movies'), + value: 'Movie' + }, { + name: globalize.translate('Music'), + value: 'Music' + }, { + name: globalize.translate('Trailers'), + value: 'Trailer' + }, { + name: globalize.translate('Shows'), + value: 'Series' + }]; + let html = ''; + html += '

' + globalize.translate('HeaderBlockItemsWithNoRating') + '

'; + html += '
'; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + const checkedAttribute = user.Policy.BlockUnratedItems.indexOf(item.value) != -1 ? ' checked="checked"' : ''; + html += ''; + } + + html += '
'; + $('.blockUnratedItems', page).html(html).trigger('create'); +} + +function loadUser(page, user, allParentalRatings) { + page.querySelector('.username').innerHTML = user.Name; + libraryMenu.setTitle(user.Name); + loadUnratedItems(page, user); + loadBlockedTags(page, user.Policy.BlockedTags); + populateRatings(allParentalRatings, page); + let ratingValue = ''; + + if (user.Policy.MaxParentalRating) { + for (let i = 0, length = allParentalRatings.length; i < length; i++) { + const rating = allParentalRatings[i]; + + if (user.Policy.MaxParentalRating >= rating.Value) { + ratingValue = rating.Value; + } + } + } + + $('#selectMaxParentalRating', page).val(ratingValue); + + if (user.Policy.IsAdministrator) { + $('.accessScheduleSection', page).hide(); + } else { + $('.accessScheduleSection', page).show(); + } + + renderAccessSchedule(page, user.Policy.AccessSchedules || []); + loading.hide(); +} + +function loadBlockedTags(page, tags) { + let html = tags.map(function (h) { + let li = '
'; + li += '
'; + li += '

'; + li += h; + li += '

'; + li += '
'; + li += ''; + li += '
'; + return li; + }).join(''); + + if (html) { + html = '
' + html + '
'; + } + + const blockedTags = page.querySelector('.blockedTags'); + blockedTags.innerHTML = html; + + for (const btnDeleteTag of blockedTags.querySelectorAll('.btnDeleteTag')) { + btnDeleteTag.addEventListener('click', function () { + const tag = this.getAttribute('data-tag'); + const newTags = tags.filter(function (t) { + return t != tag; + }); + loadBlockedTags(page, newTags); + }); + } +} + +function deleteAccessSchedule(page, schedules, index) { + schedules.splice(index, 1); + renderAccessSchedule(page, schedules); +} + +function renderAccessSchedule(page, schedules) { + let html = ''; + let index = 0; + html += schedules.map(function (a) { + let itemHtml = ''; + itemHtml += '
'; + itemHtml += '
'; + itemHtml += '

'; + itemHtml += globalize.translate('Option' + a.DayOfWeek); + itemHtml += '

'; + itemHtml += '
' + getDisplayTime(a.StartHour) + ' - ' + getDisplayTime(a.EndHour) + '
'; + itemHtml += '
'; + itemHtml += ''; + itemHtml += '
'; + index++; + return itemHtml; + }).join(''); + const accessScheduleList = page.querySelector('.accessScheduleList'); + accessScheduleList.innerHTML = html; + $('.btnDelete', accessScheduleList).on('click', function () { + deleteAccessSchedule(page, schedules, parseInt(this.getAttribute('data-index'), 10)); + }); +} + +function onSaveComplete() { + loading.hide(); + toast(globalize.translate('SettingsSaved')); +} + +function saveUser(user, page) { + user.Policy.MaxParentalRating = $('#selectMaxParentalRating', page).val() || null; + user.Policy.BlockUnratedItems = $('.chkUnratedItem', page).get().filter(function (i) { + return i.checked; + }).map(function (i) { + return i.getAttribute('data-itemtype'); + }); + user.Policy.AccessSchedules = getSchedulesFromPage(page); + user.Policy.BlockedTags = getBlockedTagsFromPage(page); + ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () { + onSaveComplete(); + }); +} + +function getDisplayTime(hours) { + let minutes = 0; + const pct = hours % 1; + + if (pct) { + minutes = parseInt(60 * pct, 10); + } + + return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); +} + +function showSchedulePopup(page, schedule, index) { + schedule = schedule || {}; + import('../../../components/accessSchedule/accessSchedule').then(({ default: accessschedule }) => { + accessschedule.show({ + schedule: schedule + }).then(function (updatedSchedule) { + const schedules = getSchedulesFromPage(page); + + if (index == -1) { + index = schedules.length; + } + + schedules[index] = updatedSchedule; + renderAccessSchedule(page, schedules); + }); + }); +} + +function getSchedulesFromPage(page) { + return $('.liSchedule', page).map(function () { + return { + DayOfWeek: this.getAttribute('data-day'), + StartHour: this.getAttribute('data-start'), + EndHour: this.getAttribute('data-end') + }; + }).get(); +} + +function getBlockedTagsFromPage(page) { + return $('.blockedTag', page).map(function () { + return this.getAttribute('data-tag'); + }).get(); +} + +function showBlockedTagPopup(page) { + import('../../../components/prompt/prompt').then(({ default: prompt }) => { + prompt({ + label: globalize.translate('LabelTag') + }).then(function (value) { + const tags = getBlockedTagsFromPage(page); + + if (tags.indexOf(value) == -1) { + tags.push(value); + loadBlockedTags(page, tags); + } + }); + }); +} + +window.UserParentalControlPage = { + onSubmit: function () { + const page = $(this).parents('.page'); + loading.show(); + const userId = getParameterByName('userId'); + ApiClient.getUser(userId).then(function (result) { + saveUser(result, page); + }); + return false; + } +}; +$(document).on('pageinit', '#userParentalControlPage', function () { + const page = this; + $('.btnAddSchedule', page).on('click', function () { + showSchedulePopup(page, {}, -1); + }); + $('.btnAddBlockedTag', page).on('click', function () { + showBlockedTagPopup(page); + }); + $('.userParentalControlForm').off('submit', UserParentalControlPage.onSubmit).on('submit', UserParentalControlPage.onSubmit); +}).on('pageshow', '#userParentalControlPage', function () { + const page = this; + loading.show(); + const userId = getParameterByName('userId'); + const promise1 = ApiClient.getUser(userId); + const promise2 = ApiClient.getParentalRatings(); + Promise.all([promise1, promise2]).then(function (responses) { + loadUser(page, responses[0], responses[1]); + }); +}); + diff --git a/src/controllers/dashboard/users/userpassword.html b/src/controllers/dashboard/users/userpassword.html new file mode 100644 index 0000000000..897f0e7bd5 --- /dev/null +++ b/src/controllers/dashboard/users/userpassword.html @@ -0,0 +1,72 @@ +
+
+
+
+
+

+ ${Help} +
+
+ + + +
+
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+
+ ${HeaderEasyPinCode} +
+
+
${EasyPasswordHelp}
+
+
+ +
+
+
+ +
${LabelInNetworkSignInWithEasyPasswordHelp}
+
+
+ + +
+
+
+
+
+
+
diff --git a/src/controllers/dashboard/users/userpasswordpage.js b/src/controllers/dashboard/users/userpasswordpage.js new file mode 100644 index 0000000000..4171c55d6e --- /dev/null +++ b/src/controllers/dashboard/users/userpasswordpage.js @@ -0,0 +1,179 @@ +import loading from '../../../components/loading/loading'; +import libraryMenu from '../../../scripts/libraryMenu'; +import globalize from '../../../scripts/globalize'; +import '../../../elements/emby-button/emby-button'; +import Dashboard from '../../../utils/dashboard'; +import toast from '../../../components/toast/toast'; +import confirm from '../../../components/confirm/confirm'; + +function loadUser(page, params) { + const userid = params.userId; + ApiClient.getUser(userid).then(function (user) { + Dashboard.getCurrentUser().then(function (loggedInUser) { + libraryMenu.setTitle(user.Name); + page.querySelector('.username').innerText = user.Name; + let showPasswordSection = true; + let showLocalAccessSection = false; + + if (user.ConnectLinkType == 'Guest') { + page.querySelector('.localAccessSection').classList.add('hide'); + showPasswordSection = false; + } else if (user.HasConfiguredPassword) { + page.querySelector('#btnResetPassword').classList.remove('hide'); + page.querySelector('#fldCurrentPassword').classList.remove('hide'); + showLocalAccessSection = true; + } else { + page.querySelector('#btnResetPassword').classList.add('hide'); + page.querySelector('#fldCurrentPassword').classList.add('hide'); + } + + if (showPasswordSection && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { + page.querySelector('.passwordSection').classList.remove('hide'); + } else { + page.querySelector('.passwordSection').classList.add('hide'); + } + + if (showLocalAccessSection && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { + page.querySelector('.localAccessSection').classList.remove('hide'); + } else { + page.querySelector('.localAccessSection').classList.add('hide'); + } + + const txtEasyPassword = page.querySelector('#txtEasyPassword'); + txtEasyPassword.value = ''; + + if (user.HasConfiguredEasyPassword) { + txtEasyPassword.placeholder = '******'; + page.querySelector('#btnResetEasyPassword').classList.remove('hide'); + } else { + txtEasyPassword.removeAttribute('placeholder'); + txtEasyPassword.placeholder = ''; + page.querySelector('#btnResetEasyPassword').classList.add('hide'); + } + + page.querySelector('.chkEnableLocalEasyPassword').checked = user.Configuration.EnableLocalPassword; + + import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + }); + page.querySelector('#txtCurrentPassword').value = ''; + page.querySelector('#txtNewPassword').value = ''; + page.querySelector('#txtNewPasswordConfirm').value = ''; +} + +export default function (view, params) { + function saveEasyPassword() { + const userId = params.userId; + const easyPassword = view.querySelector('#txtEasyPassword').value; + + if (easyPassword) { + ApiClient.updateEasyPassword(userId, easyPassword).then(function () { + onEasyPasswordSaved(userId); + }); + } else { + onEasyPasswordSaved(userId); + } + } + + function onEasyPasswordSaved(userId) { + ApiClient.getUser(userId).then(function (user) { + user.Configuration.EnableLocalPassword = view.querySelector('.chkEnableLocalEasyPassword').checked; + ApiClient.updateUserConfiguration(user.Id, user.Configuration).then(function () { + loading.hide(); + toast(globalize.translate('SettingsSaved')); + + loadUser(view, params); + }); + }); + } + + function savePassword() { + const userId = params.userId; + let currentPassword = view.querySelector('#txtCurrentPassword').value; + const newPassword = view.querySelector('#txtNewPassword').value; + + if (view.querySelector('#fldCurrentPassword').classList.contains('hide')) { + // Firefox does not respect autocomplete=off, so clear it if the field is supposed to be hidden (and blank) + // This should only happen when user.HasConfiguredPassword is false, but this information is not passed on + currentPassword = ''; + } + + ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () { + loading.hide(); + toast(globalize.translate('PasswordSaved')); + + loadUser(view, params); + }, function () { + loading.hide(); + Dashboard.alert({ + title: globalize.translate('HeaderLoginFailure'), + message: globalize.translate('MessageInvalidUser') + }); + }); + } + + function onSubmit(e) { + const form = this; + + if (form.querySelector('#txtNewPassword').value != form.querySelector('#txtNewPasswordConfirm').value) { + toast(globalize.translate('PasswordMatchError')); + } else { + loading.show(); + savePassword(); + } + + e.preventDefault(); + return false; + } + + function onLocalAccessSubmit(e) { + loading.show(); + saveEasyPassword(); + e.preventDefault(); + return false; + } + + function resetPassword() { + const msg = globalize.translate('PasswordResetConfirmation'); + confirm(msg, globalize.translate('ResetPassword')).then(function () { + const userId = params.userId; + loading.show(); + ApiClient.resetUserPassword(userId).then(function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('PasswordResetComplete'), + title: globalize.translate('ResetPassword') + }); + loadUser(view, params); + }); + }); + } + + function resetEasyPassword() { + const msg = globalize.translate('PinCodeResetConfirmation'); + + confirm(msg, globalize.translate('HeaderPinCodeReset')).then(function () { + const userId = params.userId; + loading.show(); + ApiClient.resetEasyPassword(userId).then(function () { + loading.hide(); + Dashboard.alert({ + message: globalize.translate('PinCodeResetComplete'), + title: globalize.translate('HeaderPinCodeReset') + }); + loadUser(view, params); + }); + }); + } + + view.querySelector('.updatePasswordForm').addEventListener('submit', onSubmit); + view.querySelector('.localAccessForm').addEventListener('submit', onLocalAccessSubmit); + view.querySelector('#btnResetEasyPassword').addEventListener('click', resetEasyPassword); + view.querySelector('#btnResetPassword').addEventListener('click', resetPassword); + view.addEventListener('viewshow', function () { + loadUser(view, params); + }); +} + diff --git a/src/controllers/dashboard/users/userprofiles.html b/src/controllers/dashboard/users/userprofiles.html new file mode 100644 index 0000000000..9e2908266b --- /dev/null +++ b/src/controllers/dashboard/users/userprofiles.html @@ -0,0 +1,16 @@ +
+
+
+
+
+

${HeaderUsers}

+ + ${Help} +
+
+
+
+
+
diff --git a/src/controllers/dashboard/users/userprofilespage.js b/src/controllers/dashboard/users/userprofilespage.js new file mode 100644 index 0000000000..59d61a443f --- /dev/null +++ b/src/controllers/dashboard/users/userprofilespage.js @@ -0,0 +1,184 @@ +import loading from '../../../components/loading/loading'; +import dom from '../../../scripts/dom'; +import globalize from '../../../scripts/globalize'; +import { formatDistanceToNow } from 'date-fns'; +import { getLocaleWithSuffix } from '../../../utils/dateFnsLocale.ts'; +import '../../../elements/emby-button/paper-icon-button-light'; +import '../../../components/cardbuilder/card.scss'; +import '../../../elements/emby-button/emby-button'; +import '../../../components/indicators/indicators.scss'; +import '../../../styles/flexstyles.scss'; +import Dashboard, { pageIdOn } from '../../../utils/dashboard'; +import confirm from '../../../components/confirm/confirm'; +import cardBuilder from '../../../components/cardbuilder/cardBuilder'; + +function deleteUser(page, id) { + const msg = globalize.translate('DeleteUserConfirmation'); + + confirm({ + title: globalize.translate('DeleteUser'), + text: msg, + confirmText: globalize.translate('Delete'), + primary: 'delete' + }).then(function () { + loading.show(); + ApiClient.deleteUser(id).then(function () { + loadData(page); + }); + }); +} + +function showUserMenu(elem) { + const card = dom.parentWithClass(elem, 'card'); + const page = dom.parentWithClass(card, 'page'); + const userId = card.getAttribute('data-userid'); + const menuItems = []; + menuItems.push({ + name: globalize.translate('ButtonOpen'), + id: 'open', + icon: 'mode_edit' + }); + menuItems.push({ + name: globalize.translate('ButtonLibraryAccess'), + id: 'access', + icon: 'lock' + }); + menuItems.push({ + name: globalize.translate('ButtonParentalControl'), + id: 'parentalcontrol', + icon: 'person' + }); + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete', + icon: 'delete' + }); + + import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { + actionsheet.show({ + items: menuItems, + positionTo: card, + callback: function (id) { + switch (id) { + case 'open': + Dashboard.navigate('useredit.html?userId=' + userId); + break; + + case 'access': + Dashboard.navigate('userlibraryaccess.html?userId=' + userId); + break; + + case 'parentalcontrol': + Dashboard.navigate('userparentalcontrol.html?userId=' + userId); + break; + + case 'delete': + deleteUser(page, userId); + } + } + }); + }); +} + +function getUserHtml(user) { + let html = ''; + let cssClass = 'card squareCard scalableCard squareCard-scalable'; + + if (user.Policy.IsDisabled) { + cssClass += ' grayscale'; + } + + html += "
"; + html += '
'; + html += '
'; + html += '
'; + html += ``; + let imgUrl; + + if (user.PrimaryImageTag) { + imgUrl = ApiClient.getUserImageUrl(user.Id, { + width: 300, + tag: user.PrimaryImageTag, + type: 'Primary' + }); + } + + let imageClass = 'cardImage'; + + if (user.Policy.IsDisabled) { + imageClass += ' disabledUser'; + } + + if (imgUrl) { + html += ''; + html += '
'; + html += '
'; + html += '
'; + html += user.Name; + html += '
'; + html += ''; + html += '
'; + html += '
'; + const lastSeen = getLastSeenText(user.LastActivityDate); + html += lastSeen != '' ? lastSeen : ' '; + html += '
'; + html += '
'; + html += '
'; + return html + '
'; +} +// FIXME: It seems that, sometimes, server sends date in the future, so date-fns displays messages like 'in less than a minute'. We should fix +// how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences +function getLastSeenText(lastActivityDate) { + const localeWithSuffix = getLocaleWithSuffix(); + + if (lastActivityDate) { + return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), localeWithSuffix)); + } + + return ''; +} + +function getUserSectionHtml(users) { + return users.map(function (u__q) { + return getUserHtml(u__q); + }).join(''); +} + +function renderUsers(page, users) { + page.querySelector('.localUsers').innerHTML = getUserSectionHtml(users); +} + +function loadData(page) { + loading.show(); + ApiClient.getUsers().then(function (users) { + renderUsers(page, users); + loading.hide(); + }); +} + +pageIdOn('pageinit', 'userProfilesPage', function () { + const page = this; + page.querySelector('.btnAddUser').addEventListener('click', function() { + Dashboard.navigate('usernew.html'); + }); + page.querySelector('.localUsers').addEventListener('click', function (e__e) { + const btnUserMenu = dom.parentWithClass(e__e.target, 'btnUserMenu'); + + if (btnUserMenu) { + showUserMenu(btnUserMenu); + } + }); +}); + +pageIdOn('pagebeforeshow', 'userProfilesPage', function () { + loadData(this); +}); + diff --git a/src/controllers/favorites.js b/src/controllers/favorites.js index 88114385c9..97ce3eaf86 100644 --- a/src/controllers/favorites.js +++ b/src/controllers/favorites.js @@ -9,265 +9,263 @@ import '../elements/emby-itemscontainer/emby-itemscontainer'; import '../elements/emby-scroller/emby-scroller'; import ServerConnections from '../components/ServerConnections'; -/* eslint-disable indent */ +function enableScrollX() { + return true; +} - function enableScrollX() { - return true; - } +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +function getPosterShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} - function getPosterShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; - } +function getSections() { + return [{ + name: 'Movies', + types: 'Movie', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Shows', + types: 'Series', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Episodes', + types: 'Episode', + shape: getThumbShape(), + preferThumb: false, + showTitle: true, + showParentTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Videos', + types: 'Video', + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Collections', + types: 'BoxSet', + shape: getPosterShape(), + showTitle: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }, { + name: 'Playlists', + types: 'Playlist', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'People', + types: 'Person', + shape: getPosterShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Artists', + types: 'MusicArtist', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: false, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Albums', + types: 'MusicAlbum', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayPlayButton: true, + coverImage: true + }, { + name: 'Songs', + types: 'Audio', + shape: getSquareShape(), + preferThumb: false, + showTitle: true, + overlayText: false, + showParentTitle: true, + centerText: true, + overlayMoreButton: true, + action: 'instantmix', + coverImage: true + }, { + name: 'Books', + types: 'Book', + shape: getPosterShape(), + showTitle: true, + showYear: true, + overlayPlayButton: true, + overlayText: false, + centerText: true + }]; +} - function getSections() { - return [{ - name: 'Movies', - types: 'Movie', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Shows', - types: 'Series', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Episodes', - types: 'Episode', - shape: getThumbShape(), - preferThumb: false, - showTitle: true, - showParentTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Videos', - types: 'Video', - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Collections', - types: 'BoxSet', - shape: getPosterShape(), - showTitle: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }, { - name: 'Playlists', - types: 'Playlist', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'People', - types: 'Person', - shape: getPosterShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Artists', - types: 'MusicArtist', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: false, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Albums', - types: 'MusicAlbum', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayPlayButton: true, - coverImage: true - }, { - name: 'Songs', - types: 'Audio', - shape: getSquareShape(), - preferThumb: false, - showTitle: true, - overlayText: false, - showParentTitle: true, - centerText: true, - overlayMoreButton: true, - action: 'instantmix', - coverImage: true - }, { - name: 'Books', - types: 'Book', - shape: getPosterShape(), - showTitle: true, - showYear: true, - overlayPlayButton: true, - overlayText: false, - centerText: true - }]; - } - - function getFetchDataFn(section) { - return function () { - const apiClient = this.apiClient; - const options = { - SortBy: 'SeriesName,SortName', - SortOrder: 'Ascending', - Filters: 'IsFavorite', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - CollapseBoxSetItems: false, - ExcludeLocationTypes: 'Virtual', - EnableTotalRecordCount: false - }; - options.Limit = 20; - const userId = apiClient.getCurrentUserId(); - - if (section.types === 'MusicArtist') { - return apiClient.getArtists(userId, options); - } - - if (section.types === 'Person') { - return apiClient.getPeople(userId, options); - } - - options.IncludeItemTypes = section.types; - return apiClient.getItems(userId, options); +function getFetchDataFn(section) { + return function () { + const apiClient = this.apiClient; + const options = { + SortBy: 'SeriesName,SortName', + SortOrder: 'Ascending', + Filters: 'IsFavorite', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + CollapseBoxSetItems: false, + ExcludeLocationTypes: 'Virtual', + EnableTotalRecordCount: false }; - } + options.Limit = 20; + const userId = apiClient.getCurrentUserId(); - function getRouteUrl(section, serverId) { - return appRouter.getRouteUrl('list', { - serverId: serverId, - itemTypes: section.types, - isFavorite: true + if (section.types === 'MusicArtist') { + return apiClient.getArtists(userId, options); + } + + if (section.types === 'Person') { + return apiClient.getPeople(userId, options); + } + + options.IncludeItemTypes = section.types; + return apiClient.getItems(userId, options); + }; +} + +function getRouteUrl(section, serverId) { + return appRouter.getRouteUrl('list', { + serverId: serverId, + itemTypes: section.types, + isFavorite: true + }); +} + +function getItemsHtmlFn(section) { + return function (items) { + let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; + cardLayout = false; + const serverId = this.apiClient.serverId(); + const leadingButtons = layoutManager.tv ? [{ + name: globalize.translate('All'), + id: 'more', + icon: 'favorite', + routeUrl: getRouteUrl(section, serverId) + }] : null; + let lines = 0; + + if (section.showTitle) { + lines++; + } + + if (section.showYear) { + lines++; + } + + if (section.showParentTitle) { + lines++; + } + + return cardBuilder.getCardsHtml({ + items: items, + preferThumb: section.preferThumb, + shape: section.shape, + centerText: section.centerText && !cardLayout, + overlayText: section.overlayText !== false, + showTitle: section.showTitle, + showYear: section.showYear, + showParentTitle: section.showParentTitle, + scalable: true, + coverImage: section.coverImage, + overlayPlayButton: section.overlayPlayButton, + overlayMoreButton: section.overlayMoreButton && !cardLayout, + action: section.action, + allowBottomPadding: !enableScrollX(), + cardLayout: cardLayout, + leadingButtons: leadingButtons, + lines: lines }); - } + }; +} - function getItemsHtmlFn(section) { - return function (items) { - let cardLayout = appHost.preferVisualCards && section.autoCardLayout && section.showTitle; - cardLayout = false; - const serverId = this.apiClient.serverId(); - const leadingButtons = layoutManager.tv ? [{ - name: globalize.translate('All'), - id: 'more', - icon: 'favorite', - routeUrl: getRouteUrl(section, serverId) - }] : null; - let lines = 0; +function createSections(instance, elem, apiClient) { + const sections = getSections(); + let html = ''; - if (section.showTitle) { - lines++; - } + for (const section of sections) { + let sectionClass = 'verticalSection'; - if (section.showYear) { - lines++; - } - - if (section.showParentTitle) { - lines++; - } - - return cardBuilder.getCardsHtml({ - items: items, - preferThumb: section.preferThumb, - shape: section.shape, - centerText: section.centerText && !cardLayout, - overlayText: section.overlayText !== false, - showTitle: section.showTitle, - showYear: section.showYear, - showParentTitle: section.showParentTitle, - scalable: true, - coverImage: section.coverImage, - overlayPlayButton: section.overlayPlayButton, - overlayMoreButton: section.overlayMoreButton && !cardLayout, - action: section.action, - allowBottomPadding: !enableScrollX(), - cardLayout: cardLayout, - leadingButtons: leadingButtons, - lines: lines - }); - }; - } - - function createSections(instance, elem, apiClient) { - const sections = getSections(); - let html = ''; - - for (const section of sections) { - let sectionClass = 'verticalSection'; - - if (!section.showTitle) { - sectionClass += ' verticalSection-extrabottompadding'; - } - - html += '
'; - html += '
'; - - if (layoutManager.tv) { - html += '

' + globalize.translate(section.name) + '

'; - } else { - html += ''; - html += '

'; - html += globalize.translate(section.name); - html += '

'; - html += ''; - html += '
'; - } - - html += '
'; - html += '
'; - html += '
'; + if (!section.showTitle) { + sectionClass += ' verticalSection-extrabottompadding'; } - elem.innerHTML = html; - window.CustomElements.upgradeSubtree(elem); + html += '
'; + html += '
'; - const elems = elem.querySelectorAll('.itemsContainer'); - - for (let i = 0, length = elems.length; i < length; i++) { - const itemsContainer = elems[i]; - itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); - itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); - itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); + if (layoutManager.tv) { + html += '

' + globalize.translate(section.name) + '

'; + } else { + html += ''; + html += '

'; + html += globalize.translate(section.name); + html += '

'; + html += ''; + html += '
'; } + + html += '
'; + html += '
'; + html += '
'; } + elem.innerHTML = html; + window.CustomElements.upgradeSubtree(elem); + + const elems = elem.querySelectorAll('.itemsContainer'); + + for (let i = 0, length = elems.length; i < length; i++) { + const itemsContainer = elems[i]; + itemsContainer.fetchData = getFetchDataFn(sections[i]).bind(instance); + itemsContainer.getItemsHtml = getItemsHtmlFn(sections[i]).bind(instance); + itemsContainer.parentContainer = dom.parentWithClass(itemsContainer, 'verticalSection'); + } +} + class FavoritesTab { constructor(view, params) { this.view = view; @@ -318,4 +316,3 @@ class FavoritesTab { export default FavoritesTab; -/* eslint-enable indent */ diff --git a/src/controllers/home.html b/src/controllers/home.html new file mode 100644 index 0000000000..240caef6c6 --- /dev/null +++ b/src/controllers/home.html @@ -0,0 +1,9 @@ +
+ +
+
+
+
+
+
+
diff --git a/src/controllers/home.js b/src/controllers/home.js new file mode 100644 index 0000000000..657d406f67 --- /dev/null +++ b/src/controllers/home.js @@ -0,0 +1,65 @@ +import TabbedView from '../components/tabbedview/tabbedview'; +import globalize from '../scripts/globalize'; +import '../elements/emby-tabs/emby-tabs'; +import '../elements/emby-button/emby-button'; +import '../elements/emby-scroller/emby-scroller'; +import LibraryMenu from '../scripts/libraryMenu'; + +class HomeView extends TabbedView { + setTitle() { + LibraryMenu.setTitle(null); + } + + onPause() { + super.onPause(this); + document.querySelector('.skinHeader').classList.remove('noHomeButtonHeader'); + } + + onResume(options) { + super.onResume(this, options); + document.querySelector('.skinHeader').classList.add('noHomeButtonHeader'); + } + + getDefaultTabIndex() { + return 0; + } + + getTabs() { + return [{ + name: globalize.translate('Home') + }, { + name: globalize.translate('Favorites') + }]; + } + + getTabController(index) { + if (index == null) { + throw new Error('index cannot be null'); + } + + let depends = ''; + + switch (index) { + case 0: + depends = 'hometab'; + break; + + case 1: + depends = 'favorites'; + } + + const instance = this; + return import(/* webpackChunkName: "[request]" */ `../controllers/${depends}`).then(({ default: controllerFactory }) => { + let controller = instance.tabControllers[index]; + + if (!controller) { + controller = new controllerFactory(instance.view.querySelector(".tabContent[data-index='" + index + "']"), instance.params); + instance.tabControllers[index] = controller; + } + + return controller; + }); + } +} + +export default HomeView; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index 5c59483291..943b5f74a2 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -113,6 +113,11 @@ function getProgramScheduleHtml(items, action = 'none') { }); } +function getSelectedMediaSource(page, mediaSources) { + const mediaSourceId = page.querySelector('.selectSource').value; + return mediaSources.filter(m => m.Id === mediaSourceId)[0]; +} + function renderSeriesTimerSchedule(page, apiClient, seriesTimerId) { apiClient.getLiveTvTimers({ UserId: apiClient.getCurrentUserId(), @@ -206,10 +211,7 @@ function renderTrackSelections(page, instance, item, forceReload) { } function renderVideoSelections(page, mediaSources) { - const mediaSourceId = page.querySelector('.selectSource').value; - const mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; + const mediaSource = getSelectedMediaSource(page, mediaSources); const tracks = mediaSource.MediaStreams.filter(function (m) { return m.Type === 'Video'; @@ -243,10 +245,8 @@ function renderVideoSelections(page, mediaSources) { } function renderAudioSelections(page, mediaSources) { - const mediaSourceId = page.querySelector('.selectSource').value; - const mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; + const mediaSource = getSelectedMediaSource(page, mediaSources); + const tracks = mediaSource.MediaStreams.filter(function (m) { return m.Type === 'Audio'; }); @@ -273,10 +273,8 @@ function renderAudioSelections(page, mediaSources) { } function renderSubtitleSelections(page, mediaSources) { - const mediaSourceId = page.querySelector('.selectSource').value; - const mediaSource = mediaSources.filter(function (m) { - return m.Id === mediaSourceId; - })[0]; + const mediaSource = getSelectedMediaSource(page, mediaSources); + const tracks = mediaSource.MediaStreams.filter(function (m) { return m.Type === 'Subtitle'; }); @@ -2029,6 +2027,7 @@ export default function (view, params) { renderVideoSelections(view, self._currentPlaybackMediaSources); renderAudioSelections(view, self._currentPlaybackMediaSources); renderSubtitleSelections(view, self._currentPlaybackMediaSources); + updateMiscInfo(); }); view.addEventListener('viewshow', function (e) { const page = this; @@ -2063,5 +2062,14 @@ export default function (view, params) { }); } + function updateMiscInfo() { + const selectedMediaSource = getSelectedMediaSource(view, self._currentPlaybackMediaSources); + renderMiscInfo(view, { + // patch currentItem (primary item) with details from the selected MediaSource: + ...currentItem, + ...selectedMediaSource + }); + } + init(); } diff --git a/src/controllers/list.js b/src/controllers/list.js index cf57fa2ee0..62ef3c12cb 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -13,417 +13,415 @@ import '../elements/emby-scroller/emby-scroller'; import ServerConnections from '../components/ServerConnections'; import LibraryMenu from '../scripts/libraryMenu'; -/* eslint-disable indent */ +function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) { + const query = { + UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(), + StartIndex: startIndex, + Fields: 'ChannelInfo,PrimaryImageAspectRatio', + Limit: limit + }; - function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) { - const query = { - UserId: ServerConnections.getApiClient(params.serverId).getCurrentUserId(), - StartIndex: startIndex, - Fields: 'ChannelInfo,PrimaryImageAspectRatio', - Limit: limit - }; - - if (params.type === 'Recordings') { - query.IsInProgress = false; - } else { - query.HasAired = false; - } - - if (params.genreId) { - query.GenreIds = params.genreId; - } - - if (params.IsMovie === 'true') { - query.IsMovie = true; - } else if (params.IsMovie === 'false') { - query.IsMovie = false; - } - - if (params.IsSeries === 'true') { - query.IsSeries = true; - } else if (params.IsSeries === 'false') { - query.IsSeries = false; - } - - if (params.IsNews === 'true') { - query.IsNews = true; - } else if (params.IsNews === 'false') { - query.IsNews = false; - } - - if (params.IsSports === 'true') { - query.IsSports = true; - } else if (params.IsSports === 'false') { - query.IsSports = false; - } - - if (params.IsKids === 'true') { - query.IsKids = true; - } else if (params.IsKids === 'false') { - query.IsKids = false; - } - - if (params.IsAiring === 'true') { - query.IsAiring = true; - } else if (params.IsAiring === 'false') { - query.IsAiring = false; - } - - return modifyQueryWithFilters(instance, query); + if (params.type === 'Recordings') { + query.IsInProgress = false; + } else { + query.HasAired = false; } - function modifyQueryWithFilters(instance, query) { - const sortValues = instance.getSortValues(); + if (params.genreId) { + query.GenreIds = params.genreId; + } - if (!query.SortBy) { - query.SortBy = sortValues.sortBy; - query.SortOrder = sortValues.sortOrder; + if (params.IsMovie === 'true') { + query.IsMovie = true; + } else if (params.IsMovie === 'false') { + query.IsMovie = false; + } + + if (params.IsSeries === 'true') { + query.IsSeries = true; + } else if (params.IsSeries === 'false') { + query.IsSeries = false; + } + + if (params.IsNews === 'true') { + query.IsNews = true; + } else if (params.IsNews === 'false') { + query.IsNews = false; + } + + if (params.IsSports === 'true') { + query.IsSports = true; + } else if (params.IsSports === 'false') { + query.IsSports = false; + } + + if (params.IsKids === 'true') { + query.IsKids = true; + } else if (params.IsKids === 'false') { + query.IsKids = false; + } + + if (params.IsAiring === 'true') { + query.IsAiring = true; + } else if (params.IsAiring === 'false') { + query.IsAiring = false; + } + + return modifyQueryWithFilters(instance, query); +} + +function modifyQueryWithFilters(instance, query) { + const sortValues = instance.getSortValues(); + + if (!query.SortBy) { + query.SortBy = sortValues.sortBy; + query.SortOrder = sortValues.sortOrder; + } + + query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; + query.ImageTypeLimit = 1; + let hasFilters; + const queryFilters = []; + const filters = instance.getFilters(); + + if (filters.IsPlayed) { + queryFilters.push('IsPlayed'); + hasFilters = true; + } + + if (filters.IsUnplayed) { + queryFilters.push('IsUnplayed'); + hasFilters = true; + } + + if (filters.IsFavorite) { + queryFilters.push('IsFavorite'); + hasFilters = true; + } + + if (filters.IsResumable) { + queryFilters.push('IsResumable'); + hasFilters = true; + } + + if (filters.VideoTypes) { + hasFilters = true; + query.VideoTypes = filters.VideoTypes; + } + + if (filters.GenreIds) { + hasFilters = true; + query.GenreIds = filters.GenreIds; + } + + if (filters.Is4K) { + query.Is4K = true; + hasFilters = true; + } + + if (filters.IsHD) { + query.IsHD = true; + hasFilters = true; + } + + if (filters.IsSD) { + query.IsHD = false; + hasFilters = true; + } + + if (filters.Is3D) { + query.Is3D = true; + hasFilters = true; + } + + if (filters.HasSubtitles) { + query.HasSubtitles = true; + hasFilters = true; + } + + if (filters.HasTrailer) { + query.HasTrailer = true; + hasFilters = true; + } + + if (filters.HasSpecialFeature) { + query.HasSpecialFeature = true; + hasFilters = true; + } + + if (filters.HasThemeSong) { + query.HasThemeSong = true; + hasFilters = true; + } + + if (filters.HasThemeVideo) { + query.HasThemeVideo = true; + hasFilters = true; + } + + query.Filters = queryFilters.length ? queryFilters.join(',') : null; + instance.setFilterStatus(hasFilters); + + if (instance.alphaPicker) { + const newValue = instance.alphaPicker.value(); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; } + } - query.Fields = query.Fields ? query.Fields + ',PrimaryImageAspectRatio' : 'PrimaryImageAspectRatio'; - query.ImageTypeLimit = 1; - let hasFilters; - const queryFilters = []; - const filters = instance.getFilters(); + return query; +} - if (filters.IsPlayed) { - queryFilters.push('IsPlayed'); - hasFilters = true; - } +function setSortButtonIcon(btnSortIcon, icon) { + btnSortIcon.classList.remove('arrow_downward'); + btnSortIcon.classList.remove('arrow_upward'); + btnSortIcon.classList.add(icon); +} - if (filters.IsUnplayed) { - queryFilters.push('IsUnplayed'); - hasFilters = true; - } +function updateSortText(instance) { + const btnSortText = instance.btnSortText; - if (filters.IsFavorite) { - queryFilters.push('IsFavorite'); - hasFilters = true; - } + if (btnSortText) { + const options = instance.getSortMenuOptions(); + const values = instance.getSortValues(); + const sortBy = values.sortBy; - if (filters.IsResumable) { - queryFilters.push('IsResumable'); - hasFilters = true; - } - - if (filters.VideoTypes) { - hasFilters = true; - query.VideoTypes = filters.VideoTypes; - } - - if (filters.GenreIds) { - hasFilters = true; - query.GenreIds = filters.GenreIds; - } - - if (filters.Is4K) { - query.Is4K = true; - hasFilters = true; - } - - if (filters.IsHD) { - query.IsHD = true; - hasFilters = true; - } - - if (filters.IsSD) { - query.IsHD = false; - hasFilters = true; - } - - if (filters.Is3D) { - query.Is3D = true; - hasFilters = true; - } - - if (filters.HasSubtitles) { - query.HasSubtitles = true; - hasFilters = true; - } - - if (filters.HasTrailer) { - query.HasTrailer = true; - hasFilters = true; - } - - if (filters.HasSpecialFeature) { - query.HasSpecialFeature = true; - hasFilters = true; - } - - if (filters.HasThemeSong) { - query.HasThemeSong = true; - hasFilters = true; - } - - if (filters.HasThemeVideo) { - query.HasThemeVideo = true; - hasFilters = true; - } - - query.Filters = queryFilters.length ? queryFilters.join(',') : null; - instance.setFilterStatus(hasFilters); - - if (instance.alphaPicker) { - const newValue = instance.alphaPicker.value(); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; + for (const option of options) { + if (sortBy === option.value) { + btnSortText.innerHTML = globalize.translate('SortByValue', option.name); + break; } } - return query; + const btnSortIcon = instance.btnSortIcon; + + if (btnSortIcon) { + setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); + } } +} - function setSortButtonIcon(btnSortIcon, icon) { - btnSortIcon.classList.remove('arrow_downward'); - btnSortIcon.classList.remove('arrow_upward'); - btnSortIcon.classList.add(icon); +function updateItemsContainerForViewType(instance) { + if (instance.getViewSettings().imageType === 'list') { + instance.itemsContainer.classList.remove('vertical-wrap'); + instance.itemsContainer.classList.add('vertical-list'); + } else { + instance.itemsContainer.classList.add('vertical-wrap'); + instance.itemsContainer.classList.remove('vertical-list'); } +} - function updateSortText(instance) { - const btnSortText = instance.btnSortText; +function updateAlphaPickerState(instance) { + if (instance.alphaPicker) { + const alphaPicker = instance.alphaPickerElement; - if (btnSortText) { - const options = instance.getSortMenuOptions(); + if (alphaPicker) { const values = instance.getSortValues(); - const sortBy = values.sortBy; - for (const option of options) { - if (sortBy === option.value) { - btnSortText.innerHTML = globalize.translate('SortByValue', option.name); - break; - } - } - - const btnSortIcon = instance.btnSortIcon; - - if (btnSortIcon) { - setSortButtonIcon(btnSortIcon, values.sortOrder === 'Descending' ? 'arrow_downward' : 'arrow_upward'); + if (values.sortBy.indexOf('SortName') !== -1) { + alphaPicker.classList.remove('hide'); + instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); + } else { + alphaPicker.classList.add('hide'); + instance.itemsContainer.parentNode.classList.remove('padded-right-withalphapicker'); } } } +} - function updateItemsContainerForViewType(instance) { - if (instance.getViewSettings().imageType === 'list') { - instance.itemsContainer.classList.remove('vertical-wrap'); - instance.itemsContainer.classList.add('vertical-list'); - } else { - instance.itemsContainer.classList.add('vertical-wrap'); - instance.itemsContainer.classList.remove('vertical-list'); - } +function getItems(instance, params, item, sortBy, startIndex, limit) { + const apiClient = ServerConnections.getApiClient(params.serverId); + + instance.queryRecursive = false; + if (params.type === 'Recordings') { + return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params, startIndex, limit)); } - function updateAlphaPickerState(instance) { - if (instance.alphaPicker) { - const alphaPicker = instance.alphaPickerElement; - - if (alphaPicker) { - const values = instance.getSortValues(); - - if (values.sortBy.indexOf('SortName') !== -1) { - alphaPicker.classList.remove('hide'); - instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); - } else { - alphaPicker.classList.add('hide'); - instance.itemsContainer.parentNode.classList.remove('padded-right-withalphapicker'); - } - } + if (params.type === 'Programs') { + if (params.IsAiring === 'true') { + return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); } + + return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); } - function getItems(instance, params, item, sortBy, startIndex, limit) { - const apiClient = ServerConnections.getApiClient(params.serverId); + if (params.type === 'nextup') { + return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { + Limit: limit, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', + UserId: apiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false, + SortBy: sortBy, + EnableRewatching: userSettings.enableRewatchingInNextUp() + })); + } - instance.queryRecursive = false; - if (params.type === 'Recordings') { - return apiClient.getLiveTvRecordings(getInitialLiveTvQuery(instance, params, startIndex, limit)); + if (!item) { + instance.queryRecursive = true; + let method = 'getItems'; + + if (params.type === 'MusicArtist') { + method = 'getArtists'; + } else if (params.type === 'Person') { + method = 'getPeople'; } - if (params.type === 'Programs') { - if (params.IsAiring === 'true') { - return apiClient.getLiveTvRecommendedPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); - } - - return apiClient.getLiveTvPrograms(getInitialLiveTvQuery(instance, params, startIndex, limit)); - } - - if (params.type === 'nextup') { - return apiClient.getNextUpEpisodes(modifyQueryWithFilters(instance, { - Limit: limit, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', - UserId: apiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false, - SortBy: sortBy, - EnableRewatching: userSettings.enableRewatchingInNextUp() - })); - } - - if (!item) { - instance.queryRecursive = true; - let method = 'getItems'; - - if (params.type === 'MusicArtist') { - method = 'getArtists'; - } else if (params.type === 'Person') { - method = 'getPeople'; - } - - return apiClient[method](apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { - StartIndex: startIndex, - Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName', - ImageTypeLimit: 1, - IncludeItemTypes: params.type === 'MusicArtist' || params.type === 'Person' ? null : params.type, - Recursive: true, - IsFavorite: params.IsFavorite === 'true' || null, - ArtistIds: params.artistId || null, - SortBy: sortBy - })); - } - - if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') { - instance.queryRecursive = true; - const query = { - StartIndex: startIndex, - Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName', - Recursive: true, - parentId: params.parentId, - SortBy: sortBy - }; - - if (item.Type === 'Studio') { - query.StudioIds = item.Id; - } else if (item.Type === 'Genre' || item.Type === 'MusicGenre') { - query.GenreIds = item.Id; - } else if (item.Type === 'Person') { - query.PersonIds = item.Id; - } - - if (item.Type === 'MusicGenre') { - query.IncludeItemTypes = 'MusicAlbum'; - } else if (item.CollectionType === 'movies') { - query.IncludeItemTypes = 'Movie'; - } else if (item.CollectionType === 'tvshows') { - query.IncludeItemTypes = 'Series'; - } else if (item.Type === 'Genre') { - query.IncludeItemTypes = 'Movie,Series,Video'; - } else if (item.Type === 'Person') { - query.IncludeItemTypes = params.type; - } - - return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, query)); - } - - return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { + return apiClient[method](apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { StartIndex: startIndex, Limit: limit, - Fields: 'PrimaryImageAspectRatio,SortName,Path,SongCount,ChildCount,MediaSourceCount', + Fields: 'PrimaryImageAspectRatio,SortName', ImageTypeLimit: 1, - ParentId: item.Id, + IncludeItemTypes: params.type === 'MusicArtist' || params.type === 'Person' ? null : params.type, + Recursive: true, + IsFavorite: params.IsFavorite === 'true' || null, + ArtistIds: params.artistId || null, SortBy: sortBy })); } - function getItem(params) { - if (params.type === 'Recordings' || params.type === 'Programs' || params.type === 'nextup') { - return Promise.resolve(null); + if (item.Type === 'Genre' || item.Type === 'MusicGenre' || item.Type === 'Studio' || item.Type === 'Person') { + instance.queryRecursive = true; + const query = { + StartIndex: startIndex, + Limit: limit, + Fields: 'PrimaryImageAspectRatio,SortName', + Recursive: true, + parentId: params.parentId, + SortBy: sortBy + }; + + if (item.Type === 'Studio') { + query.StudioIds = item.Id; + } else if (item.Type === 'Genre' || item.Type === 'MusicGenre') { + query.GenreIds = item.Id; + } else if (item.Type === 'Person') { + query.PersonIds = item.Id; } - const apiClient = ServerConnections.getApiClient(params.serverId); - const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; - - if (itemId) { - return apiClient.getItem(apiClient.getCurrentUserId(), itemId); + if (item.Type === 'MusicGenre') { + query.IncludeItemTypes = 'MusicAlbum'; + } else if (item.CollectionType === 'movies') { + query.IncludeItemTypes = 'Movie'; + } else if (item.CollectionType === 'tvshows') { + query.IncludeItemTypes = 'Series'; + } else if (item.Type === 'Genre') { + query.IncludeItemTypes = 'Movie,Series,Video'; + } else if (item.Type === 'Person') { + query.IncludeItemTypes = params.type; } + return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, query)); + } + + return apiClient.getItems(apiClient.getCurrentUserId(), modifyQueryWithFilters(instance, { + StartIndex: startIndex, + Limit: limit, + Fields: 'PrimaryImageAspectRatio,SortName,Path,SongCount,ChildCount,MediaSourceCount', + ImageTypeLimit: 1, + ParentId: item.Id, + SortBy: sortBy + })); +} + +function getItem(params) { + if (params.type === 'Recordings' || params.type === 'Programs' || params.type === 'nextup') { return Promise.resolve(null); } - function showViewSettingsMenu() { - const instance = this; + const apiClient = ServerConnections.getApiClient(params.serverId); + const itemId = params.genreId || params.musicGenreId || params.studioId || params.personId || params.parentId; - import('../components/viewSettings/viewSettings').then(({ default: ViewSettings }) => { - new ViewSettings().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getViewSettings(), - visibleSettings: instance.getVisibleViewSettings() - }).then(function () { - updateItemsContainerForViewType(instance); - instance.itemsContainer.refreshItems(); - }); - }); + if (itemId) { + return apiClient.getItem(apiClient.getCurrentUserId(), itemId); } - function showFilterMenu() { - const instance = this; + return Promise.resolve(null); +} - import('../components/filtermenu/filtermenu').then(({ default: FilterMenu }) => { - new FilterMenu().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getFilters(), - visibleSettings: instance.getVisibleFilters(), - onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), - parentId: instance.params.parentId, - itemTypes: instance.getItemTypes(), - serverId: instance.params.serverId, - filterMenuOptions: instance.getFilterMenuOptions() - }).then(function () { - instance.itemsContainer.refreshItems(); - }); +function showViewSettingsMenu() { + const instance = this; + + import('../components/viewSettings/viewSettings').then(({ default: ViewSettings }) => { + new ViewSettings().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getViewSettings(), + visibleSettings: instance.getVisibleViewSettings() + }).then(function () { + updateItemsContainerForViewType(instance); + instance.itemsContainer.refreshItems(); }); - } + }); +} - function showSortMenu() { - const instance = this; +function showFilterMenu() { + const instance = this; - import('../components/sortmenu/sortmenu').then(({ default: SortMenu }) => { - new SortMenu().show({ - settingsKey: instance.getSettingsKey(), - settings: instance.getSortValues(), - onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), - serverId: instance.params.serverId, - sortOptions: instance.getSortMenuOptions() - }).then(function () { - updateSortText(instance); - updateAlphaPickerState(instance); - instance.itemsContainer.refreshItems(); - }); + import('../components/filtermenu/filtermenu').then(({ default: FilterMenu }) => { + new FilterMenu().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getFilters(), + visibleSettings: instance.getVisibleFilters(), + onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), + parentId: instance.params.parentId, + itemTypes: instance.getItemTypes(), + serverId: instance.params.serverId, + filterMenuOptions: instance.getFilterMenuOptions() + }).then(function () { + instance.itemsContainer.refreshItems(); }); - } + }); +} - function onNewItemClick() { - const instance = this; +function showSortMenu() { + const instance = this; - import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { - new playlistEditor({ - items: [], - serverId: instance.params.serverId - }); + import('../components/sortmenu/sortmenu').then(({ default: SortMenu }) => { + new SortMenu().show({ + settingsKey: instance.getSettingsKey(), + settings: instance.getSortValues(), + onChange: instance.itemsContainer.refreshItems.bind(instance.itemsContainer), + serverId: instance.params.serverId, + sortOptions: instance.getSortMenuOptions() + }).then(function () { + updateSortText(instance); + updateAlphaPickerState(instance); + instance.itemsContainer.refreshItems(); }); - } + }); +} - function hideOrShowAll(elems, hide) { - for (const elem of elems) { - if (hide) { - elem.classList.add('hide'); - } else { - elem.classList.remove('hide'); - } +function onNewItemClick() { + const instance = this; + + import('../components/playlisteditor/playlisteditor').then(({ default: playlistEditor }) => { + new playlistEditor({ + items: [], + serverId: instance.params.serverId + }); + }); +} + +function hideOrShowAll(elems, hide) { + for (const elem of elems) { + if (hide) { + elem.classList.add('hide'); + } else { + elem.classList.remove('hide'); } } +} - function bindAll(elems, eventName, fn) { - for (const elem of elems) { - elem.addEventListener(eventName, fn); - } +function bindAll(elems, eventName, fn) { + for (const elem of elems) { + elem.addEventListener(eventName, fn); } +} class ItemsView { constructor(view, params) { @@ -1295,4 +1293,3 @@ class ItemsView { export default ItemsView; -/* eslint-enable indent */ diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js new file mode 100644 index 0000000000..6e6af9dc98 --- /dev/null +++ b/src/controllers/movies/moviecollections.js @@ -0,0 +1,261 @@ +import loading from '../../components/loading/loading'; +import libraryBrowser from '../../scripts/libraryBrowser'; +import imageLoader from '../../components/images/imageLoader'; +import listView from '../../components/listview/listview'; +import cardBuilder from '../../components/cardbuilder/cardBuilder'; +import * as userSettings from '../../scripts/settings/userSettings'; +import globalize from '../../scripts/globalize'; +import '../../elements/emby-itemscontainer/emby-itemscontainer'; + +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; + + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'BoxSet', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return params.topParentId + '-' + 'moviecollections'; + } + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = (page) => { + loading.show(); + isLoading = true; + const query = getQuery(); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + overlayPlayButton: true, + centerText: true, + showTitle: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + lazy: true, + cardLayout: true, + showTitle: true + }); + } else if (viewStyle == 'Banner') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'banner', + preferBanner: true, + context: 'movies', + lazy: true + }); + } else if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'movies', + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'movies', + showTitle: true, + centerText: false, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'movies', + centerText: true, + overlayPlayButton: true, + showTitle: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

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

'; + html += '

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

'; + html += '
'; + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + }; + + const data = {}; + let isLoading = false; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + const initPage = (tabElement) => { + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' + }], + callback: function () { + getQuery().StartIndex = 0; + reloadItems(tabElement); + }, + query: getQuery(), + button: e.target + }); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; + onViewStyleChange(); + reloadItems(tabElement); + }); + tabElement.querySelector('.btnNewCollection').addEventListener('click', () => { + import('../../components/collectionEditor/collectionEditor').then(({ default: CollectionEditor }) => { + const serverId = ApiClient.serverInfo().Id; + const collectionEditor = new CollectionEditor(); + collectionEditor.show({ + items: [], + serverId: serverId + }); + }); + }); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = function () { + reloadItems(tabContent); + }; +} + diff --git a/src/controllers/movies/moviegenres.js b/src/controllers/movies/moviegenres.js new file mode 100644 index 0000000000..ee0b04837c --- /dev/null +++ b/src/controllers/movies/moviegenres.js @@ -0,0 +1,221 @@ +import escapeHtml from 'escape-html'; +import layoutManager from '../../components/layoutManager'; +import loading from '../../components/loading/loading'; +import libraryBrowser from '../../scripts/libraryBrowser'; +import cardBuilder from '../../components/cardbuilder/cardBuilder'; +import lazyLoader from '../../components/lazyLoader/lazyLoaderIntersectionObserver'; +import globalize from '../../scripts/globalize'; +import { appRouter } from '../../components/appRouter'; +import '../../elements/emby-button/emby-button'; + +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; + + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', + Recursive: true, + EnableTotalRecordCount: false + }, + view: 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return params.topParentId + '-' + 'moviegenres'; + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); + } + + function enableScrollX() { + return !layoutManager.desktop; + } + + function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; + } + + function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; + } + + const fillItemsContainer = (entry) => { + const elem = entry.target; + const id = elem.getAttribute('data-id'); + const viewStyle = this.getCurrentViewStyle(); + let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; + + if (enableScrollX()) { + limit = 10; + } + + const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; + const query = { + SortBy: 'Random', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: enableImageTypes, + Limit: limit, + GenreIds: id, + EnableTotalRecordCount: false, + ParentId: params.topParentId + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + if (viewStyle == 'Thumb') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: false + }); + } else if (viewStyle == 'ThumbCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'PosterCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'Poster') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + scalable: true, + overlayMoreButton: true, + allowBottomPadding: true, + showTitle: true, + centerText: true, + showYear: true + }); + } + if (result.Items.length >= query.Limit) { + tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); + } + }); + }; + + function reloadItems(context, promise) { + const query = getQuery(); + promise.then(function (result) { + const elem = context.querySelector('#items'); + let html = ''; + const items = result.Items; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + + html += '
'; + html += ''; + if (enableScrollX()) { + let scrollXClass = 'scrollX hiddenScrollX'; + + if (layoutManager.tv) { + scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; + } + + html += '
'; + } else { + html += '
'; + } + + html += '
'; + 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); + loading.hide(); + }); + } + + const fullyReload = () => { + this.preRender(); + this.renderTab(); + }; + + const data = {}; + + this.getViewStyles = function () { + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + this.setCurrentViewStyle = function (viewStyle) { + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + fullyReload(); + }; + + this.enableViewSelection = true; + let promise; + + this.preRender = function () { + promise = getPromise(); + }; + + this.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/movies/movies.html b/src/controllers/movies/movies.html new file mode 100644 index 0000000000..7a08694b2a --- /dev/null +++ b/src/controllers/movies/movies.html @@ -0,0 +1,92 @@ +
+ +
+
+
+ + + + +
+ +
+
+ +
+
+
+
+
+
+
+
+
+

${HeaderContinueWatching}

+
+ +
+
+
+ +
+
+

${HeaderLatestMovies}

+
+ +
+
+
+ +
+
+
+
+

${MessageNoMovieSuggestionsAvailable}

+
+
+
+
+
+ + +
+ +
+
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+ + + +
+ +
+
+
+
+
+
+
+
+
+
diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js new file mode 100644 index 0000000000..a8e2aca1fa --- /dev/null +++ b/src/controllers/movies/movies.js @@ -0,0 +1,324 @@ +import loading from '../../components/loading/loading'; +import * as userSettings from '../../scripts/settings/userSettings'; +import libraryBrowser from '../../scripts/libraryBrowser'; +import { AlphaPicker } from '../../components/alphaPicker/alphaPicker'; +import listView from '../../components/listview/listview'; +import cardBuilder from '../../components/cardbuilder/cardBuilder'; +import globalize from '../../scripts/globalize'; +import Events from '../../utils/events.ts'; +import { playbackManager } from '../../components/playback/playbackmanager'; + +import '../../elements/emby-itemscontainer/emby-itemscontainer'; + +export default function (view, params, tabContent, options) { + const onViewStyleChange = () => { + if (this.getCurrentViewStyle() == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + function fetchData() { + isLoading = true; + loading.show(); + return ApiClient.getItems(ApiClient.getCurrentUserId(), query); + } + + function shuffle() { + ApiClient.getItem( + ApiClient.getCurrentUserId(), + params.topParentId + ).then((item) => { + playbackManager.shuffle(item); + }); + } + + const afterRefresh = (result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + itemsContainer.refreshItems(); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + itemsContainer.refreshItems(); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + + for (const elem of tabContent.querySelectorAll('.paging')) { + elem.innerHTML = pagingHtml; + } + + for (const elem of tabContent.querySelectorAll('.btnNextPage')) { + elem.addEventListener('click', onNextPageClick); + } + + for (const elem of tabContent.querySelectorAll('.btnPreviousPage')) { + elem.addEventListener('click', onPreviousPageClick); + } + + tabContent.querySelector('.btnShuffle').classList.toggle('hide', result.TotalRecordCount < 1); + + isLoading = false; + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(tabContent); + }); + }; + + const getItemsHtml = (items) => { + let html; + const viewStyle = this.getCurrentViewStyle(); + + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + lazy: true, + overlayPlayButton: true, + showTitle: true, + showYear: true, + centerText: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + lazy: true, + cardLayout: true, + showTitle: true, + showYear: true, + centerText: true + }); + } else if (viewStyle == 'Banner') { + html = cardBuilder.getCardsHtml({ + items: items, + shape: 'banner', + preferBanner: true, + context: 'movies', + lazy: true + }); + } else if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: items, + context: 'movies', + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: items, + shape: 'portrait', + context: 'movies', + showTitle: true, + showYear: true, + centerText: true, + lazy: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: items, + shape: 'portrait', + context: 'movies', + overlayPlayButton: true, + showTitle: true, + showYear: true, + centerText: true + }); + } + + return html; + }; + + const initPage = (tabElement) => { + itemsContainer.fetchData = fetchData; + itemsContainer.getItemsHtml = getItemsHtml; + itemsContainer.afterRefresh = afterRefresh; + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + + if (alphaPickerElement) { + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + itemsContainer.refreshItems(); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + } + + const btnFilter = tabElement.querySelector('.btnFilter'); + + if (btnFilter) { + btnFilter.addEventListener('click', () => { + this.showFilterMenu(); + }); + } + const btnSort = tabElement.querySelector('.btnSort'); + + if (btnSort) { + btnSort.addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName,ProductionYear' + }, { + name: globalize.translate('OptionRandom'), + id: 'Random' + }, { + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName,ProductionYear' + }, { + name: globalize.translate('OptionCriticRating'), + id: 'CriticRating,SortName,ProductionYear' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName,ProductionYear' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName,ProductionYear' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName,ProductionYear' + }, { + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SortName,ProductionYear' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName,ProductionYear' + }, { + name: globalize.translate('Runtime'), + id: 'Runtime,SortName,ProductionYear' + }], + callback: function () { + query.StartIndex = 0; + userSettings.saveQuerySettings(savedQueryKey, query); + itemsContainer.refreshItems(); + }, + query: query, + button: e.target + }); + }); + } + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + userSettings.set(savedViewKey, viewStyle); + query.StartIndex = 0; + onViewStyleChange(); + itemsContainer.refreshItems(); + }); + + tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle); + }; + + let itemsContainer = tabContent.querySelector('.itemsContainer'); + const savedQueryKey = params.topParentId + '-' + options.mode; + const savedViewKey = savedQueryKey + '-view'; + let query = { + SortBy: 'SortName,ProductionYear', + SortOrder: 'Ascending', + IncludeItemTypes: 'Movie', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0, + ParentId: params.topParentId + }; + + if (userSettings.libraryPageSize() > 0) { + query['Limit'] = userSettings.libraryPageSize(); + } + + let isLoading = false; + + if (options.mode === 'favorites') { + query.IsFavorite = true; + } + + query = userSettings.loadQuerySettings(savedQueryKey, query); + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: query, + mode: 'movies', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', () => { + query.StartIndex = 0; + itemsContainer.refreshItems(); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return userSettings.get(savedViewKey) || 'Poster'; + }; + + this.initTab = function () { + initPage(tabContent); + onViewStyleChange(); + }; + + this.renderTab = () => { + itemsContainer.refreshItems(); + this.alphaPicker?.updateControls(query); + }; + + this.destroy = function () { + itemsContainer = null; + }; +} + diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js new file mode 100644 index 0000000000..a7fab00085 --- /dev/null +++ b/src/controllers/movies/moviesrecommended.js @@ -0,0 +1,425 @@ +import escapeHtml from 'escape-html'; +import layoutManager from '../../components/layoutManager'; +import inputManager from '../../scripts/inputManager'; +import * as userSettings from '../../scripts/settings/userSettings'; +import libraryMenu from '../../scripts/libraryMenu'; +import * as mainTabsManager from '../../components/maintabsmanager'; +import cardBuilder from '../../components/cardbuilder/cardBuilder'; +import dom from '../../scripts/dom'; +import imageLoader from '../../components/images/imageLoader'; +import { playbackManager } from '../../components/playback/playbackmanager'; +import globalize from '../../scripts/globalize'; +import Dashboard from '../../utils/dashboard'; +import Events from '../../utils/events.ts'; + +import '../../elements/emby-scroller/emby-scroller'; +import '../../elements/emby-itemscontainer/emby-itemscontainer'; +import '../../elements/emby-tabs/emby-tabs'; +import '../../elements/emby-button/emby-button'; + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function loadLatest(page, userId, parentId) { + const options = { + IncludeItemTypes: 'Movie', + Limit: 18, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#recentlyAddedItems'); + cardBuilder.buildCards(items, { + itemsContainer: container, + shape: getPortraitShape(), + scalable: true, + overlayPlayButton: true, + allowBottomPadding: allowBottomPadding, + showTitle: true, + showYear: true, + centerText: true + }); + + // FIXME: Wait for all sections to load + autoFocus(page); + }); +} + +function loadResume(page, userId, parentId) { + const screenWidth = dom.getWindowSize().innerWidth; + const options = { + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Movie', + Filters: 'IsResumable', + Limit: screenWidth >= 1600 ? 5 : 3, + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + CollapseBoxSetItems: false, + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(userId, options).then(function (result) { + if (result.Items.length) { + page.querySelector('#resumableSection').classList.remove('hide'); + } else { + page.querySelector('#resumableSection').classList.add('hide'); + } + + const allowBottomPadding = !enableScrollX(); + const container = page.querySelector('#resumableItems'); + cardBuilder.buildCards(result.Items, { + itemsContainer: container, + preferThumb: true, + shape: getThumbShape(), + scalable: true, + overlayPlayButton: true, + allowBottomPadding: allowBottomPadding, + cardLayout: false, + showTitle: true, + showYear: true, + centerText: true + }); + + // FIXME: Wait for all sections to load + autoFocus(page); + }); +} + +function getRecommendationHtml(recommendation) { + let html = ''; + let title = ''; + + switch (recommendation.RecommendationType) { + case 'SimilarToRecentlyPlayed': + title = globalize.translate('RecommendationBecauseYouWatched', recommendation.BaselineItemName); + break; + + case 'SimilarToLikedItem': + title = globalize.translate('RecommendationBecauseYouLike', recommendation.BaselineItemName); + break; + + case 'HasDirectorFromRecentlyPlayed': + case 'HasLikedDirector': + title = globalize.translate('RecommendationDirectedBy', recommendation.BaselineItemName); + break; + + case 'HasActorFromRecentlyPlayed': + case 'HasLikedActor': + title = globalize.translate('RecommendationStarring', recommendation.BaselineItemName); + break; + } + + html += '
'; + html += '

' + escapeHtml(title) + '

'; + const allowBottomPadding = true; + + if (enableScrollX()) { + html += '
'; + html += '
'; + } else { + html += '
'; + } + + html += cardBuilder.getCardsHtml(recommendation.Items, { + shape: getPortraitShape(), + scalable: true, + overlayPlayButton: true, + allowBottomPadding: allowBottomPadding, + showTitle: true, + showYear: true, + centerText: true + }); + + if (enableScrollX()) { + html += '
'; + } + html += '
'; + html += '
'; + return html; +} + +function loadSuggestions(page, userId) { + const screenWidth = dom.getWindowSize().innerWidth; + let itemLimit = 5; + if (screenWidth >= 1600) { + itemLimit = 8; + } else if (screenWidth >= 1200) { + itemLimit = 6; + } + + const url = ApiClient.getUrl('Movies/Recommendations', { + userId: userId, + categoryLimit: 6, + ItemLimit: itemLimit, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' + }); + ApiClient.getJSON(url).then(function (recommendations) { + if (!recommendations.length) { + page.querySelector('.noItemsMessage').classList.remove('hide'); + page.querySelector('.recommendations').innerHTML = ''; + return; + } + + const html = recommendations.map(getRecommendationHtml).join(''); + page.querySelector('.noItemsMessage').classList.add('hide'); + const recs = page.querySelector('.recommendations'); + recs.innerHTML = html; + imageLoader.lazyChildren(recs); + + // FIXME: Wait for all sections to load + autoFocus(page); + }); +} + +function autoFocus(page) { + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); +} + +function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); + + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); + elem.classList.add('padded-top-focusscale'); + elem.classList.add('padded-bottom-focusscale'); + } + + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); + } +} + +function initSuggestedTab(page, tabContent) { + const containers = tabContent.querySelectorAll('.itemsContainer'); + + for (const container of containers) { + setScrollClasses(container, enableScrollX()); + } +} + +function loadSuggestionsTab(view, params, tabContent) { + const parentId = params.topParentId; + const userId = ApiClient.getCurrentUserId(); + loadResume(tabContent, userId, parentId); + loadLatest(tabContent, userId, parentId); + loadSuggestions(tabContent, userId); +} + +function getTabs() { + return [{ + name: globalize.translate('Movies') + }, { + name: globalize.translate('Suggestions') + }, { + name: globalize.translate('Trailers') + }, { + name: globalize.translate('Favorites') + }, { + name: globalize.translate('Collections') + }, { + name: globalize.translate('Genres') + }]; +} + +function getDefaultTabIndex(folderId) { + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': + return 1; + + case 'favorites': + return 3; + + case 'collections': + return 4; + + case 'genres': + return 5; + + default: + return 0; + } +} + +export default function (view, params) { + function onBeforeTabChange(e) { + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); + } + + function onTabChange(e) { + const newIndex = parseInt(e.detail.selectedTabIndex, 10); + loadTab(view, newIndex); + } + + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + const getTabController = (page, index, callback) => { + let depends = ''; + + switch (index) { + case 0: + depends = 'movies'; + break; + + case 1: + depends = 'moviesrecommended.js'; + break; + + case 2: + depends = 'movietrailers'; + break; + + case 3: + depends = 'movies'; + break; + + case 4: + depends = 'moviecollections'; + break; + + case 5: + depends = 'moviegenres'; + break; + } + + import(`../movies/${depends}`).then(({ default: controllerFactory }) => { + let tabContent; + + if (index === suggestionsTabIndex) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + this.tabContent = tabContent; + } + + let controller = tabControllers[index]; + + if (!controller) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + + if (index === suggestionsTabIndex) { + controller = this; + } else if (index == 0 || index == 3) { + controller = new controllerFactory(view, params, tabContent, { + mode: index ? 'favorites' : 'movies' + }); + } else { + controller = new controllerFactory(view, params, tabContent); + } + + tabControllers[index] = controller; + + if (controller.initTab) { + controller.initTab(); + } + } + + callback(controller); + }); + }; + + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1 && controller.preRender) { + controller.preRender(); + } + }); + } + + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, ((controller) => { + if (renderedTabs.indexOf(index) == -1) { + renderedTabs.push(index); + controller.renderTab(); + } + })); + } + + function onPlaybackStop(e, state) { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { + renderedTabs = []; + mainTabsManager.getTabsElement().triggerTabChange(); + } + } + + function onInputCommand(e) { + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=movies&parentId=' + params.topParentId); + } + } + + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); + const suggestionsTabIndex = 1; + + this.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + initSuggestedTab(view, tabContent); + }; + + this.renderTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + loadSuggestionsTab(view, params, tabContent); + }; + + const tabControllers = []; + let renderedTabs = []; + view.addEventListener('viewshow', function () { + initTabs(); + if (!view.getAttribute('data-title')) { + const parentId = params.topParentId; + + if (parentId) { + ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { + view.setAttribute('data-title', item.Name); + libraryMenu.setTitle(item.Name); + }); + } else { + view.setAttribute('data-title', globalize.translate('Movies')); + libraryMenu.setTitle(globalize.translate('Movies')); + } + } + + Events.on(playbackManager, 'playbackstop', onPlaybackStop); + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + inputManager.off(window, onInputCommand); + }); + for (const tabController of tabControllers) { + if (tabController.destroy) { + tabController.destroy(); + } + } +} + diff --git a/src/controllers/movies/movietrailers.js b/src/controllers/movies/movietrailers.js new file mode 100644 index 0000000000..a96d7e51aa --- /dev/null +++ b/src/controllers/movies/movietrailers.js @@ -0,0 +1,272 @@ +import loading from '../../components/loading/loading'; +import libraryBrowser from '../../scripts/libraryBrowser'; +import imageLoader from '../../components/images/imageLoader'; +import { AlphaPicker } from '../../components/alphaPicker/alphaPicker'; +import listView from '../../components/listview/listview'; +import cardBuilder from '../../components/cardbuilder/cardBuilder'; +import * as userSettings from '../../scripts/settings/userSettings'; +import globalize from '../../scripts/globalize'; +import Events from '../../utils/events.ts'; + +import '../../elements/emby-itemscontainer/emby-itemscontainer'; + +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; + + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Trailer', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + libraryBrowser.loadSavedQueryValues(key, pageData.query); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return params.topParentId + '-' + 'trailers'; + } + + const reloadItems = () => { + loading.show(); + isLoading = true; + const query = getQuery(); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + let html; + const viewStyle = this.getCurrentViewStyle(); + + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + overlayPlayButton: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'movies', + cardLayout: true, + showTitle: true, + showYear: true, + centerText: true + }); + } else if (viewStyle == 'Banner') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'banner', + preferBanner: true, + context: 'movies' + }); + } else if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'movies', + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'movies', + showTitle: true, + showYear: true, + cardLayout: true, + centerText: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'movies', + centerText: true, + overlayPlayButton: true, + showTitle: true, + showYear: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + if (!result.Items.length) { + html = ''; + + html += '
'; + html += '

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

'; + html += '

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

'; + html += '
'; + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + isLoading = false; + }); + }; + + const data = {}; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(), + mode: 'movies', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery().StartIndex = 0; + reloadItems(); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' + }, { + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' + }], + callback: function () { + getQuery().StartIndex = 0; + reloadItems(); + }, + query: getQuery(), + button: e.target + }); + }); + }; + + initPage(tabContent); + + this.renderTab = () => { + reloadItems(); + this.alphaPicker?.updateControls(getQuery()); + }; +} + diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index d1ad28b992..04ea17a983 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -11,294 +11,291 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ - - export default function (view, params, tabContent) { - function playAll() { - ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { - playbackManager.play({ - items: [item] - }); +export default function (view, params, tabContent) { + function playAll() { + ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { + playbackManager.play({ + items: [item] }); - } - - function shuffle() { - ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { - getQuery(); - playbackManager.shuffle(item); - }); - } - - function getPageData() { - const key = getSavedQueryKey(); - - if (!pageData) { - pageData = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'MusicAlbum', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - if (!savedQueryKey) { - savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); - } - - return savedQueryKey; - } - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = () => { - loading.show(); - isLoading = true; - const query = getQuery(); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - context: 'music', - sortBy: query.SortBy, - addToListButton: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - showParentTitle: true, - lazy: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - showParentTitle: true, - lazy: true, - centerText: true, - overlayPlayButton: true - }); - } - - let elems = tabContent.querySelectorAll('.paging'); - - for (const elem of elems) { - elem.innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (const elem of elems) { - elem.addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (const elem of elems) { - elem.addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(tabContent); - }); - }); - }; - - let savedQueryKey; - let pageData; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(), - mode: 'albums', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery().StartIndex = 0; - reloadItems(); - }); - - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(); - }); - - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - - tabElement.querySelector('.btnSort').addEventListener('click', (e) => { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SortName' - }, { - name: globalize.translate('AlbumArtist'), - id: 'AlbumArtist,SortName' - }, { - name: globalize.translate('OptionCommunityRating'), - id: 'CommunityRating,SortName' - }, { - name: globalize.translate('OptionCriticRating'), - id: 'CriticRating,SortName' - }, { - name: globalize.translate('OptionDateAdded'), - id: 'DateCreated,SortName' - }, { - name: globalize.translate('OptionReleaseDate'), - id: 'ProductionYear,PremiereDate,SortName' - }, { - name: globalize.translate('OptionRandom'), - id: 'Random,SortName' - }], - callback: function () { - getQuery().StartIndex = 0; - reloadItems(); - }, - query: getQuery(), - button: e.target - }); - }); - - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - getQuery().StartIndex = 0; - onViewStyleChange(); - reloadItems(); - }); - - tabElement.querySelector('.btnPlayAll').addEventListener('click', playAll); - tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(); - this.alphaPicker?.updateControls(getQuery()); - }; + }); } -/* eslint-enable indent */ + function shuffle() { + ApiClient.getItem(ApiClient.getCurrentUserId(), params.topParentId).then(function (item) { + getQuery(); + playbackManager.shuffle(item); + }); + } + + function getPageData() { + const key = getSavedQueryKey(); + + if (!pageData) { + pageData = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'MusicAlbum', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); + } + + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); + } + + return pageData; + } + + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + if (!savedQueryKey) { + savedQueryKey = libraryBrowser.getSavedQueryKey('musicalbums'); + } + + return savedQueryKey; + } + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = () => { + loading.show(); + isLoading = true; + const query = getQuery(); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'music', + sortBy: query.SortBy, + addToListButton: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + showParentTitle: true, + lazy: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(tabContent); + }); + }); + }; + + let savedQueryKey; + let pageData; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(), + mode: 'albums', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery().StartIndex = 0; + reloadItems(); + }); + + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(); + }); + + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + + tabElement.querySelector('.btnSort').addEventListener('click', (e) => { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('AlbumArtist'), + id: 'AlbumArtist,SortName' + }, { + name: globalize.translate('OptionCommunityRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionCriticRating'), + id: 'CriticRating,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'ProductionYear,PremiereDate,SortName' + }, { + name: globalize.translate('OptionRandom'), + id: 'Random,SortName' + }], + callback: function () { + getQuery().StartIndex = 0; + reloadItems(); + }, + query: getQuery(), + button: e.target + }); + }); + + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + getQuery().StartIndex = 0; + onViewStyleChange(); + reloadItems(); + }); + + tabElement.querySelector('.btnPlayAll').addEventListener('click', playAll); + tabElement.querySelector('.btnShuffle').addEventListener('click', shuffle); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(); + this.alphaPicker?.updateControls(getQuery()); + }; +} + diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 040df1af73..084f092db3 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -9,237 +9,234 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + const queryValues = { + SortBy: 'SortName', + SortOrder: 'Ascending', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', + StartIndex: 0, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' + }; - if (!pageData) { - const queryValues = { - SortBy: 'SortName', - SortOrder: 'Ascending', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,BasicSyncInfo', - StartIndex: 0, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb' - }; - - if (userSettings.libraryPageSize() > 0) { - queryValues['Limit'] = userSettings.libraryPageSize(); - } - - pageData = data[key] = { - query: queryValues, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + queryValues['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData = data[key] = { + query: queryValues, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - const getSavedQueryKey = (context) => { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); - } - - return context.savedQueryKey; - }; - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = (page) => { - loading.show(); - isLoading = true; - const query = getQuery(page); - const promise = this.mode == 'albumartists' ? - ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : - ApiClient.getArtists(ApiClient.getCurrentUserId(), query); - promise.then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - sortBy: query.SortBy - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - context: 'music', - showTitle: true, - coverImage: true, - lazy: true, - centerText: true, - overlayPlayButton: true - }); - } - let elems = tabContent.querySelectorAll('.paging'); - - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(tabContent); - }); - }); - }; - - const data = {}; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: this.mode, - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(tabElement); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(tabElement); - }); - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; - onViewStyleChange(); - reloadItems(tabElement); - }); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + const getSavedQueryKey = (context) => { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey(this.mode); + } + + return context.savedQueryKey; + }; + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = (page) => { + loading.show(); + isLoading = true; + const query = getQuery(page); + const promise = this.mode == 'albumartists' ? + ApiClient.getAlbumArtists(ApiClient.getCurrentUserId(), query) : + ApiClient.getArtists(ApiClient.getCurrentUserId(), query); + promise.then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + context: 'music', + showTitle: true, + coverImage: true, + lazy: true, + centerText: true, + overlayPlayButton: true + }); + } + let elems = tabContent.querySelectorAll('.paging'); + + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(tabContent); + }); + }); + }; + + const data = {}; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: this.mode, + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery(tabContent).StartIndex = 0; + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(tabElement); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(tabElement); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getQuery(tabElement).StartIndex = 0; + onViewStyleChange(); + reloadItems(tabElement); + }); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(tabContent); + this.alphaPicker?.updateControls(getQuery(tabContent)); + }; +} + diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index de64af44de..9c5bc9dcb2 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -3,132 +3,129 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,ItemCounts', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,ItemCounts', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); - } - - const reloadItems = (context, promise) => { - const query = getQuery(); - promise.then((result) => { - let html = ''; - const viewStyle = this.getCurrentViewStyle(); - - if (viewStyle == 'Thumb') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'music', - centerText: true, - overlayMoreButton: true, - showTitle: true - }); - } else if (viewStyle == 'ThumbCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'music', - cardLayout: true, - showTitle: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - context: 'music', - cardLayout: true, - showTitle: true - }); - } else if (viewStyle == 'Poster') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'auto', - context: 'music', - centerText: true, - overlayMoreButton: true, - showTitle: true - }); - } - - const elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); - }); - }; - - function fullyReload() { - this.preRender(); - this.renderTab(); - } - - const data = {}; - - this.getViewStyles = function () { - return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); - }; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - this.setCurrentViewStyle = function (viewStyle) { - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - fullyReload(); - }; - - this.enableViewSelection = true; - let promise; - - this.preRender = function () { - promise = getPromise(); - }; - - this.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('genres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); + } + + const reloadItems = (context, promise) => { + const query = getQuery(); + promise.then((result) => { + let html = ''; + const viewStyle = this.getCurrentViewStyle(); + + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'music', + centerText: true, + overlayMoreButton: true, + showTitle: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'music', + cardLayout: true, + showTitle: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'music', + cardLayout: true, + showTitle: true + }); + } else if (viewStyle == 'Poster') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'auto', + context: 'music', + centerText: true, + overlayMoreButton: true, + showTitle: true + }); + } + + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); + }; + + function fullyReload() { + this.preRender(); + this.renderTab(); + } + + const data = {}; + + this.getViewStyles = function () { + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); + }; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + this.setCurrentViewStyle = function (viewStyle) { + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + fullyReload(); + }; + + this.enableViewSelection = true; + let promise; + + this.preRender = function () { + promise = getPromise(); + }; + + this.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index 8f562f1aaa..ab3cf8f1d2 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -3,87 +3,84 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; import imageLoader from '../../components/images/imageLoader'; import loading from '../../components/loading/loading'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Playlist', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Playlist', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,SortName,CanDelete', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('genres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getItems(ApiClient.getCurrentUserId(), query); - } - - function reloadItems(context, promise) { - const query = getQuery(); - promise.then(function (result) { - let html = ''; - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'square', - showTitle: true, - coverImage: true, - centerText: true, - overlayPlayButton: true, - allowBottomPadding: true, - cardLayout: false - }); - const elem = context.querySelector('#items'); - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - libraryBrowser.saveQueryValues(getSavedQueryKey(), query); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); - }); - } - - const data = {}; - - this.getCurrentViewStyle = function () { - return getPageData().view; - }; - - let promise; - - this.preRender = function () { - promise = getPromise(); - }; - - this.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('genres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getItems(ApiClient.getCurrentUserId(), query); + } + + function reloadItems(context, promise) { + const query = getQuery(); + promise.then(function (result) { + let html = ''; + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'square', + showTitle: true, + coverImage: true, + centerText: true, + overlayPlayButton: true, + allowBottomPadding: true, + cardLayout: false + }); + const elem = context.querySelector('#items'); + elem.innerHTML = html; + imageLoader.lazyChildren(elem); + libraryBrowser.saveQueryValues(getSavedQueryKey(), query); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); + } + + const data = {}; + + this.getCurrentViewStyle = function () { + return getPageData().view; + }; + + let promise; + + this.preRender = function () { + promise = getPromise(); + }; + + this.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index e2e15d2ab2..07946897c7 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -16,386 +16,383 @@ import '../../elements/emby-button/emby-button'; import '../../styles/flexstyles.scss'; import Dashboard from '../../utils/dashboard'; -/* eslint-disable indent */ +function itemsPerRow() { + const screenWidth = dom.getWindowSize().innerWidth; - function itemsPerRow() { - const screenWidth = dom.getWindowSize().innerWidth; - - if (screenWidth >= 1920) { - return 9; - } - - if (screenWidth >= 1200) { - return 12; - } - - if (screenWidth >= 1000) { - return 10; - } - - return 8; + if (screenWidth >= 1920) { + return 9; } - function enableScrollX() { - return !layoutManager.desktop; + if (screenWidth >= 1200) { + return 12; } - function getSquareShape() { - return enableScrollX() ? 'overflowSquare' : 'square'; + if (screenWidth >= 1000) { + return 10; } - function loadLatest(page, parentId) { + return 8; +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getSquareShape() { + return enableScrollX() ? 'overflowSquare' : 'square'; +} + +function loadLatest(page, parentId) { + loading.show(); + const userId = ApiClient.getCurrentUserId(); + const options = { + IncludeItemTypes: 'Audio', + Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { + const elem = page.querySelector('#recentlyAddedSongs'); + elem.innerHTML = cardBuilder.getCardsHtml({ + items: items, + showUnplayedIndicator: false, + showLatestItemsPopup: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + lazy: true, + centerText: true, + overlayPlayButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(elem); + loading.hide(); + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); +} + +function loadRecentlyPlayed(page, parentId) { + const options = { + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', + Limit: itemsPerRow(), + Recursive: true, + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { + const elem = page.querySelector('#recentlyPlayed'); + + if (result.Items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + showUnplayedIndicator: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(itemsContainer); + }); +} + +function loadFrequentlyPlayed(page, parentId) { + const options = { + SortBy: 'PlayCount', + SortOrder: 'Descending', + IncludeItemTypes: 'Audio', + Limit: itemsPerRow(), + Recursive: true, + Fields: 'PrimaryImageAspectRatio,AudioInfo', + Filters: 'IsPlayed', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { + const elem = page.querySelector('#topPlayed'); + + if (result.Items.length) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + + const itemsContainer = elem.querySelector('.itemsContainer'); + itemsContainer.innerHTML = cardBuilder.getCardsHtml({ + items: result.Items, + showUnplayedIndicator: false, + shape: getSquareShape(), + showTitle: true, + showParentTitle: true, + action: 'instantmix', + lazy: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: !enableScrollX(), + cardLayout: false, + coverImage: true + }); + imageLoader.lazyChildren(itemsContainer); + }); +} + +function loadSuggestionsTab(page, tabContent, parentId) { + console.debug('loadSuggestionsTab'); + loadLatest(tabContent, parentId); + loadRecentlyPlayed(tabContent, parentId); + loadFrequentlyPlayed(tabContent, parentId); + + import('../../components/favoriteitems').then(({ default: favoriteItems }) => { + favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); + }); +} + +function getTabs() { + return [{ + name: globalize.translate('Albums') + }, { + name: globalize.translate('Suggestions') + }, { + name: globalize.translate('HeaderAlbumArtists') + }, { + name: globalize.translate('Artists') + }, { + name: globalize.translate('Playlists') + }, { + name: globalize.translate('Songs') + }, { + name: globalize.translate('Genres') + }]; +} + +function getDefaultTabIndex(folderId) { + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': + return 1; + + case 'albumartists': + return 2; + + case 'artists': + return 3; + + case 'playlists': + return 4; + + case 'songs': + return 5; + + case 'genres': + return 6; + + default: + return 0; + } +} + +export default function (view, params) { + function reload() { loading.show(); - const userId = ApiClient.getCurrentUserId(); - const options = { - IncludeItemTypes: 'Audio', - Limit: enableScrollX() ? 3 * itemsPerRow() : 2 * itemsPerRow(), - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getJSON(ApiClient.getUrl('Users/' + userId + '/Items/Latest', options)).then(function (items) { - const elem = page.querySelector('#recentlyAddedSongs'); - elem.innerHTML = cardBuilder.getCardsHtml({ - items: items, - showUnplayedIndicator: false, - showLatestItemsPopup: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - lazy: true, - centerText: true, - overlayPlayButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(elem); - loading.hide(); - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + loadSuggestionsTab(view, tabContent, params.topParentId); } - function loadRecentlyPlayed(page, parentId) { - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Audio', - Limit: itemsPerRow(), - Recursive: true, - Fields: 'PrimaryImageAspectRatio,AudioInfo', - Filters: 'IsPlayed', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - const elem = page.querySelector('#recentlyPlayed'); + function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); - if (result.Items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); } - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - showUnplayedIndicator: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - action: 'instantmix', - lazy: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function loadFrequentlyPlayed(page, parentId) { - const options = { - SortBy: 'PlayCount', - SortOrder: 'Descending', - IncludeItemTypes: 'Audio', - Limit: itemsPerRow(), - Recursive: true, - Fields: 'PrimaryImageAspectRatio,AudioInfo', - Filters: 'IsPlayed', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(ApiClient.getCurrentUserId(), options).then(function (result) { - const elem = page.querySelector('#topPlayed'); - - if (result.Items.length) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - - const itemsContainer = elem.querySelector('.itemsContainer'); - itemsContainer.innerHTML = cardBuilder.getCardsHtml({ - items: result.Items, - showUnplayedIndicator: false, - shape: getSquareShape(), - showTitle: true, - showParentTitle: true, - action: 'instantmix', - lazy: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: !enableScrollX(), - cardLayout: false, - coverImage: true - }); - imageLoader.lazyChildren(itemsContainer); - }); - } - - function loadSuggestionsTab(page, tabContent, parentId) { - console.debug('loadSuggestionsTab'); - loadLatest(tabContent, parentId); - loadRecentlyPlayed(tabContent, parentId); - loadFrequentlyPlayed(tabContent, parentId); - - import('../../components/favoriteitems').then(({ default: favoriteItems }) => { - favoriteItems.render(tabContent, ApiClient.getCurrentUserId(), parentId, ['favoriteArtists', 'favoriteAlbums', 'favoriteSongs']); - }); - } - - function getTabs() { - return [{ - name: globalize.translate('Albums') - }, { - name: globalize.translate('Suggestions') - }, { - name: globalize.translate('HeaderAlbumArtists') - }, { - name: globalize.translate('Artists') - }, { - name: globalize.translate('Playlists') - }, { - name: globalize.translate('Songs') - }, { - name: globalize.translate('Genres') - }]; - } - - function getDefaultTabIndex(folderId) { - switch (userSettings.get('landing-' + folderId)) { - case 'suggestions': - return 1; - - case 'albumartists': - return 2; - - case 'artists': - return 3; - - case 'playlists': - return 4; - - case 'songs': - return 5; - - case 'genres': - return 6; - - default: - return 0; + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } } - export default function (view, params) { - function reload() { - loading.show(); - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - loadSuggestionsTab(view, tabContent, params.topParentId); + function onBeforeTabChange(e) { + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); + } + + function onTabChange(e) { + loadTab(view, parseInt(e.detail.selectedTabIndex, 10)); + } + + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + const getTabController = (page, index, callback) => { + let depends; + + switch (index) { + case 0: + depends = 'musicalbums'; + break; + + case 1: + depends = 'musicrecommended'; + break; + + case 2: + case 3: + depends = 'musicartists'; + break; + + case 4: + depends = 'musicplaylists'; + break; + + case 5: + depends = 'songs'; + break; + + case 6: + depends = 'musicgenres'; + break; } - function setScrollClasses(elem, scrollX) { - if (scrollX) { - elem.classList.add('hiddenScrollX'); + import(`../music/${depends}`).then(({ default: controllerFactory }) => { + let tabContent; - if (layoutManager.tv) { - elem.classList.add('smoothScrollX'); - } - - elem.classList.add('scrollX'); - elem.classList.remove('vertical-wrap'); - } else { - elem.classList.remove('hiddenScrollX'); - elem.classList.remove('smoothScrollX'); - elem.classList.remove('scrollX'); - elem.classList.add('vertical-wrap'); - } - } - - function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function onTabChange(e) { - loadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function getTabContainers() { - return view.querySelectorAll('.pageTabContent'); - } - - function initTabs() { - mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); - } - - const getTabController = (page, index, callback) => { - let depends; - - switch (index) { - case 0: - depends = 'musicalbums'; - break; - - case 1: - depends = 'musicrecommended'; - break; - - case 2: - case 3: - depends = 'musicartists'; - break; - - case 4: - depends = 'musicplaylists'; - break; - - case 5: - depends = 'songs'; - break; - - case 6: - depends = 'musicgenres'; - break; + if (index == 1) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + this.tabContent = tabContent; } - import(`../music/${depends}`).then(({ default: controllerFactory }) => { - let tabContent; + let controller = tabControllers[index]; - if (index == 1) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - this.tabContent = tabContent; - } + if (!controller) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - let controller = tabControllers[index]; - - if (!controller) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - - if (index === 1) { - controller = this; - } else { - controller = new controllerFactory(view, params, tabContent); - } - - if (index == 2) { - controller.mode = 'albumartists'; - } else if (index == 3) { - controller.mode = 'artists'; - } - - tabControllers[index] = controller; - if (controller.initTab) { - controller.initTab(); - } - } - - callback(controller); - }); - }; - - function preLoadTab(page, index) { - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1 && controller.preRender) { - controller.preRender(); - } - }); - } - - function loadTab(page, index) { - currentTabIndex = index; - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1) { - renderedTabs.push(index); - controller.renderTab(); - } - }); - } - - function onInputCommand(e) { - if (e.detail.command === 'search') { - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); - } - } - - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); - const suggestionsTabIndex = 1; - - this.initTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - const containers = tabContent.querySelectorAll('.itemsContainer'); - - for (let i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], browser.mobile); - } - }; - - this.renderTab = function () { - reload(); - }; - - const tabControllers = []; - const renderedTabs = []; - view.addEventListener('viewshow', function () { - initTabs(); - if (!view.getAttribute('data-title')) { - const parentId = params.topParentId; - - if (parentId) { - ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute('data-title', item.Name); - libraryMenu.setTitle(item.Name); - }); + if (index === 1) { + controller = this; } else { - view.setAttribute('data-title', globalize.translate('TabMusic')); - libraryMenu.setTitle(globalize.translate('TabMusic')); + controller = new controllerFactory(view, params, tabContent); + } + + if (index == 2) { + controller.mode = 'albumartists'; + } else if (index == 3) { + controller.mode = 'artists'; + } + + tabControllers[index] = controller; + if (controller.initTab) { + controller.initTab(); } } - inputManager.on(window, onInputCommand); + callback(controller); }); - view.addEventListener('viewbeforehide', function () { - inputManager.off(window, onInputCommand); - }); - view.addEventListener('viewdestroy', function () { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + }; + + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1 && controller.preRender) { + controller.preRender(); + } }); } -/* eslint-enable indent */ + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1) { + renderedTabs.push(index); + controller.renderTab(); + } + }); + } + + function onInputCommand(e) { + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); + } + } + + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); + const suggestionsTabIndex = 1; + + this.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + const containers = tabContent.querySelectorAll('.itemsContainer'); + + for (let i = 0, length = containers.length; i < length; i++) { + setScrollClasses(containers[i], browser.mobile); + } + }; + + this.renderTab = function () { + reload(); + }; + + const tabControllers = []; + const renderedTabs = []; + view.addEventListener('viewshow', function () { + initTabs(); + if (!view.getAttribute('data-title')) { + const parentId = params.topParentId; + + if (parentId) { + ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { + view.setAttribute('data-title', item.Name); + libraryMenu.setTitle(item.Name); + }); + } else { + view.setAttribute('data-title', globalize.translate('TabMusic')); + libraryMenu.setTitle(globalize.translate('TabMusic')); + } + } + + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + inputManager.off(window, onInputCommand); + }); + view.addEventListener('viewdestroy', function () { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); + }); +} + diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html index 002a818314..ca49f872c9 100644 --- a/src/controllers/playback/video/index.html +++ b/src/controllers/playback/video/index.html @@ -65,6 +65,9 @@
+ +
+
'; + } + + context.querySelector('#divUsers').innerHTML = html; +} + +export default function (view, params) { + function getApiClient() { + const serverId = params.serverid; + + if (serverId) { + return ServerConnections.getOrCreateApiClient(serverId); + } + + return ApiClient; + } + + function showVisualForm() { + view.querySelector('.visualLoginForm').classList.remove('hide'); + view.querySelector('.manualLoginForm').classList.add('hide'); + view.querySelector('.btnManual').classList.remove('hide'); + + import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(view); }); } - function onLoginSuccessful(id, accessToken, apiClient) { - Dashboard.onServerChanged(id, accessToken, apiClient); - Dashboard.navigate('home.html'); - } + view.querySelector('#divUsers').addEventListener('click', function (e) { + const card = dom.parentWithClass(e.target, 'card'); + const cardContent = card ? card.querySelector('.cardContent') : null; - function showManualForm(context, showCancel, focusPassword) { - context.querySelector('.chkRememberLogin').checked = appSettings.enableAutoLogin(); - context.querySelector('.manualLoginForm').classList.remove('hide'); - context.querySelector('.visualLoginForm').classList.add('hide'); - context.querySelector('.btnManual').classList.add('hide'); + if (cardContent) { + const context = view; + const id = cardContent.getAttribute('data-userid'); + const name = cardContent.getAttribute('data-username'); + const haspw = cardContent.getAttribute('data-haspw'); - if (focusPassword) { - context.querySelector('#txtManualPassword').focus(); - } else { - context.querySelector('#txtManualName').focus(); - } - - if (showCancel) { - context.querySelector('.btnCancel').classList.remove('hide'); - } else { - context.querySelector('.btnCancel').classList.add('hide'); - } - } - - function loadUserList(context, apiClient, users) { - let html = ''; - - for (let i = 0; i < users.length; i++) { - const user = users[i]; - - // TODO move card creation code to Card component - let cssClass = 'card squareCard scalableCard squareCard-scalable'; - - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - const cardBoxCssClass = 'cardBox cardBox-bottompadded'; - html += ''; + view.addEventListener('viewshow', function () { + loading.show(); + libraryMenu.setTransparentMenu(true); + + if (!appHost.supports('multiserver')) { + view.querySelector('.btnSelectServer').classList.add('hide'); } - context.querySelector('#divUsers').innerHTML = html; - } + const apiClient = getApiClient(); - export default function (view, params) { - function getApiClient() { - const serverId = params.serverid; - - if (serverId) { - return ServerConnections.getOrCreateApiClient(serverId); - } - - return ApiClient; - } - - function showVisualForm() { - view.querySelector('.visualLoginForm').classList.remove('hide'); - view.querySelector('.manualLoginForm').classList.add('hide'); - view.querySelector('.btnManual').classList.remove('hide'); - - import('../../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(view); + apiClient.getQuickConnect('Enabled') + .then(enabled => { + if (enabled === true) { + view.querySelector('.btnQuick').classList.remove('hide'); + } + }) + .catch(() => { + console.debug('Failed to get QuickConnect status'); }); - } - view.querySelector('#divUsers').addEventListener('click', function (e) { - const card = dom.parentWithClass(e.target, 'card'); - const cardContent = card ? card.querySelector('.cardContent') : null; + apiClient.getPublicUsers().then(function (users) { + if (users.length) { + showVisualForm(); + loadUserList(view, apiClient, users); + } else { + view.querySelector('#txtManualName').value = ''; + showManualForm(view, false, false); + } + }).catch().then(function () { + loading.hide(); + }); + apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { + const loginDisclaimer = view.querySelector('.loginDisclaimer'); - if (cardContent) { - const context = view; - const id = cardContent.getAttribute('data-userid'); - const name = cardContent.getAttribute('data-username'); - const haspw = cardContent.getAttribute('data-haspw'); + loginDisclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || '')); - if (id === 'manual') { - context.querySelector('#txtManualName').value = ''; - showManualForm(context, true); - } else if (haspw == 'false') { - authenticateUserByName(context, getApiClient(), name, ''); - } else { - context.querySelector('#txtManualName').value = name; - context.querySelector('#txtManualPassword').value = ''; - showManualForm(context, true, true); + for (const elem of loginDisclaimer.querySelectorAll('a')) { + elem.rel = 'noopener noreferrer'; + elem.target = '_blank'; + elem.classList.add('button-link'); + elem.setAttribute('is', 'emby-linkbutton'); + + if (layoutManager.tv) { + // Disable links navigation on TV + elem.tabIndex = -1; } } }); - view.querySelector('.manualLoginForm').addEventListener('submit', function (e) { - appSettings.enableAutoLogin(view.querySelector('.chkRememberLogin').checked); - const apiClient = getApiClient(); - authenticateUserByName(view, apiClient, view.querySelector('#txtManualName').value, view.querySelector('#txtManualPassword').value); - e.preventDefault(); - return false; - }); - view.querySelector('.btnForgotPassword').addEventListener('click', function () { - Dashboard.navigate('forgotpassword.html'); - }); - view.querySelector('.btnCancel').addEventListener('click', showVisualForm); - view.querySelector('.btnQuick').addEventListener('click', function () { - const apiClient = getApiClient(); - authenticateQuickConnect(apiClient); - return false; - }); - view.querySelector('.btnManual').addEventListener('click', function () { - view.querySelector('#txtManualName').value = ''; - showManualForm(view, true); - }); - view.querySelector('.btnSelectServer').addEventListener('click', function () { - Dashboard.selectServer(); - }); + }); + view.addEventListener('viewhide', function () { + libraryMenu.setTransparentMenu(false); + }); +} - view.addEventListener('viewshow', function () { - loading.show(); - libraryMenu.setTransparentMenu(true); - - if (!appHost.supports('multiserver')) { - view.querySelector('.btnSelectServer').classList.add('hide'); - } - - const apiClient = getApiClient(); - - apiClient.getQuickConnect('Enabled') - .then(enabled => { - if (enabled === true) { - view.querySelector('.btnQuick').classList.remove('hide'); - } - }) - .catch(() => { - console.debug('Failed to get QuickConnect status'); - }); - - apiClient.getPublicUsers().then(function (users) { - if (users.length) { - showVisualForm(); - loadUserList(view, apiClient, users); - } else { - view.querySelector('#txtManualName').value = ''; - showManualForm(view, false, false); - } - }).catch().then(function () { - loading.hide(); - }); - apiClient.getJSON(apiClient.getUrl('Branding/Configuration')).then(function (options) { - const loginDisclaimer = view.querySelector('.loginDisclaimer'); - - loginDisclaimer.innerHTML = DOMPurify.sanitize(marked(options.LoginDisclaimer || '')); - - for (const elem of loginDisclaimer.querySelectorAll('a')) { - elem.rel = 'noopener noreferrer'; - elem.target = '_blank'; - elem.classList.add('button-link'); - elem.setAttribute('is', 'emby-linkbutton'); - - if (layoutManager.tv) { - // Disable links navigation on TV - elem.tabIndex = -1; - } - } - }); - }); - view.addEventListener('viewhide', function () { - libraryMenu.setTransparentMenu(false); - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/session/resetPassword/index.js b/src/controllers/session/resetPassword/index.js index ef311ff88c..10a721ec61 100644 --- a/src/controllers/session/resetPassword/index.js +++ b/src/controllers/session/resetPassword/index.js @@ -1,46 +1,43 @@ import globalize from '../../../scripts/globalize'; import Dashboard from '../../../utils/dashboard'; -/* eslint-disable indent */ - - function processForgotPasswordResult(result) { - if (result.Success) { - let msg = globalize.translate('MessagePasswordResetForUsers'); - msg += '
'; - msg += '
'; - msg += result.UsersReset.join('
'); - Dashboard.alert({ - message: msg, - title: globalize.translate('HeaderPasswordReset'), - callback: function () { - window.location.href = 'index.html'; - } - }); - return; - } - +function processForgotPasswordResult(result) { + if (result.Success) { + let msg = globalize.translate('MessagePasswordResetForUsers'); + msg += '
'; + msg += '
'; + msg += result.UsersReset.join('
'); Dashboard.alert({ - message: globalize.translate('MessageInvalidForgotPasswordPin'), - title: globalize.translate('HeaderPasswordReset') + message: msg, + title: globalize.translate('HeaderPasswordReset'), + callback: function () { + window.location.href = 'index.html'; + } }); + return; } - export default function (view) { - function onSubmit(e) { - ApiClient.ajax({ - type: 'POST', - url: ApiClient.getUrl('Users/ForgotPassword/Pin'), - dataType: 'json', - data: JSON.stringify({ - Pin: view.querySelector('#txtPin').value - }), - contentType: 'application/json' - }).then(processForgotPasswordResult); - e.preventDefault(); - return false; - } + Dashboard.alert({ + message: globalize.translate('MessageInvalidForgotPasswordPin'), + title: globalize.translate('HeaderPasswordReset') + }); +} - view.querySelector('form').addEventListener('submit', onSubmit); +export default function (view) { + function onSubmit(e) { + ApiClient.ajax({ + type: 'POST', + url: ApiClient.getUrl('Users/ForgotPassword/Pin'), + dataType: 'json', + data: JSON.stringify({ + Pin: view.querySelector('#txtPin').value + }), + contentType: 'application/json' + }).then(processForgotPasswordResult); + e.preventDefault(); + return false; } -/* eslint-enable indent */ + view.querySelector('form').addEventListener('submit', onSubmit); +} + diff --git a/src/controllers/session/selectServer/index.js b/src/controllers/session/selectServer/index.js index b45cb71060..8d3edbf3cb 100644 --- a/src/controllers/session/selectServer/index.js +++ b/src/controllers/session/selectServer/index.js @@ -21,196 +21,193 @@ import alert from '../../../components/alert'; import cardBuilder from '../../../components/cardbuilder/cardBuilder'; import { ConnectionState } from '../../../utils/jellyfin-apiclient/ConnectionState.ts'; -/* eslint-disable indent */ +const enableFocusTransform = !browser.slow && !browser.edge; - const enableFocusTransform = !browser.slow && !browser.edge; +function renderSelectServerItems(view, servers) { + const items = servers.map(function (server) { + return { + name: server.Name, + icon: 'storage', + cardType: '', + id: server.Id, + server: server + }; + }); + let html = items.map(function (item) { + // TODO move card creation code to Card component + const cardImageContainer = ''; + let cssClass = 'card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable'; - function renderSelectServerItems(view, servers) { - const items = servers.map(function (server) { - return { - name: server.Name, - icon: 'storage', - cardType: '', - id: server.Id, - server: server - }; - }); - let html = items.map(function (item) { - // TODO move card creation code to Card component - const cardImageContainer = ''; - let cssClass = 'card overflowSquareCard loginSquareCard scalableCard overflowSquareCard-scalable'; + if (layoutManager.tv) { + cssClass += ' show-focus'; - if (layoutManager.tv) { - cssClass += ' show-focus'; - - if (enableFocusTransform) { - cssClass += ' show-animation'; - } - } - - const cardBoxCssClass = 'cardBox'; - - const innerOpening = '
'; - let cardContainer = ''; - cardContainer += '
'; - return cardContainer; - }).join(''); - const itemsContainer = view.querySelector('.servers'); - - if (!items.length) { - html = '

' + globalize.translate('MessageNoServersAvailable') + '

'; - } - - itemsContainer.innerHTML = html; - loading.hide(); - } - - function updatePageStyle(view, params) { - if (params.showuser == '1') { - view.classList.add('libraryPage'); - view.classList.remove('standalonePage'); - view.classList.add('noSecondaryNavPage'); - } else { - view.classList.add('standalonePage'); - view.classList.remove('libraryPage'); - view.classList.remove('noSecondaryNavPage'); - } - } - - function alertText(text) { - alertTextWithOptions({ - text: text - }); - } - - function alertTextWithOptions(options) { - alert(options); - } - - function showServerConnectionFailure() { - alertText(globalize.translate('MessageUnableToConnectToServer')); - } - - export default function (view, params) { - function connectToServer(server) { - loading.show(); - ServerConnections.connectToServer(server, { - enableAutoLogin: appSettings.enableAutoLogin() - }).then(function (result) { - loading.hide(); - const apiClient = result.ApiClient; - - switch (result.State) { - case ConnectionState.SignedIn: - Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); - Dashboard.navigate('home.html'); - break; - - case ConnectionState.ServerSignIn: - Dashboard.onServerChanged(null, null, apiClient); - Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id); - break; - - case ConnectionState.ServerUpdateNeeded: - alertTextWithOptions({ - text: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), - html: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') - }); - break; - - default: - showServerConnectionFailure(); - } - }); - } - - function deleteServer(server) { - loading.show(); - ServerConnections.deleteServer(server.Id).then(function () { - loading.hide(); - loadServers(); - }); - } - - function onServerClick(server) { - const menuItems = []; - menuItems.push({ - name: globalize.translate('Connect'), - id: 'connect' - }); - menuItems.push({ - name: globalize.translate('Delete'), - id: 'delete' - }); - actionSheet.show({ - items: menuItems, - title: server.Name - }).then(function (id) { - switch (id) { - case 'connect': - connectToServer(server); - break; - - case 'delete': - deleteServer(server); - } - }); - } - - function onServersRetrieved(result) { - servers = result; - renderSelectServerItems(view, result); - - if (layoutManager.tv) { - focusManager.autoFocus(view); + if (enableFocusTransform) { + cssClass += ' show-animation'; } } - function loadServers() { - loading.show(); - ServerConnections.getAvailableServers().then(onServersRetrieved); - } + const cardBoxCssClass = 'cardBox'; - let servers; - updatePageStyle(view, params); - view.addEventListener('viewshow', function (e) { - const isRestored = e.detail.isRestored; - libraryMenu.setTitle(null); - libraryMenu.setTransparentMenu(true); + const innerOpening = '
'; + let cardContainer = ''; + cardContainer += '
'; + return cardContainer; + }).join(''); + const itemsContainer = view.querySelector('.servers'); - if (!isRestored) { - loadServers(); - } - }); - view.querySelector('.servers').addEventListener('click', function (e) { - const card = dom.parentWithClass(e.target, 'card'); + if (!items.length) { + html = '

' + globalize.translate('MessageNoServersAvailable') + '

'; + } - if (card) { - const url = card.getAttribute('data-url'); + itemsContainer.innerHTML = html; + loading.hide(); +} - if (url) { - appRouter.show(url); - } else { - const id = card.getAttribute('data-id'); - onServerClick(servers.filter(function (s) { - return s.Id === id; - })[0]); - } +function updatePageStyle(view, params) { + if (params.showuser == '1') { + view.classList.add('libraryPage'); + view.classList.remove('standalonePage'); + view.classList.add('noSecondaryNavPage'); + } else { + view.classList.add('standalonePage'); + view.classList.remove('libraryPage'); + view.classList.remove('noSecondaryNavPage'); + } +} + +function alertText(text) { + alertTextWithOptions({ + text: text + }); +} + +function alertTextWithOptions(options) { + alert(options); +} + +function showServerConnectionFailure() { + alertText(globalize.translate('MessageUnableToConnectToServer')); +} + +export default function (view, params) { + function connectToServer(server) { + loading.show(); + ServerConnections.connectToServer(server, { + enableAutoLogin: appSettings.enableAutoLogin() + }).then(function (result) { + loading.hide(); + const apiClient = result.ApiClient; + + switch (result.State) { + case ConnectionState.SignedIn: + Dashboard.onServerChanged(apiClient.getCurrentUserId(), apiClient.accessToken(), apiClient); + Dashboard.navigate('home.html'); + break; + + case ConnectionState.ServerSignIn: + Dashboard.onServerChanged(null, null, apiClient); + Dashboard.navigate('login.html?serverid=' + result.Servers[0].Id); + break; + + case ConnectionState.ServerUpdateNeeded: + alertTextWithOptions({ + text: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'), + html: globalize.translate('core#ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin') + }); + break; + + default: + showServerConnectionFailure(); } }); } -/* eslint-enable indent */ + function deleteServer(server) { + loading.show(); + ServerConnections.deleteServer(server.Id).then(function () { + loading.hide(); + loadServers(); + }); + } + + function onServerClick(server) { + const menuItems = []; + menuItems.push({ + name: globalize.translate('Connect'), + id: 'connect' + }); + menuItems.push({ + name: globalize.translate('Delete'), + id: 'delete' + }); + actionSheet.show({ + items: menuItems, + title: server.Name + }).then(function (id) { + switch (id) { + case 'connect': + connectToServer(server); + break; + + case 'delete': + deleteServer(server); + } + }); + } + + function onServersRetrieved(result) { + servers = result; + renderSelectServerItems(view, result); + + if (layoutManager.tv) { + focusManager.autoFocus(view); + } + } + + function loadServers() { + loading.show(); + ServerConnections.getAvailableServers().then(onServersRetrieved); + } + + let servers; + updatePageStyle(view, params); + view.addEventListener('viewshow', function (e) { + const isRestored = e.detail.isRestored; + libraryMenu.setTitle(null); + libraryMenu.setTransparentMenu(true); + + if (!isRestored) { + loadServers(); + } + }); + view.querySelector('.servers').addEventListener('click', function (e) { + const card = dom.parentWithClass(e.target, 'card'); + + if (card) { + const url = card.getAttribute('data-url'); + + if (url) { + appRouter.show(url); + } else { + const id = card.getAttribute('data-id'); + onServerClick(servers.filter(function (s) { + return s.Id === id; + })[0]); + } + } + }); +} + diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 2cbe4117f6..38b7d077c3 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -10,243 +10,240 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SeriesSortName,SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Episode', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount', + IsMissing: false, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SeriesSortName,SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Episode', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount', - IsMissing: false, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); - } - - return context.savedQueryKey; - } - - function onViewStyleChange() { - const viewStyle = self.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - } - - function reloadItems(page) { - loading.show(); - isLoading = true; - const query = getQuery(page); - ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = self.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - sortBy: query.SortBy, - showParentTitle: true - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - showTitle: true, - showParentTitle: true, - scalable: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - showTitle: true, - showParentTitle: true, - overlayText: false, - centerText: true, - scalable: true, - overlayPlayButton: true - }); - } - let elems; - - elems = tabContent.querySelectorAll('.paging'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (let i = 0, length = elems.length; i < length; i++) { - elems[i].addEventListener('click', onPreviousPageClick); - } - - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); - } - - const self = this; - const data = {}; - let isLoading = false; - - self.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: 'episodes', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - self.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - function initPage(tabElement) { - tabElement.querySelector('.btnFilter').addEventListener('click', function () { - self.showFilterMenu(); - }); - tabElement.querySelector('.btnSort').addEventListener('click', function (e) { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SeriesSortName,SortName' - }, { - name: globalize.translate('OptionTvdbRating'), - id: 'CommunityRating,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionDateAdded'), - id: 'DateCreated,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionPremiereDate'), - id: 'PremiereDate,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionDatePlayed'), - id: 'DatePlayed,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionParentalRating'), - id: 'OfficialRating,SeriesSortName,SortName' - }, { - name: globalize.translate('OptionPlayCount'), - id: 'PlayCount,SeriesSortName,SortName' - }, { - name: globalize.translate('Runtime'), - id: 'Runtime,SeriesSortName,SortName' - }], - callback: function () { - reloadItems(tabElement); - }, - query: getQuery(tabElement), - button: e.target - }); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', function (e) { - libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - onViewStyleChange(); - reloadItems(tabElement); - }); - } - - initPage(tabContent); - onViewStyleChange(); - - self.renderTab = function () { - reloadItems(tabContent); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + function getSavedQueryKey(context) { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey('episodes'); + } + + return context.savedQueryKey; + } + + function onViewStyleChange() { + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + } + + function reloadItems(page) { + loading.show(); + isLoading = true; + const query = getQuery(page); + ApiClient.getItems(Dashboard.getCurrentUserId(), query).then(function (result) { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = self.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + sortBy: query.SortBy, + showParentTitle: true + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + showTitle: true, + showParentTitle: true, + scalable: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + showTitle: true, + showParentTitle: true, + overlayText: false, + centerText: true, + scalable: true, + overlayPlayButton: true + }); + } + let elems; + + elems = tabContent.querySelectorAll('.paging'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (let i = 0, length = elems.length; i < length; i++) { + elems[i].addEventListener('click', onPreviousPageClick); + } + + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + } + + const self = this; + const data = {}; + let isLoading = false; + + self.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: 'episodes', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + self.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + function initPage(tabElement) { + tabElement.querySelector('.btnFilter').addEventListener('click', function () { + self.showFilterMenu(); + }); + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SeriesSortName,SortName' + }, { + name: globalize.translate('OptionTvdbRating'), + id: 'CommunityRating,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionDateAdded'), + id: 'DateCreated,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionPremiereDate'), + id: 'PremiereDate,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'DatePlayed,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SeriesSortName,SortName' + }, { + name: globalize.translate('OptionPlayCount'), + id: 'PlayCount,SeriesSortName,SortName' + }, { + name: globalize.translate('Runtime'), + id: 'Runtime,SeriesSortName,SortName' + }], + callback: function () { + reloadItems(tabElement); + }, + query: getQuery(tabElement), + button: e.target + }); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', function (e) { + libraryBrowser.showLayoutMenu(e.target, self.getCurrentViewStyle(), 'List,Poster,PosterCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + onViewStyleChange(); + reloadItems(tabElement); + }); + } + + initPage(tabContent); + onViewStyleChange(); + + self.renderTab = function () { + reloadItems(tabContent); + }; +} + diff --git a/src/controllers/shows/tvgenres.js b/src/controllers/shows/tvgenres.js index 7db974744b..bc27ad4759 100644 --- a/src/controllers/shows/tvgenres.js +++ b/src/controllers/shows/tvgenres.js @@ -8,213 +8,210 @@ import globalize from '../../scripts/globalize'; import { appRouter } from '../../components/appRouter'; import '../../elements/emby-button/emby-button'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData() { + const key = getSavedQueryKey(); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData() { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - EnableTotalRecordCount: false - }, - view: 'Poster' - }; - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); - } - - return pageData; - } - - function getQuery() { - return getPageData().query; - } - - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('seriesgenres'); - } - - function getPromise() { - loading.show(); - const query = getQuery(); - return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); - } - - function enableScrollX() { - return !layoutManager.desktop; - } - - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - function getPortraitShape() { - return enableScrollX() ? 'overflowPortrait' : 'portrait'; - } - - function fillItemsContainer(entry) { - const elem = entry.target; - const id = elem.getAttribute('data-id'); - const viewStyle = self.getCurrentViewStyle(); - let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; - - if (enableScrollX()) { - limit = 10; - } - - const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; - const query = { - SortBy: 'Random', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: enableImageTypes, - Limit: limit, - GenreIds: id, - EnableTotalRecordCount: false, - ParentId: params.topParentId + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + EnableTotalRecordCount: false + }, + view: 'Poster' }; - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { - if (viewStyle == 'Thumb') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - scalable: true, - centerText: true, - overlayMoreButton: true, - allowBottomPadding: false - }); - } else if (viewStyle == 'ThumbCard') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getThumbShape(), - preferThumb: true, - showTitle: true, - scalable: true, - centerText: false, - cardLayout: true, - showYear: true - }); - } else if (viewStyle == 'PosterCard') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getPortraitShape(), - showTitle: true, - scalable: true, - centerText: false, - cardLayout: true, - showYear: true - }); - } else if (viewStyle == 'Poster') { - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: getPortraitShape(), - scalable: true, - showTitle: true, - centerText: true, - showYear: true, - overlayMoreButton: true, - allowBottomPadding: false - }); - } - if (result.Items.length >= query.Limit) { - tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); - } - }); + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function reloadItems(context, promise) { - const query = getQuery(); - promise.then(function (result) { - const elem = context.querySelector('#items'); - let html = ''; - const items = result.Items; - - for (const item of items) { - html += '
'; - html += ''; - if (enableScrollX()) { - let scrollXClass = 'scrollX hiddenScrollX'; - if (layoutManager.tv) { - scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; - } - html += '
'; - } else { - html += '
'; - } - html += '
'; - 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); - loading.hide(); - }); - } - - function fullyReload() { - self.preRender(); - self.renderTab(); - } - - const self = this; - const data = {}; - - self.getViewStyles = function () { - return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); - }; - - self.getCurrentViewStyle = function () { - return getPageData().view; - }; - - self.setCurrentViewStyle = function (viewStyle) { - getPageData().view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); - fullyReload(); - }; - - self.enableViewSelection = true; - let promise; - - self.preRender = function () { - promise = getPromise(); - }; - - self.renderTab = function () { - reloadItems(tabContent, promise); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery() { + return getPageData().query; + } + + function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('seriesgenres'); + } + + function getPromise() { + loading.show(); + const query = getQuery(); + return ApiClient.getGenres(ApiClient.getCurrentUserId(), query); + } + + function enableScrollX() { + return !layoutManager.desktop; + } + + function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; + } + + function getPortraitShape() { + return enableScrollX() ? 'overflowPortrait' : 'portrait'; + } + + function fillItemsContainer(entry) { + const elem = entry.target; + const id = elem.getAttribute('data-id'); + const viewStyle = self.getCurrentViewStyle(); + let limit = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 5 : 9; + + if (enableScrollX()) { + limit = 10; + } + + const enableImageTypes = viewStyle == 'Thumb' || viewStyle == 'ThumbCard' ? 'Primary,Backdrop,Thumb' : 'Primary'; + const query = { + SortBy: 'Random', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: enableImageTypes, + Limit: limit, + GenreIds: id, + EnableTotalRecordCount: false, + ParentId: params.topParentId + }; + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then(function (result) { + if (viewStyle == 'Thumb') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: true, + overlayMoreButton: true, + allowBottomPadding: false + }); + } else if (viewStyle == 'ThumbCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getThumbShape(), + preferThumb: true, + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'PosterCard') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + showTitle: true, + scalable: true, + centerText: false, + cardLayout: true, + showYear: true + }); + } else if (viewStyle == 'Poster') { + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: getPortraitShape(), + scalable: true, + showTitle: true, + centerText: true, + showYear: true, + overlayMoreButton: true, + allowBottomPadding: false + }); + } + if (result.Items.length >= query.Limit) { + tabContent.querySelector('.btnMoreFromGenre' + id + ' .material-icons').classList.remove('hide'); + } + }); + } + + function reloadItems(context, promise) { + const query = getQuery(); + promise.then(function (result) { + const elem = context.querySelector('#items'); + let html = ''; + const items = result.Items; + + for (const item of items) { + html += '
'; + html += ''; + if (enableScrollX()) { + let scrollXClass = 'scrollX hiddenScrollX'; + if (layoutManager.tv) { + scrollXClass += 'smoothScrollX padded-top-focusscale padded-bottom-focusscale'; + } + html += '
'; + } else { + html += '
'; + } + html += '
'; + 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); + loading.hide(); + }); + } + + function fullyReload() { + self.preRender(); + self.renderTab(); + } + + const self = this; + const data = {}; + + self.getViewStyles = function () { + return 'Poster,PosterCard,Thumb,ThumbCard'.split(','); + }; + + self.getCurrentViewStyle = function () { + return getPageData().view; + }; + + self.setCurrentViewStyle = function (viewStyle) { + getPageData().view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(), viewStyle); + fullyReload(); + }; + + self.enableViewSelection = true; + let promise; + + self.preRender = function () { + promise = getPromise(); + }; + + self.renderTab = function () { + reloadItems(tabContent, promise); + }; +} + diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 0066cf48a2..944a75260c 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -16,377 +16,374 @@ import Dashboard from '../../utils/dashboard'; import Events from '../../utils/events.ts'; import autoFocuser from '../../components/autoFocuser'; -/* eslint-disable indent */ +function getTabs() { + return [{ + name: globalize.translate('Shows') + }, { + name: globalize.translate('Suggestions') + }, { + name: globalize.translate('TabUpcoming') + }, { + name: globalize.translate('Genres') + }, { + name: globalize.translate('TabNetworks') + }, { + name: globalize.translate('Episodes') + }]; +} - function getTabs() { - return [{ - name: globalize.translate('Shows') - }, { - name: globalize.translate('Suggestions') - }, { - name: globalize.translate('TabUpcoming') - }, { - name: globalize.translate('Genres') - }, { - name: globalize.translate('TabNetworks') - }, { - name: globalize.translate('Episodes') - }]; +function getDefaultTabIndex(folderId) { + switch (userSettings.get('landing-' + folderId)) { + case 'suggestions': + return 1; + + case 'upcoming': + return 2; + + case 'genres': + return 3; + + case 'networks': + return 4; + + case 'episodes': + return 5; + + default: + return 0; } +} - function getDefaultTabIndex(folderId) { - switch (userSettings.get('landing-' + folderId)) { - case 'suggestions': - return 1; +function setScrollClasses(elem, scrollX) { + if (scrollX) { + elem.classList.add('hiddenScrollX'); - case 'upcoming': - return 2; - - case 'genres': - return 3; - - case 'networks': - return 4; - - case 'episodes': - return 5; - - default: - return 0; + if (layoutManager.tv) { + elem.classList.add('smoothScrollX'); } + + elem.classList.add('scrollX'); + elem.classList.remove('vertical-wrap'); + } else { + elem.classList.remove('hiddenScrollX'); + elem.classList.remove('smoothScrollX'); + elem.classList.remove('scrollX'); + elem.classList.add('vertical-wrap'); } +} - function setScrollClasses(elem, scrollX) { - if (scrollX) { - elem.classList.add('hiddenScrollX'); +function initSuggestedTab(page, tabContent) { + const containers = tabContent.querySelectorAll('.itemsContainer'); - if (layoutManager.tv) { - elem.classList.add('smoothScrollX'); - } + for (let i = 0, length = containers.length; i < length; i++) { + setScrollClasses(containers[i], enableScrollX()); + } +} - elem.classList.add('scrollX'); - elem.classList.remove('vertical-wrap'); +function loadSuggestionsTab(view, params, tabContent) { + const parentId = params.topParentId; + const userId = ApiClient.getCurrentUserId(); + console.debug('loadSuggestionsTab'); + loadResume(tabContent, userId, parentId); + loadLatest(tabContent, userId, parentId); + loadNextUp(tabContent, userId, parentId); +} + +function loadResume(view, userId, parentId) { + const screenWidth = dom.getWindowSize().innerWidth; + const options = { + SortBy: 'DatePlayed', + SortOrder: 'Descending', + IncludeItemTypes: 'Episode', + Filters: 'IsResumable', + Limit: screenWidth >= 1600 ? 5 : 3, + Recursive: true, + Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', + CollapseBoxSetItems: false, + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + ApiClient.getItems(userId, options).then(function (result) { + if (result.Items.length) { + view.querySelector('#resumableSection').classList.remove('hide'); } else { - elem.classList.remove('hiddenScrollX'); - elem.classList.remove('smoothScrollX'); - elem.classList.remove('scrollX'); - elem.classList.add('vertical-wrap'); + view.querySelector('#resumableSection').classList.add('hide'); } - } - function initSuggestedTab(page, tabContent) { - const containers = tabContent.querySelectorAll('.itemsContainer'); + const allowBottomPadding = !enableScrollX(); + const container = view.querySelector('#resumableItems'); + cardBuilder.buildCards(result.Items, { + itemsContainer: container, + preferThumb: true, + inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), + shape: getThumbShape(), + scalable: true, + overlayPlayButton: true, + allowBottomPadding: allowBottomPadding, + cardLayout: false, + showTitle: true, + showYear: true, + centerText: true + }); + loading.hide(); - for (let i = 0, length = containers.length; i < length; i++) { - setScrollClasses(containers[i], enableScrollX()); + autoFocuser.autoFocus(view); + }); +} + +function loadLatest(view, userId, parentId) { + const options = { + userId: userId, + IncludeItemTypes: 'Episode', + Limit: 30, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb' + }; + ApiClient.getLatestItems(options).then(function (items) { + const section = view.querySelector('#latestItemsSection'); + const allowBottomPadding = !enableScrollX(); + const container = section.querySelector('#latestEpisodesItems'); + cardBuilder.buildCards(items, { + parentContainer: section, + itemsContainer: container, + items: items, + shape: 'backdrop', + preferThumb: true, + showTitle: true, + showSeriesYear: true, + showParentTitle: true, + overlayText: false, + cardLayout: false, + allowBottomPadding: allowBottomPadding, + showUnplayedIndicator: false, + showChildCountIndicator: true, + centerText: true, + lazy: true, + overlayPlayButton: true, + lines: 2 + }); + loading.hide(); + + autoFocuser.autoFocus(view); + }); +} + +function loadNextUp(view, userId, parentId) { + const query = { + userId: userId, + Limit: 24, + Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', + ParentId: parentId, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + EnableTotalRecordCount: false + }; + query.ParentId = libraryMenu.getTopParentId(); + ApiClient.getNextUpEpisodes(query).then(function (result) { + if (result.Items.length) { + view.querySelector('.noNextUpItems').classList.add('hide'); + } else { + view.querySelector('.noNextUpItems').classList.remove('hide'); } + + const section = view.querySelector('#nextUpItemsSection'); + const container = section.querySelector('#nextUpItems'); + cardBuilder.buildCards(result.Items, { + parentContainer: section, + itemsContainer: container, + preferThumb: true, + inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), + shape: 'backdrop', + scalable: true, + showTitle: true, + showParentTitle: true, + overlayText: false, + centerText: true, + overlayPlayButton: true, + cardLayout: false + }); + loading.hide(); + + autoFocuser.autoFocus(view); + }); +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +export default function (view, params) { + function onBeforeTabChange(e) { + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); } - function loadSuggestionsTab(view, params, tabContent) { - const parentId = params.topParentId; - const userId = ApiClient.getCurrentUserId(); - console.debug('loadSuggestionsTab'); - loadResume(tabContent, userId, parentId); - loadLatest(tabContent, userId, parentId); - loadNextUp(tabContent, userId, parentId); + function onTabChange(e) { + const newIndex = parseInt(e.detail.selectedTabIndex, 10); + loadTab(view, newIndex); } - function loadResume(view, userId, parentId) { - const screenWidth = dom.getWindowSize().innerWidth; - const options = { - SortBy: 'DatePlayed', - SortOrder: 'Descending', - IncludeItemTypes: 'Episode', - Filters: 'IsResumable', - Limit: screenWidth >= 1600 ? 5 : 3, - Recursive: true, - Fields: 'PrimaryImageAspectRatio,MediaSourceCount,BasicSyncInfo', - CollapseBoxSetItems: false, - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - ApiClient.getItems(userId, options).then(function (result) { - if (result.Items.length) { - view.querySelector('#resumableSection').classList.remove('hide'); - } else { - view.querySelector('#resumableSection').classList.add('hide'); + function getTabContainers() { + return view.querySelectorAll('.pageTabContent'); + } + + function initTabs() { + mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); + } + + function getTabController(page, index, callback) { + let depends; + + switch (index) { + case 0: + depends = 'tvshows'; + break; + + case 1: + depends = 'tvrecommended'; + break; + + case 2: + depends = 'tvupcoming'; + break; + + case 3: + depends = 'tvgenres'; + break; + + case 4: + depends = 'tvstudios'; + break; + + case 5: + depends = 'episodes'; + break; + } + + import(`../shows/${depends}`).then(({ default: controllerFactory }) => { + let tabContent; + + if (index === 1) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); + self.tabContent = tabContent; } - const allowBottomPadding = !enableScrollX(); - const container = view.querySelector('#resumableItems'); - cardBuilder.buildCards(result.Items, { - itemsContainer: container, - preferThumb: true, - inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), - shape: getThumbShape(), - scalable: true, - overlayPlayButton: true, - allowBottomPadding: allowBottomPadding, - cardLayout: false, - showTitle: true, - showYear: true, - centerText: true - }); - loading.hide(); + let controller = tabControllers[index]; - autoFocuser.autoFocus(view); - }); - } - - function loadLatest(view, userId, parentId) { - const options = { - userId: userId, - IncludeItemTypes: 'Episode', - Limit: 30, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb' - }; - ApiClient.getLatestItems(options).then(function (items) { - const section = view.querySelector('#latestItemsSection'); - const allowBottomPadding = !enableScrollX(); - const container = section.querySelector('#latestEpisodesItems'); - cardBuilder.buildCards(items, { - parentContainer: section, - itemsContainer: container, - items: items, - shape: 'backdrop', - preferThumb: true, - showTitle: true, - showSeriesYear: true, - showParentTitle: true, - overlayText: false, - cardLayout: false, - allowBottomPadding: allowBottomPadding, - showUnplayedIndicator: false, - showChildCountIndicator: true, - centerText: true, - lazy: true, - overlayPlayButton: true, - lines: 2 - }); - loading.hide(); - - autoFocuser.autoFocus(view); - }); - } - - function loadNextUp(view, userId, parentId) { - const query = { - userId: userId, - Limit: 24, - Fields: 'PrimaryImageAspectRatio,DateCreated,BasicSyncInfo,MediaSourceCount', - ParentId: parentId, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - EnableTotalRecordCount: false - }; - query.ParentId = libraryMenu.getTopParentId(); - ApiClient.getNextUpEpisodes(query).then(function (result) { - if (result.Items.length) { - view.querySelector('.noNextUpItems').classList.add('hide'); - } else { - view.querySelector('.noNextUpItems').classList.remove('hide'); - } - - const section = view.querySelector('#nextUpItemsSection'); - const container = section.querySelector('#nextUpItems'); - cardBuilder.buildCards(result.Items, { - parentContainer: section, - itemsContainer: container, - preferThumb: true, - inheritThumb: !userSettings.useEpisodeImagesInNextUpAndResume(), - shape: 'backdrop', - scalable: true, - showTitle: true, - showParentTitle: true, - overlayText: false, - centerText: true, - overlayPlayButton: true, - cardLayout: false - }); - loading.hide(); - - autoFocuser.autoFocus(view); - }); - } - - function enableScrollX() { - return !layoutManager.desktop; - } - - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } - - export default function (view, params) { - function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); - } - - function onTabChange(e) { - const newIndex = parseInt(e.detail.selectedTabIndex, 10); - loadTab(view, newIndex); - } - - function getTabContainers() { - return view.querySelectorAll('.pageTabContent'); - } - - function initTabs() { - mainTabsManager.setTabs(view, currentTabIndex, getTabs, getTabContainers, onBeforeTabChange, onTabChange); - } - - function getTabController(page, index, callback) { - let depends; - - switch (index) { - case 0: - depends = 'tvshows'; - break; - - case 1: - depends = 'tvrecommended'; - break; - - case 2: - depends = 'tvupcoming'; - break; - - case 3: - depends = 'tvgenres'; - break; - - case 4: - depends = 'tvstudios'; - break; - - case 5: - depends = 'episodes'; - break; - } - - import(`../shows/${depends}`).then(({ default: controllerFactory }) => { - let tabContent; + if (!controller) { + tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); if (index === 1) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - self.tabContent = tabContent; - } - - let controller = tabControllers[index]; - - if (!controller) { - tabContent = view.querySelector(".pageTabContent[data-index='" + index + "']"); - - if (index === 1) { - controller = self; - } else { - controller = new controllerFactory(view, params, tabContent); - } - - tabControllers[index] = controller; - - if (controller.initTab) { - controller.initTab(); - } - } - - callback(controller); - }); - } - - function preLoadTab(page, index) { - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1 && controller.preRender) { - controller.preRender(); - } - }); - } - - function loadTab(page, index) { - currentTabIndex = index; - getTabController(page, index, function (controller) { - if (renderedTabs.indexOf(index) == -1) { - renderedTabs.push(index); - controller.renderTab(); - } - }); - } - - function onPlaybackStop(e, state) { - if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { - renderedTabs = []; - mainTabsManager.getTabsElement().triggerTabChange(); - } - } - - function onWebSocketMessage(e, data) { - const msg = data; - - if (msg.MessageType === 'UserDataChanged' && msg.Data.UserId == ApiClient.getCurrentUserId()) { - renderedTabs = []; - } - } - - function onInputCommand(e) { - if (e.detail.command === 'search') { - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); - } - } - - const self = this; - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); - const suggestionsTabIndex = 1; - - self.initTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - initSuggestedTab(view, tabContent); - }; - - self.renderTab = function () { - const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); - loadSuggestionsTab(view, params, tabContent); - }; - - const tabControllers = []; - let renderedTabs = []; - view.addEventListener('viewshow', function () { - initTabs(); - if (!view.getAttribute('data-title')) { - const parentId = params.topParentId; - - if (parentId) { - ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { - view.setAttribute('data-title', item.Name); - libraryMenu.setTitle(item.Name); - }); + controller = self; } else { - view.setAttribute('data-title', globalize.translate('Shows')); - libraryMenu.setTitle(globalize.translate('Shows')); + controller = new controllerFactory(view, params, tabContent); + } + + tabControllers[index] = controller; + + if (controller.initTab) { + controller.initTab(); } } - Events.on(playbackManager, 'playbackstop', onPlaybackStop); - Events.on(ApiClient, 'message', onWebSocketMessage); - inputManager.on(window, onInputCommand); - }); - view.addEventListener('viewbeforehide', function () { - inputManager.off(window, onInputCommand); - Events.off(playbackManager, 'playbackstop', onPlaybackStop); - Events.off(ApiClient, 'message', onWebSocketMessage); - }); - view.addEventListener('viewdestroy', function () { - tabControllers.forEach(function (t) { - if (t.destroy) { - t.destroy(); - } - }); + callback(controller); }); } -/* eslint-enable indent */ + function preLoadTab(page, index) { + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1 && controller.preRender) { + controller.preRender(); + } + }); + } + + function loadTab(page, index) { + currentTabIndex = index; + getTabController(page, index, function (controller) { + if (renderedTabs.indexOf(index) == -1) { + renderedTabs.push(index); + controller.renderTab(); + } + }); + } + + function onPlaybackStop(e, state) { + if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video') { + renderedTabs = []; + mainTabsManager.getTabsElement().triggerTabChange(); + } + } + + function onWebSocketMessage(e, data) { + const msg = data; + + if (msg.MessageType === 'UserDataChanged' && msg.Data.UserId == ApiClient.getCurrentUserId()) { + renderedTabs = []; + } + } + + function onInputCommand(e) { + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); + } + } + + const self = this; + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); + const suggestionsTabIndex = 1; + + self.initTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + initSuggestedTab(view, tabContent); + }; + + self.renderTab = function () { + const tabContent = view.querySelector(".pageTabContent[data-index='" + suggestionsTabIndex + "']"); + loadSuggestionsTab(view, params, tabContent); + }; + + const tabControllers = []; + let renderedTabs = []; + view.addEventListener('viewshow', function () { + initTabs(); + if (!view.getAttribute('data-title')) { + const parentId = params.topParentId; + + if (parentId) { + ApiClient.getItem(ApiClient.getCurrentUserId(), parentId).then(function (item) { + view.setAttribute('data-title', item.Name); + libraryMenu.setTitle(item.Name); + }); + } else { + view.setAttribute('data-title', globalize.translate('Shows')); + libraryMenu.setTitle(globalize.translate('Shows')); + } + } + + Events.on(playbackManager, 'playbackstop', onPlaybackStop); + Events.on(ApiClient, 'message', onWebSocketMessage); + inputManager.on(window, onInputCommand); + }); + view.addEventListener('viewbeforehide', function () { + inputManager.off(window, onInputCommand); + Events.off(playbackManager, 'playbackstop', onPlaybackStop); + Events.off(ApiClient, 'message', onWebSocketMessage); + }); + view.addEventListener('viewdestroy', function () { + tabControllers.forEach(function (t) { + if (t.destroy) { + t.destroy(); + } + }); + }); +} + diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index 296746613e..d378cd34d5 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -10,300 +10,297 @@ import Events from '../../utils/events.ts'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +export default function (view, params, tabContent) { + function getPageData(context) { + const key = getSavedQueryKey(context); + let pageData = data[key]; - export default function (view, params, tabContent) { - function getPageData(context) { - const key = getSavedQueryKey(context); - let pageData = data[key]; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + StartIndex: 0 + }, + view: libraryBrowser.getSavedView(key) || 'Poster' + }; - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'PrimaryImageAspectRatio,BasicSyncInfo', - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - StartIndex: 0 - }, - view: libraryBrowser.getSavedView(key) || 'Poster' - }; - - if (userSettings.libraryPageSize() > 0) { - pageData.query['Limit'] = userSettings.libraryPageSize(); - } - - pageData.query.ParentId = params.topParentId; - libraryBrowser.loadSavedQueryValues(key, pageData.query); + if (userSettings.libraryPageSize() > 0) { + pageData.query['Limit'] = userSettings.libraryPageSize(); } - return pageData; + pageData.query.ParentId = params.topParentId; + libraryBrowser.loadSavedQueryValues(key, pageData.query); } - function getQuery(context) { - return getPageData(context).query; - } - - function getSavedQueryKey(context) { - if (!context.savedQueryKey) { - context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); - } - - return context.savedQueryKey; - } - - const onViewStyleChange = () => { - const viewStyle = this.getCurrentViewStyle(); - const itemsContainer = tabContent.querySelector('.itemsContainer'); - - if (viewStyle == 'List') { - itemsContainer.classList.add('vertical-list'); - itemsContainer.classList.remove('vertical-wrap'); - } else { - itemsContainer.classList.remove('vertical-list'); - itemsContainer.classList.add('vertical-wrap'); - } - - itemsContainer.innerHTML = ''; - }; - - const reloadItems = (page) => { - loading.show(); - isLoading = true; - const query = getQuery(page); - ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { - function onNextPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex += query.Limit; - } - reloadItems(tabContent); - } - - function onPreviousPageClick() { - if (isLoading) { - return; - } - - if (userSettings.libraryPageSize() > 0) { - query.StartIndex = Math.max(0, query.StartIndex - query.Limit); - } - reloadItems(tabContent); - } - - window.scrollTo(0, 0); - this.alphaPicker?.updateControls(query); - let html; - const pagingHtml = libraryBrowser.getQueryPagingHtml({ - startIndex: query.StartIndex, - limit: query.Limit, - totalRecordCount: result.TotalRecordCount, - showLimit: false, - updatePageSizeSetting: false, - addLayoutButton: false, - sortButton: false, - filterButton: false - }); - const viewStyle = this.getCurrentViewStyle(); - if (viewStyle == 'Thumb') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'tvshows', - overlayMoreButton: true, - showTitle: true, - centerText: true - }); - } else if (viewStyle == 'ThumbCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'backdrop', - preferThumb: true, - context: 'tvshows', - cardLayout: true, - showTitle: true, - showYear: true, - centerText: true - }); - } else if (viewStyle == 'Banner') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'banner', - preferBanner: true, - context: 'tvshows' - }); - } else if (viewStyle == 'List') { - html = listView.getListViewHtml({ - items: result.Items, - context: 'tvshows', - sortBy: query.SortBy - }); - } else if (viewStyle == 'PosterCard') { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'portrait', - context: 'tvshows', - showTitle: true, - showYear: true, - centerText: true, - cardLayout: true - }); - } else { - html = cardBuilder.getCardsHtml({ - items: result.Items, - shape: 'portrait', - context: 'tvshows', - centerText: true, - lazy: true, - overlayMoreButton: true, - showTitle: true, - showYear: true - }); - } - - let elems = tabContent.querySelectorAll('.paging'); - - for (const elem of elems) { - elem.innerHTML = pagingHtml; - } - - elems = tabContent.querySelectorAll('.btnNextPage'); - for (const elem of elems) { - elem.addEventListener('click', onNextPageClick); - } - - elems = tabContent.querySelectorAll('.btnPreviousPage'); - for (const elem of elems) { - elem.addEventListener('click', onPreviousPageClick); - } - - const itemsContainer = tabContent.querySelector('.itemsContainer'); - itemsContainer.innerHTML = html; - imageLoader.lazyChildren(itemsContainer); - libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); - loading.hide(); - isLoading = false; - - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(page); - }); - }); - }; - - const data = {}; - let isLoading = false; - - this.showFilterMenu = function () { - import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { - const filterDialog = new filterDialogFactory({ - query: getQuery(tabContent), - mode: 'series', - serverId: ApiClient.serverId() - }); - Events.on(filterDialog, 'filterchange', function () { - getQuery(tabContent).StartIndex = 0; - reloadItems(tabContent); - }); - filterDialog.show(); - }); - }; - - this.getCurrentViewStyle = function () { - return getPageData(tabContent).view; - }; - - const initPage = (tabElement) => { - const alphaPickerElement = tabElement.querySelector('.alphaPicker'); - const itemsContainer = tabElement.querySelector('.itemsContainer'); - - alphaPickerElement.addEventListener('alphavaluechanged', function (e) { - const newValue = e.detail.value; - const query = getQuery(tabElement); - if (newValue === '#') { - query.NameLessThan = 'A'; - delete query.NameStartsWith; - } else { - query.NameStartsWith = newValue; - delete query.NameLessThan; - } - query.StartIndex = 0; - reloadItems(tabElement); - }); - this.alphaPicker = new AlphaPicker({ - element: alphaPickerElement, - valueChangeEvent: 'click' - }); - - tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); - alphaPickerElement.classList.add('alphaPicker-fixed-right'); - itemsContainer.classList.add('padded-right-withalphapicker'); - - tabElement.querySelector('.btnFilter').addEventListener('click', () => { - this.showFilterMenu(); - }); - tabElement.querySelector('.btnSort').addEventListener('click', function (e) { - libraryBrowser.showSortMenu({ - items: [{ - name: globalize.translate('Name'), - id: 'SortName' - }, { - name: globalize.translate('OptionRandom'), - id: 'Random' - }, { - name: globalize.translate('OptionImdbRating'), - id: 'CommunityRating,SortName' - }, { - name: globalize.translate('OptionDateShowAdded'), - id: 'DateCreated,SortName' - }, { - name: globalize.translate('OptionDateEpisodeAdded'), - id: 'DateLastContentAdded,SortName' - }, { - name: globalize.translate('OptionDatePlayed'), - id: 'SeriesDatePlayed,SortName' - }, { - name: globalize.translate('OptionParentalRating'), - id: 'OfficialRating,SortName' - }, { - name: globalize.translate('OptionReleaseDate'), - id: 'PremiereDate,SortName' - }], - callback: function () { - getQuery(tabElement).StartIndex = 0; - reloadItems(tabElement); - }, - query: getQuery(tabElement), - button: e.target - }); - }); - const btnSelectView = tabElement.querySelector('.btnSelectView'); - btnSelectView.addEventListener('click', (e) => { - libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); - }); - btnSelectView.addEventListener('layoutchange', function (e) { - const viewStyle = e.detail.viewStyle; - getPageData(tabElement).view = viewStyle; - libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); - getQuery(tabElement).StartIndex = 0; - onViewStyleChange(); - reloadItems(tabElement); - }); - }; - - initPage(tabContent); - onViewStyleChange(); - - this.renderTab = () => { - reloadItems(tabContent); - this.alphaPicker?.updateControls(getQuery(tabContent)); - }; + return pageData; } -/* eslint-enable indent */ + function getQuery(context) { + return getPageData(context).query; + } + + function getSavedQueryKey(context) { + if (!context.savedQueryKey) { + context.savedQueryKey = libraryBrowser.getSavedQueryKey('series'); + } + + return context.savedQueryKey; + } + + const onViewStyleChange = () => { + const viewStyle = this.getCurrentViewStyle(); + const itemsContainer = tabContent.querySelector('.itemsContainer'); + + if (viewStyle == 'List') { + itemsContainer.classList.add('vertical-list'); + itemsContainer.classList.remove('vertical-wrap'); + } else { + itemsContainer.classList.remove('vertical-list'); + itemsContainer.classList.add('vertical-wrap'); + } + + itemsContainer.innerHTML = ''; + }; + + const reloadItems = (page) => { + loading.show(); + isLoading = true; + const query = getQuery(page); + ApiClient.getItems(ApiClient.getCurrentUserId(), query).then((result) => { + function onNextPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex += query.Limit; + } + reloadItems(tabContent); + } + + function onPreviousPageClick() { + if (isLoading) { + return; + } + + if (userSettings.libraryPageSize() > 0) { + query.StartIndex = Math.max(0, query.StartIndex - query.Limit); + } + reloadItems(tabContent); + } + + window.scrollTo(0, 0); + this.alphaPicker?.updateControls(query); + let html; + const pagingHtml = libraryBrowser.getQueryPagingHtml({ + startIndex: query.StartIndex, + limit: query.Limit, + totalRecordCount: result.TotalRecordCount, + showLimit: false, + updatePageSizeSetting: false, + addLayoutButton: false, + sortButton: false, + filterButton: false + }); + const viewStyle = this.getCurrentViewStyle(); + if (viewStyle == 'Thumb') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'tvshows', + overlayMoreButton: true, + showTitle: true, + centerText: true + }); + } else if (viewStyle == 'ThumbCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'backdrop', + preferThumb: true, + context: 'tvshows', + cardLayout: true, + showTitle: true, + showYear: true, + centerText: true + }); + } else if (viewStyle == 'Banner') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'banner', + preferBanner: true, + context: 'tvshows' + }); + } else if (viewStyle == 'List') { + html = listView.getListViewHtml({ + items: result.Items, + context: 'tvshows', + sortBy: query.SortBy + }); + } else if (viewStyle == 'PosterCard') { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'tvshows', + showTitle: true, + showYear: true, + centerText: true, + cardLayout: true + }); + } else { + html = cardBuilder.getCardsHtml({ + items: result.Items, + shape: 'portrait', + context: 'tvshows', + centerText: true, + lazy: true, + overlayMoreButton: true, + showTitle: true, + showYear: true + }); + } + + let elems = tabContent.querySelectorAll('.paging'); + + for (const elem of elems) { + elem.innerHTML = pagingHtml; + } + + elems = tabContent.querySelectorAll('.btnNextPage'); + for (const elem of elems) { + elem.addEventListener('click', onNextPageClick); + } + + elems = tabContent.querySelectorAll('.btnPreviousPage'); + for (const elem of elems) { + elem.addEventListener('click', onPreviousPageClick); + } + + const itemsContainer = tabContent.querySelector('.itemsContainer'); + itemsContainer.innerHTML = html; + imageLoader.lazyChildren(itemsContainer); + libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); + loading.hide(); + isLoading = false; + + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(page); + }); + }); + }; + + const data = {}; + let isLoading = false; + + this.showFilterMenu = function () { + import('../../components/filterdialog/filterdialog').then(({ default: filterDialogFactory }) => { + const filterDialog = new filterDialogFactory({ + query: getQuery(tabContent), + mode: 'series', + serverId: ApiClient.serverId() + }); + Events.on(filterDialog, 'filterchange', function () { + getQuery(tabContent).StartIndex = 0; + reloadItems(tabContent); + }); + filterDialog.show(); + }); + }; + + this.getCurrentViewStyle = function () { + return getPageData(tabContent).view; + }; + + const initPage = (tabElement) => { + const alphaPickerElement = tabElement.querySelector('.alphaPicker'); + const itemsContainer = tabElement.querySelector('.itemsContainer'); + + alphaPickerElement.addEventListener('alphavaluechanged', function (e) { + const newValue = e.detail.value; + const query = getQuery(tabElement); + if (newValue === '#') { + query.NameLessThan = 'A'; + delete query.NameStartsWith; + } else { + query.NameStartsWith = newValue; + delete query.NameLessThan; + } + query.StartIndex = 0; + reloadItems(tabElement); + }); + this.alphaPicker = new AlphaPicker({ + element: alphaPickerElement, + valueChangeEvent: 'click' + }); + + tabElement.querySelector('.alphaPicker').classList.add('alphabetPicker-right'); + alphaPickerElement.classList.add('alphaPicker-fixed-right'); + itemsContainer.classList.add('padded-right-withalphapicker'); + + tabElement.querySelector('.btnFilter').addEventListener('click', () => { + this.showFilterMenu(); + }); + tabElement.querySelector('.btnSort').addEventListener('click', function (e) { + libraryBrowser.showSortMenu({ + items: [{ + name: globalize.translate('Name'), + id: 'SortName' + }, { + name: globalize.translate('OptionRandom'), + id: 'Random' + }, { + name: globalize.translate('OptionImdbRating'), + id: 'CommunityRating,SortName' + }, { + name: globalize.translate('OptionDateShowAdded'), + id: 'DateCreated,SortName' + }, { + name: globalize.translate('OptionDateEpisodeAdded'), + id: 'DateLastContentAdded,SortName' + }, { + name: globalize.translate('OptionDatePlayed'), + id: 'SeriesDatePlayed,SortName' + }, { + name: globalize.translate('OptionParentalRating'), + id: 'OfficialRating,SortName' + }, { + name: globalize.translate('OptionReleaseDate'), + id: 'PremiereDate,SortName' + }], + callback: function () { + getQuery(tabElement).StartIndex = 0; + reloadItems(tabElement); + }, + query: getQuery(tabElement), + button: e.target + }); + }); + const btnSelectView = tabElement.querySelector('.btnSelectView'); + btnSelectView.addEventListener('click', (e) => { + libraryBrowser.showLayoutMenu(e.target, this.getCurrentViewStyle(), 'Banner,List,Poster,PosterCard,Thumb,ThumbCard'.split(',')); + }); + btnSelectView.addEventListener('layoutchange', function (e) { + const viewStyle = e.detail.viewStyle; + getPageData(tabElement).view = viewStyle; + libraryBrowser.saveViewSetting(getSavedQueryKey(tabElement), viewStyle); + getQuery(tabElement).StartIndex = 0; + onViewStyleChange(); + reloadItems(tabElement); + }); + }; + + initPage(tabContent); + onViewStyleChange(); + + this.renderTab = () => { + reloadItems(tabContent); + this.alphaPicker?.updateControls(getQuery(tabContent)); + }; +} + diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index faa8bb90af..26ed743f73 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -2,73 +2,70 @@ import loading from '../../components/loading/loading'; import libraryBrowser from '../../scripts/libraryBrowser'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; -/* eslint-disable indent */ +function getQuery(params) { + const key = getSavedQueryKey(); + let pageData = data[key]; - function getQuery(params) { - const key = getSavedQueryKey(); - let pageData = data[key]; - - if (!pageData) { - pageData = data[key] = { - query: { - SortBy: 'SortName', - SortOrder: 'Ascending', - IncludeItemTypes: 'Series', - Recursive: true, - Fields: 'DateCreated,PrimaryImageAspectRatio', - StartIndex: 0 - } - }; - pageData.query.ParentId = params.topParentId; - } - - return pageData.query; + if (!pageData) { + pageData = data[key] = { + query: { + SortBy: 'SortName', + SortOrder: 'Ascending', + IncludeItemTypes: 'Series', + Recursive: true, + Fields: 'DateCreated,PrimaryImageAspectRatio', + StartIndex: 0 + } + }; + pageData.query.ParentId = params.topParentId; } - function getSavedQueryKey() { - return libraryBrowser.getSavedQueryKey('studios'); - } + return pageData.query; +} - function getPromise(context, params) { - const query = getQuery(params); - loading.show(); - return ApiClient.getStudios(ApiClient.getCurrentUserId(), query); - } +function getSavedQueryKey() { + return libraryBrowser.getSavedQueryKey('studios'); +} - function reloadItems(context, params, promise) { - promise.then(function (result) { - const elem = context.querySelector('#items'); - cardBuilder.buildCards(result.Items, { - itemsContainer: elem, - shape: 'backdrop', - preferThumb: true, - showTitle: true, - scalable: true, - centerText: true, - overlayMoreButton: true, - context: 'tvshows' - }); - loading.hide(); +function getPromise(context, params) { + const query = getQuery(params); + loading.show(); + return ApiClient.getStudios(ApiClient.getCurrentUserId(), query); +} - import('../../components/autoFocuser').then(({ default: autoFocuser }) => { - autoFocuser.autoFocus(context); - }); +function reloadItems(context, params, promise) { + promise.then(function (result) { + const elem = context.querySelector('#items'); + cardBuilder.buildCards(result.Items, { + itemsContainer: elem, + shape: 'backdrop', + preferThumb: true, + showTitle: true, + scalable: true, + centerText: true, + overlayMoreButton: true, + context: 'tvshows' }); - } + loading.hide(); - const data = {}; + import('../../components/autoFocuser').then(({ default: autoFocuser }) => { + autoFocuser.autoFocus(context); + }); + }); +} - export default function (view, params, tabContent) { - let promise; - const self = this; +const data = {}; - self.preRender = function () { - promise = getPromise(view, params); - }; +export default function (view, params, tabContent) { + let promise; + const self = this; - self.renderTab = function () { - reloadItems(tabContent, params, promise); - }; - } + self.preRender = function () { + promise = getPromise(view, params); + }; + + self.renderTab = function () { + reloadItems(tabContent, params, promise); + }; +} -/* eslint-enable indent */ diff --git a/src/controllers/shows/tvupcoming.js b/src/controllers/shows/tvupcoming.js index fa6be1f356..f8b2f31eaa 100644 --- a/src/controllers/shows/tvupcoming.js +++ b/src/controllers/shows/tvupcoming.js @@ -7,138 +7,135 @@ import globalize from '../../scripts/globalize'; import '../../styles/scrollstyles.scss'; import '../../elements/emby-itemscontainer/emby-itemscontainer'; -/* eslint-disable indent */ +function getUpcomingPromise(context, params) { + loading.show(); + const query = { + Limit: 48, + Fields: 'AirTime', + UserId: ApiClient.getCurrentUserId(), + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', + EnableTotalRecordCount: false + }; + query.ParentId = params.topParentId; + return ApiClient.getJSON(ApiClient.getUrl('Shows/Upcoming', query)); +} - function getUpcomingPromise(context, params) { - loading.show(); - const query = { - Limit: 48, - Fields: 'AirTime', - UserId: ApiClient.getCurrentUserId(), - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - EnableTotalRecordCount: false - }; - query.ParentId = params.topParentId; - return ApiClient.getJSON(ApiClient.getUrl('Shows/Upcoming', query)); - } +function loadUpcoming(context, params, promise) { + promise.then(function (result) { + const items = result.Items; - function loadUpcoming(context, params, promise) { - promise.then(function (result) { - const items = result.Items; + if (items.length) { + context.querySelector('.noItemsMessage').style.display = 'none'; + } else { + context.querySelector('.noItemsMessage').style.display = 'block'; + } - if (items.length) { - context.querySelector('.noItemsMessage').style.display = 'none'; - } else { - context.querySelector('.noItemsMessage').style.display = 'block'; + renderUpcoming(context.querySelector('#upcomingItems'), items); + loading.hide(); + }); +} + +function enableScrollX() { + return !layoutManager.desktop; +} + +function getThumbShape() { + return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; +} + +function renderUpcoming(elem, items) { + const groups = []; + let currentGroupName = ''; + let currentGroup = []; + + for (let i = 0, length = items.length; i < length; i++) { + const item = items[i]; + let dateText = ''; + + if (item.PremiereDate) { + try { + const premiereDate = datetime.parseISO8601Date(item.PremiereDate, true); + dateText = datetime.isRelativeDay(premiereDate, -1) ? globalize.translate('Yesterday') : datetime.toLocaleDateString(premiereDate, { + weekday: 'long', + month: 'short', + day: 'numeric' + }); + } catch (err) { + console.error('error parsing timestamp for upcoming tv shows'); + } + } + + if (dateText != currentGroupName) { + if (currentGroup.length) { + groups.push({ + name: currentGroupName, + items: currentGroup + }); } - renderUpcoming(context.querySelector('#upcomingItems'), items); - loading.hide(); + currentGroupName = dateText; + currentGroup = [item]; + } else { + currentGroup.push(item); + } + } + + let html = ''; + + for (let i = 0, length = groups.length; i < length; i++) { + const group = groups[i]; + html += '
'; + html += '

' + group.name + '

'; + let allowBottomPadding = true; + + if (enableScrollX()) { + allowBottomPadding = false; + let scrollXClass = 'scrollX hiddenScrollX'; + + if (layoutManager.tv) { + scrollXClass += ' smoothScrollX'; + } + + html += '
'; + } else { + html += '
'; + } + + html += cardBuilder.getCardsHtml({ + items: group.items, + showLocationTypeIndicator: false, + shape: getThumbShape(), + showTitle: true, + preferThumb: true, + lazy: true, + showDetailsMenu: true, + centerText: true, + showParentTitle: true, + overlayText: false, + allowBottomPadding: allowBottomPadding, + cardLayout: false, + overlayMoreButton: true, + missingIndicator: false }); + html += '
'; + html += '
'; } - function enableScrollX() { - return !layoutManager.desktop; - } + elem.innerHTML = html; + imageLoader.lazyChildren(elem); +} - function getThumbShape() { - return enableScrollX() ? 'overflowBackdrop' : 'backdrop'; - } +export default function (view, params, tabContent) { + let upcomingPromise; + const self = this; - function renderUpcoming(elem, items) { - const groups = []; - let currentGroupName = ''; - let currentGroup = []; + self.preRender = function () { + upcomingPromise = getUpcomingPromise(view, params); + }; - for (let i = 0, length = items.length; i < length; i++) { - const item = items[i]; - let dateText = ''; + self.renderTab = function () { + loadUpcoming(tabContent, params, upcomingPromise); + }; +} - if (item.PremiereDate) { - try { - const premiereDate = datetime.parseISO8601Date(item.PremiereDate, true); - dateText = datetime.isRelativeDay(premiereDate, -1) ? globalize.translate('Yesterday') : datetime.toLocaleDateString(premiereDate, { - weekday: 'long', - month: 'short', - day: 'numeric' - }); - } catch (err) { - console.error('error parsing timestamp for upcoming tv shows'); - } - } - - if (dateText != currentGroupName) { - if (currentGroup.length) { - groups.push({ - name: currentGroupName, - items: currentGroup - }); - } - - currentGroupName = dateText; - currentGroup = [item]; - } else { - currentGroup.push(item); - } - } - - let html = ''; - - for (let i = 0, length = groups.length; i < length; i++) { - const group = groups[i]; - html += '
'; - html += '

' + group.name + '

'; - let allowBottomPadding = true; - - if (enableScrollX()) { - allowBottomPadding = false; - let scrollXClass = 'scrollX hiddenScrollX'; - - if (layoutManager.tv) { - scrollXClass += ' smoothScrollX'; - } - - html += '
'; - } else { - html += '
'; - } - - html += cardBuilder.getCardsHtml({ - items: group.items, - showLocationTypeIndicator: false, - shape: getThumbShape(), - showTitle: true, - preferThumb: true, - lazy: true, - showDetailsMenu: true, - centerText: true, - showParentTitle: true, - overlayText: false, - allowBottomPadding: allowBottomPadding, - cardLayout: false, - overlayMoreButton: true, - missingIndicator: false - }); - html += '
'; - html += '
'; - } - - elem.innerHTML = html; - imageLoader.lazyChildren(elem); - } - - export default function (view, params, tabContent) { - let upcomingPromise; - const self = this; - - self.preRender = function () { - upcomingPromise = getUpcomingPromise(view, params); - }; - - self.renderTab = function () { - loadUpcoming(tabContent, params, upcomingPromise); - }; - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/display/index.js b/src/controllers/user/display/index.js index 5d34539c35..836c8c159a 100644 --- a/src/controllers/user/display/index.js +++ b/src/controllers/user/display/index.js @@ -2,39 +2,36 @@ import DisplaySettings from '../../../components/displaySettings/displaySettings import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let settingsInstance; - export default function (view, params) { - let settingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (settingsInstance) { + settingsInstance.loadData(); + } else { + settingsInstance = new DisplaySettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (settingsInstance) { - settingsInstance.loadData(); - } else { - settingsInstance = new DisplaySettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (settingsInstance) { + settingsInstance.destroy(); + settingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (settingsInstance) { - settingsInstance.destroy(); - settingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/home/index.js b/src/controllers/user/home/index.js index 45b0fd2f80..c2c0af99af 100644 --- a/src/controllers/user/home/index.js +++ b/src/controllers/user/home/index.js @@ -3,39 +3,36 @@ import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; import '../../../components/listview/listview.scss'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let homescreenSettingsInstance; - export default function (view, params) { - let homescreenSettingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (homescreenSettingsInstance) { + homescreenSettingsInstance.loadData(); + } else { + homescreenSettingsInstance = new HomescreenSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.homeScreenSettingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (homescreenSettingsInstance) { - homescreenSettingsInstance.loadData(); - } else { - homescreenSettingsInstance = new HomescreenSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.homeScreenSettingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (homescreenSettingsInstance) { + homescreenSettingsInstance.destroy(); + homescreenSettingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (homescreenSettingsInstance) { - homescreenSettingsInstance.destroy(); - homescreenSettingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/playback/index.js b/src/controllers/user/playback/index.js index f0b4be6a25..efb24c76ee 100644 --- a/src/controllers/user/playback/index.js +++ b/src/controllers/user/playback/index.js @@ -4,39 +4,36 @@ import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; import '../../../components/listview/listview.scss'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let settingsInstance; - export default function (view, params) { - let settingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (settingsInstance) { + settingsInstance.loadData(); + } else { + settingsInstance = new PlaybackSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (settingsInstance) { - settingsInstance.loadData(); - } else { - settingsInstance = new PlaybackSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (settingsInstance) { + settingsInstance.destroy(); + settingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (settingsInstance) { - settingsInstance.destroy(); - settingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/controllers/user/profile/index.html b/src/controllers/user/profile/index.html new file mode 100644 index 0000000000..3eaa2f7299 --- /dev/null +++ b/src/controllers/user/profile/index.html @@ -0,0 +1,69 @@ +
+
+
+
+ +
+
+
+

+
+ + +
+
+
+
+

+ ${HeaderPassword} +

+
+ +
+
+ +
+
+ +
+
+ + +
+
+
+
+
+

${HeaderEasyPinCode}

+
${EasyPasswordHelp}
+
+
+ +
+
+ +
${LabelInNetworkSignInWithEasyPasswordHelp}
+
+
+ + +
+
+
+
+
diff --git a/src/controllers/user/profile/index.js b/src/controllers/user/profile/index.js new file mode 100644 index 0000000000..07bab611c3 --- /dev/null +++ b/src/controllers/user/profile/index.js @@ -0,0 +1,105 @@ +import UserPasswordPage from '../../dashboard/users/userpasswordpage'; +import loading from '../../../components/loading/loading'; +import libraryMenu from '../../../scripts/libraryMenu'; +import { appHost } from '../../../components/apphost'; +import globalize from '../../../scripts/globalize'; +import '../../../elements/emby-button/emby-button'; +import Dashboard from '../../../utils/dashboard'; +import toast from '../../../components/toast/toast'; +import confirm from '../../../components/confirm/confirm'; +import { getParameterByName } from '../../../utils/url.ts'; + +function reloadUser(page) { + const userId = getParameterByName('userId'); + loading.show(); + ApiClient.getUser(userId).then(function (user) { + page.querySelector('.username').innerText = user.Name; + libraryMenu.setTitle(user.Name); + + let imageUrl = 'assets/img/avatar.png'; + if (user.PrimaryImageTag) { + imageUrl = ApiClient.getUserImageUrl(user.Id, { + tag: user.PrimaryImageTag, + type: 'Primary' + }); + } + + const userImage = page.querySelector('#image'); + userImage.style.backgroundImage = 'url(' + imageUrl + ')'; + + Dashboard.getCurrentUser().then(function (loggedInUser) { + if (user.PrimaryImageTag) { + page.querySelector('#btnAddImage').classList.add('hide'); + page.querySelector('#btnDeleteImage').classList.remove('hide'); + } else if (appHost.supports('fileinput') && (loggedInUser.Policy.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) { + page.querySelector('#btnDeleteImage').classList.add('hide'); + page.querySelector('#btnAddImage').classList.remove('hide'); + } + }); + loading.hide(); + }); +} + +function onFileReaderError(evt) { + loading.hide(); + switch (evt.target.error.code) { + case evt.target.error.NOT_FOUND_ERR: + toast(globalize.translate('FileNotFound')); + break; + case evt.target.error.ABORT_ERR: + onFileReaderAbort(); + break; + case evt.target.error.NOT_READABLE_ERR: + default: + toast(globalize.translate('FileReadError')); + } +} + +function onFileReaderAbort() { + loading.hide(); + toast(globalize.translate('FileReadCancelled')); +} + +function setFiles(page, files) { + const userImage = page.querySelector('#image'); + const file = files[0]; + + if (!file || !file.type.match('image.*')) { + return false; + } + + const reader = new FileReader(); + reader.onerror = onFileReaderError; + reader.onabort = onFileReaderAbort; + reader.onload = function (evt) { + userImage.style.backgroundImage = 'url(' + evt.target.result + ')'; + const userId = getParameterByName('userId'); + ApiClient.uploadUserImage(userId, 'Primary', file).then(function () { + loading.hide(); + reloadUser(page); + }); + }; + + reader.readAsDataURL(file); +} + +export default function (view, params) { + reloadUser(view); + new UserPasswordPage(view, params); + view.querySelector('#btnDeleteImage').addEventListener('click', function () { + confirm(globalize.translate('DeleteImageConfirmation'), globalize.translate('DeleteImage')).then(function () { + loading.show(); + const userId = getParameterByName('userId'); + ApiClient.deleteUserImage(userId, 'primary').then(function () { + loading.hide(); + reloadUser(view); + }); + }); + }); + view.querySelector('#btnAddImage').addEventListener('click', function () { + view.querySelector('#uploadImage').click(); + }); + view.querySelector('#uploadImage').addEventListener('change', function (evt) { + setFiles(view, evt.target.files); + }); +} diff --git a/src/controllers/user/subtitles/index.js b/src/controllers/user/subtitles/index.js index 87676df65f..6eb3197184 100644 --- a/src/controllers/user/subtitles/index.js +++ b/src/controllers/user/subtitles/index.js @@ -2,39 +2,36 @@ import SubtitleSettings from '../../../components/subtitlesettings/subtitlesetti import * as userSettings from '../../../scripts/settings/userSettings'; import autoFocuser from '../../../components/autoFocuser'; -/* eslint-disable indent */ +// Shortcuts +const UserSettings = userSettings.UserSettings; - // Shortcuts - const UserSettings = userSettings.UserSettings; +export default function (view, params) { + let subtitleSettingsInstance; - export default function (view, params) { - let subtitleSettingsInstance; + const userId = params.userId || ApiClient.getCurrentUserId(); + const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); - const userId = params.userId || ApiClient.getCurrentUserId(); - const currentSettings = userId === ApiClient.getCurrentUserId() ? userSettings : new UserSettings(); + view.addEventListener('viewshow', function () { + if (subtitleSettingsInstance) { + subtitleSettingsInstance.loadData(); + } else { + subtitleSettingsInstance = new SubtitleSettings({ + serverId: ApiClient.serverId(), + userId: userId, + element: view.querySelector('.settingsContainer'), + userSettings: currentSettings, + enableSaveButton: true, + enableSaveConfirmation: true, + autoFocus: autoFocuser.isEnabled() + }); + } + }); - view.addEventListener('viewshow', function () { - if (subtitleSettingsInstance) { - subtitleSettingsInstance.loadData(); - } else { - subtitleSettingsInstance = new SubtitleSettings({ - serverId: ApiClient.serverId(), - userId: userId, - element: view.querySelector('.settingsContainer'), - userSettings: currentSettings, - enableSaveButton: true, - enableSaveConfirmation: true, - autoFocus: autoFocuser.isEnabled() - }); - } - }); + view.addEventListener('viewdestroy', function () { + if (subtitleSettingsInstance) { + subtitleSettingsInstance.destroy(); + subtitleSettingsInstance = null; + } + }); +} - view.addEventListener('viewdestroy', function () { - if (subtitleSettingsInstance) { - subtitleSettingsInstance.destroy(); - subtitleSettingsInstance = null; - } - }); - } - -/* eslint-enable indent */ diff --git a/src/elements/emby-checkbox/emby-checkbox.js b/src/elements/emby-checkbox/emby-checkbox.js index 1923ef4e5c..2be4237d21 100644 --- a/src/elements/emby-checkbox/emby-checkbox.js +++ b/src/elements/emby-checkbox/emby-checkbox.js @@ -3,109 +3,106 @@ import dom from '../../scripts/dom'; import './emby-checkbox.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); - const EmbyCheckboxPrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + // Real (non-emulator) Tizen does nothing on Space + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { - e.preventDefault(); + this.checked = !this.checked; - this.checked = !this.checked; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); + return false; + } +} - return false; - } +const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; + +function forceRefresh(loading) { + const elem = this.parentNode; + + elem.style.webkitAnimationName = 'repaintChrome'; + elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); + elem.style.webkitAnimationDuration = '10ms'; + elem.style.webkitAnimationIterationCount = '1'; + + setTimeout(function () { + elem.style.webkitAnimationName = ''; + }, (loading === true ? 520 : 20)); +} + +EmbyCheckboxPrototype.attachedCallback = function () { + if (this.getAttribute('data-embycheckbox') === 'true') { + return; } - const enableRefreshHack = browser.tizen || browser.orsay || browser.operaTv || browser.web0s ? true : false; + this.setAttribute('data-embycheckbox', 'true'); - function forceRefresh(loading) { - const elem = this.parentNode; + this.classList.add('emby-checkbox'); - elem.style.webkitAnimationName = 'repaintChrome'; - elem.style.webkitAnimationDelay = (loading === true ? '500ms' : ''); - elem.style.webkitAnimationDuration = '10ms'; - elem.style.webkitAnimationIterationCount = '1'; + const labelElement = this.parentNode; + labelElement.classList.add('emby-checkbox-label'); - setTimeout(function () { - elem.style.webkitAnimationName = ''; - }, (loading === true ? 520 : 20)); + const labelTextElement = labelElement.querySelector('span'); + + let outlineClass = 'checkboxOutline'; + + const customClass = this.getAttribute('data-outlineclass'); + if (customClass) { + outlineClass += ' ' + customClass; } - EmbyCheckboxPrototype.attachedCallback = function () { - if (this.getAttribute('data-embycheckbox') === 'true') { - return; - } + const checkedIcon = this.getAttribute('data-checkedicon') || 'check'; + const uncheckedIcon = this.getAttribute('data-uncheckedicon') || ''; + const checkHtml = ''; + const uncheckedHtml = ''; + labelElement.insertAdjacentHTML('beforeend', '' + checkHtml + uncheckedHtml + ''); - this.setAttribute('data-embycheckbox', 'true'); + labelTextElement.classList.add('checkboxLabel'); - this.classList.add('emby-checkbox'); + this.addEventListener('keydown', onKeyDown); - const labelElement = this.parentNode; - labelElement.classList.add('emby-checkbox-label'); - - const labelTextElement = labelElement.querySelector('span'); - - let outlineClass = 'checkboxOutline'; - - const customClass = this.getAttribute('data-outlineclass'); - if (customClass) { - outlineClass += ' ' + customClass; - } - - const checkedIcon = this.getAttribute('data-checkedicon') || 'check'; - const uncheckedIcon = this.getAttribute('data-uncheckedicon') || ''; - const checkHtml = ''; - const uncheckedHtml = ''; - labelElement.insertAdjacentHTML('beforeend', '' + checkHtml + uncheckedHtml + ''); - - labelTextElement.classList.add('checkboxLabel'); - - this.addEventListener('keydown', onKeyDown); - - if (enableRefreshHack) { - forceRefresh.call(this, true); - dom.addEventListener(this, 'click', forceRefresh, { - passive: true - }); - dom.addEventListener(this, 'blur', forceRefresh, { - passive: true - }); - dom.addEventListener(this, 'focus', forceRefresh, { - passive: true - }); - dom.addEventListener(this, 'change', forceRefresh, { - passive: true - }); - } - }; - - EmbyCheckboxPrototype.detachedCallback = function () { - this.removeEventListener('keydown', onKeyDown); - - dom.removeEventListener(this, 'click', forceRefresh, { + if (enableRefreshHack) { + forceRefresh.call(this, true); + dom.addEventListener(this, 'click', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'blur', forceRefresh, { + dom.addEventListener(this, 'blur', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'focus', forceRefresh, { + dom.addEventListener(this, 'focus', forceRefresh, { passive: true }); - dom.removeEventListener(this, 'change', forceRefresh, { + dom.addEventListener(this, 'change', forceRefresh, { passive: true }); - }; + } +}; - document.registerElement('emby-checkbox', { - prototype: EmbyCheckboxPrototype, - extends: 'input' +EmbyCheckboxPrototype.detachedCallback = function () { + this.removeEventListener('keydown', onKeyDown); + + dom.removeEventListener(this, 'click', forceRefresh, { + passive: true }); + dom.removeEventListener(this, 'blur', forceRefresh, { + passive: true + }); + dom.removeEventListener(this, 'focus', forceRefresh, { + passive: true + }); + dom.removeEventListener(this, 'change', forceRefresh, { + passive: true + }); +}; + +document.registerElement('emby-checkbox', { + prototype: EmbyCheckboxPrototype, + extends: 'input' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-collapse/emby-collapse.js b/src/elements/emby-collapse/emby-collapse.js index bd9743c55b..bf281d1257 100644 --- a/src/elements/emby-collapse/emby-collapse.js +++ b/src/elements/emby-collapse/emby-collapse.js @@ -2,101 +2,98 @@ import './emby-collapse.scss'; import 'webcomponents.js/webcomponents-lite'; import '../emby-button/emby-button'; -/* eslint-disable indent */ +const EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); - const EmbyButtonPrototype = Object.create(HTMLDivElement.prototype); +function slideDownToShow(button, elem) { + requestAnimationFrame(() => { + elem.classList.remove('hide'); + elem.classList.add('expanded'); + elem.style.height = 'auto'; + const height = elem.offsetHeight + 'px'; + elem.style.height = '0'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + elem.style.height = height; - function slideDownToShow(button, elem) { - requestAnimationFrame(() => { - elem.classList.remove('hide'); - elem.classList.add('expanded'); + setTimeout(function () { + if (elem.classList.contains('expanded')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } elem.style.height = 'auto'; - const height = elem.offsetHeight + 'px'; - elem.style.height = '0'; - // trigger reflow - // TODO: Find a better way to do this - const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ - elem.style.height = height; + }, 300); - setTimeout(function () { - if (elem.classList.contains('expanded')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - elem.style.height = 'auto'; - }, 300); - - const icon = button.querySelector('.material-icons'); - icon.classList.add('emby-collapse-expandIconExpanded'); - }); - } - - function slideUpToHide(button, elem) { - requestAnimationFrame(() => { - elem.style.height = elem.offsetHeight + 'px'; - // trigger reflow - // TODO: Find a better way to do this - const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ - elem.classList.remove('expanded'); - elem.style.height = '0'; - - setTimeout(function () { - if (elem.classList.contains('expanded')) { - elem.classList.remove('hide'); - } else { - elem.classList.add('hide'); - } - }, 300); - - const icon = button.querySelector('.material-icons'); - icon.classList.remove('emby-collapse-expandIconExpanded'); - }); - } - - function onButtonClick() { - const button = this; - const collapseContent = button.parentNode.querySelector('.collapseContent'); - - if (collapseContent.expanded) { - collapseContent.expanded = false; - slideUpToHide(button, collapseContent); - } else { - collapseContent.expanded = true; - slideDownToShow(button, collapseContent); - } - } - - EmbyButtonPrototype.attachedCallback = function () { - if (this.classList.contains('emby-collapse')) { - return; - } - - this.classList.add('emby-collapse'); - - const collapseContent = this.querySelector('.collapseContent'); - if (collapseContent) { - collapseContent.classList.add('hide'); - } - - const title = this.getAttribute('title'); - - const html = ''; - - this.insertAdjacentHTML('afterbegin', html); - - const button = this.querySelector('.emby-collapsible-button'); - - button.addEventListener('click', onButtonClick); - - if (this.getAttribute('data-expanded') === 'true') { - onButtonClick.call(button); - } - }; - - document.registerElement('emby-collapse', { - prototype: EmbyButtonPrototype, - extends: 'div' + const icon = button.querySelector('.material-icons'); + icon.classList.add('emby-collapse-expandIconExpanded'); }); +} + +function slideUpToHide(button, elem) { + requestAnimationFrame(() => { + elem.style.height = elem.offsetHeight + 'px'; + // trigger reflow + // TODO: Find a better way to do this + const newHeight = elem.offsetHeight; /* eslint-disable-line no-unused-vars */ + elem.classList.remove('expanded'); + elem.style.height = '0'; + + setTimeout(function () { + if (elem.classList.contains('expanded')) { + elem.classList.remove('hide'); + } else { + elem.classList.add('hide'); + } + }, 300); + + const icon = button.querySelector('.material-icons'); + icon.classList.remove('emby-collapse-expandIconExpanded'); + }); +} + +function onButtonClick() { + const button = this; + const collapseContent = button.parentNode.querySelector('.collapseContent'); + + if (collapseContent.expanded) { + collapseContent.expanded = false; + slideUpToHide(button, collapseContent); + } else { + collapseContent.expanded = true; + slideDownToShow(button, collapseContent); + } +} + +EmbyButtonPrototype.attachedCallback = function () { + if (this.classList.contains('emby-collapse')) { + return; + } + + this.classList.add('emby-collapse'); + + const collapseContent = this.querySelector('.collapseContent'); + if (collapseContent) { + collapseContent.classList.add('hide'); + } + + const title = this.getAttribute('title'); + + const html = ''; + + this.insertAdjacentHTML('afterbegin', html); + + const button = this.querySelector('.emby-collapsible-button'); + + button.addEventListener('click', onButtonClick); + + if (this.getAttribute('data-expanded') === 'true') { + onButtonClick.call(button); + } +}; + +document.registerElement('emby-collapse', { + prototype: EmbyButtonPrototype, + extends: 'div' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index f62bcbf345..1b7067cef8 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -3,119 +3,116 @@ import dom from '../../scripts/dom'; import './emby-input.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyInputPrototype = Object.create(HTMLInputElement.prototype); - const EmbyInputPrototype = Object.create(HTMLInputElement.prototype); +let inputId = 0; +let supportsFloatingLabel = false; - let inputId = 0; - let supportsFloatingLabel = false; +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + const baseSetMethod = descriptor.set; + descriptor.set = function (value) { + baseSetMethod.call(this, value); - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - const baseSetMethod = descriptor.set; - descriptor.set = function (value) { - baseSetMethod.call(this, value); + this.dispatchEvent(new CustomEvent('valueset', { + bubbles: false, + cancelable: false + })); + }; - this.dispatchEvent(new CustomEvent('valueset', { - bubbles: false, - cancelable: false - })); - }; + Object.defineProperty(HTMLInputElement.prototype, 'value', descriptor); + supportsFloatingLabel = true; + } +} - Object.defineProperty(HTMLInputElement.prototype, 'value', descriptor); - supportsFloatingLabel = true; - } +EmbyInputPrototype.createdCallback = function () { + if (!this.id) { + this.id = 'embyinput' + inputId; + inputId++; } - EmbyInputPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embyinput' + inputId; - inputId++; - } + if (this.classList.contains('emby-input')) { + return; + } - if (this.classList.contains('emby-input')) { - return; - } + this.classList.add('emby-input'); - this.classList.add('emby-input'); + const parentNode = this.parentNode; + const document = this.ownerDocument; + const label = document.createElement('label'); + label.innerText = this.getAttribute('label') || ''; + label.classList.add('inputLabel'); + label.classList.add('inputLabelUnfocused'); - const parentNode = this.parentNode; - const document = this.ownerDocument; - const label = document.createElement('label'); - label.innerText = this.getAttribute('label') || ''; - label.classList.add('inputLabel'); - label.classList.add('inputLabelUnfocused'); + label.htmlFor = this.id; + parentNode.insertBefore(label, this); + this.labelElement = label; - label.htmlFor = this.id; - parentNode.insertBefore(label, this); - this.labelElement = label; + dom.addEventListener(this, 'focus', function () { + onChange.call(this); - dom.addEventListener(this, 'focus', function () { - onChange.call(this); - - // For Samsung orsay devices - if (document.attachIME) { - document.attachIME(this); - } - - label.classList.add('inputLabelFocused'); - label.classList.remove('inputLabelUnfocused'); - }, { - passive: true - }); - - dom.addEventListener(this, 'blur', function () { - onChange.call(this); - label.classList.remove('inputLabelFocused'); - label.classList.add('inputLabelUnfocused'); - }, { - passive: true - }); - - dom.addEventListener(this, 'change', onChange, { - passive: true - }); - dom.addEventListener(this, 'input', onChange, { - passive: true - }); - dom.addEventListener(this, 'valueset', onChange, { - passive: true - }); - - //Make sure the IME pops up if this is the first/default element on the page - if (browser.orsay && this === document.activeElement && document.attachIME) { + // For Samsung orsay devices + if (document.attachIME) { document.attachIME(this); } - }; - function onChange() { - const label = this.labelElement; - if (this.value) { - label.classList.remove('inputLabel-float'); - } else { - const instanceSupportsFloat = supportsFloatingLabel && this.type !== 'date' && this.type !== 'time'; - - if (instanceSupportsFloat) { - label.classList.add('inputLabel-float'); - } - } - } - - EmbyInputPrototype.attachedCallback = function () { - this.labelElement.htmlFor = this.id; - onChange.call(this); - }; - - EmbyInputPrototype.label = function (text) { - this.labelElement.innerText = text; - }; - - document.registerElement('emby-input', { - prototype: EmbyInputPrototype, - extends: 'input' + label.classList.add('inputLabelFocused'); + label.classList.remove('inputLabelUnfocused'); + }, { + passive: true }); -/* eslint-enable indent */ + dom.addEventListener(this, 'blur', function () { + onChange.call(this); + label.classList.remove('inputLabelFocused'); + label.classList.add('inputLabelUnfocused'); + }, { + passive: true + }); + + dom.addEventListener(this, 'change', onChange, { + passive: true + }); + dom.addEventListener(this, 'input', onChange, { + passive: true + }); + dom.addEventListener(this, 'valueset', onChange, { + passive: true + }); + + //Make sure the IME pops up if this is the first/default element on the page + if (browser.orsay && this === document.activeElement && document.attachIME) { + document.attachIME(this); + } +}; + +function onChange() { + const label = this.labelElement; + if (this.value) { + label.classList.remove('inputLabel-float'); + } else { + const instanceSupportsFloat = supportsFloatingLabel && this.type !== 'date' && this.type !== 'time'; + + if (instanceSupportsFloat) { + label.classList.add('inputLabel-float'); + } + } +} + +EmbyInputPrototype.attachedCallback = function () { + this.labelElement.htmlFor = this.id; + onChange.call(this); +}; + +EmbyInputPrototype.label = function (text) { + this.labelElement.innerText = text; +}; + +document.registerElement('emby-input', { + prototype: EmbyInputPrototype, + extends: 'input' +}); + diff --git a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js index 68206960fd..e2e1ec90ec 100644 --- a/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js +++ b/src/elements/emby-itemrefreshindicator/emby-itemrefreshindicator.js @@ -5,73 +5,70 @@ import Events from '../../utils/events.ts'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function onRefreshProgress(e, apiClient, info) { + const indicator = this; + + if (!indicator.itemId) { + indicator.itemId = dom.parentWithAttribute(indicator, 'data-id').getAttribute('data-id'); } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + if (info.ItemId === indicator.itemId) { + const progress = parseFloat(info.Progress); + + if (progress && progress < 100) { + this.classList.remove('hide'); + } else { + this.classList.add('hide'); } + + this.setAttribute('data-progress', progress); + } +} + +const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + +EmbyItemRefreshIndicatorPrototype.createdCallback = function () { + // base method + if (EmbyProgressRing.createdCallback) { + EmbyProgressRing.createdCallback.call(this); } - function onRefreshProgress(e, apiClient, info) { - const indicator = this; + addNotificationEvent(this, 'RefreshProgress', onRefreshProgress); +}; - if (!indicator.itemId) { - indicator.itemId = dom.parentWithAttribute(indicator, 'data-id').getAttribute('data-id'); - } +EmbyItemRefreshIndicatorPrototype.attachedCallback = function () { + // base method + if (EmbyProgressRing.attachedCallback) { + EmbyProgressRing.attachedCallback.call(this); + } +}; - if (info.ItemId === indicator.itemId) { - const progress = parseFloat(info.Progress); - - if (progress && progress < 100) { - this.classList.remove('hide'); - } else { - this.classList.add('hide'); - } - - this.setAttribute('data-progress', progress); - } +EmbyItemRefreshIndicatorPrototype.detachedCallback = function () { + // base method + if (EmbyProgressRing.detachedCallback) { + EmbyProgressRing.detachedCallback.call(this); } - const EmbyItemRefreshIndicatorPrototype = Object.create(EmbyProgressRing); + removeNotificationEvent(this, 'RefreshProgress'); + this.itemId = null; +}; - EmbyItemRefreshIndicatorPrototype.createdCallback = function () { - // base method - if (EmbyProgressRing.createdCallback) { - EmbyProgressRing.createdCallback.call(this); - } +document.registerElement('emby-itemrefreshindicator', { + prototype: EmbyItemRefreshIndicatorPrototype, + extends: 'div' +}); - addNotificationEvent(this, 'RefreshProgress', onRefreshProgress); - }; - - EmbyItemRefreshIndicatorPrototype.attachedCallback = function () { - // base method - if (EmbyProgressRing.attachedCallback) { - EmbyProgressRing.attachedCallback.call(this); - } - }; - - EmbyItemRefreshIndicatorPrototype.detachedCallback = function () { - // base method - if (EmbyProgressRing.detachedCallback) { - EmbyProgressRing.detachedCallback.call(this); - } - - removeNotificationEvent(this, 'RefreshProgress'); - this.itemId = null; - }; - - document.registerElement('emby-itemrefreshindicator', { - prototype: EmbyItemRefreshIndicatorPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 4c7769f32e..d34cc25438 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -13,471 +13,468 @@ import 'webcomponents.js/webcomponents-lite'; import ServerConnections from '../../components/ServerConnections'; import Sortable from 'sortablejs'; -/* eslint-disable indent */ +const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); - const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype); +function onClick(e) { + const itemsContainer = this; + const multiSelect = itemsContainer.multiSelect; - function onClick(e) { - const itemsContainer = this; - const multiSelect = itemsContainer.multiSelect; - - if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) { - return; - } - - itemShortcuts.onClick.call(itemsContainer, e); + if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) { + return; } - function disableEvent(e) { + itemShortcuts.onClick.call(itemsContainer, e); +} + +function disableEvent(e) { + e.preventDefault(); + e.stopPropagation(); + return false; +} + +function onContextMenu(e) { + const target = e.target; + const card = dom.parentWithAttribute(target, 'data-id'); + + // check for serverId, it won't be present on selectserver + if (card && card.getAttribute('data-serverid')) { + inputManager.handleCommand('menu', { + sourceElement: card + }); + e.preventDefault(); e.stopPropagation(); return false; } +} - function onContextMenu(e) { - const target = e.target; - const card = dom.parentWithAttribute(target, 'data-id'); - - // check for serverId, it won't be present on selectserver - if (card && card.getAttribute('data-serverid')) { - inputManager.handleCommand('menu', { - sourceElement: card - }); - - e.preventDefault(); - e.stopPropagation(); - return false; - } - } - - function getShortcutOptions() { - return { - click: false - }; - } - - ItemsContainerPrototype.enableMultiSelect = function (enabled) { - const current = this.multiSelect; - - if (!enabled) { - if (current) { - current.destroy(); - this.multiSelect = null; - } - return; - } - - if (current) { - return; - } - - const self = this; - import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => { - self.multiSelect = new MultiSelect({ - container: self, - bindOnClick: false - }); - }); +function getShortcutOptions() { + return { + click: false }; +} - function onDrop(evt, itemsContainer) { - const el = evt.item; +ItemsContainerPrototype.enableMultiSelect = function (enabled) { + const current = this.multiSelect; - const newIndex = evt.newIndex; - const itemId = el.getAttribute('data-playlistitemid'); - const playlistId = el.getAttribute('data-playlistid'); - - if (!playlistId) { - const oldIndex = evt.oldIndex; - el.dispatchEvent(new CustomEvent('itemdrop', { - detail: { - oldIndex: oldIndex, - newIndex: newIndex, - playlistItemId: itemId - }, - bubbles: true, - cancelable: false - })); - return; + if (!enabled) { + if (current) { + current.destroy(); + this.multiSelect = null; } - - const serverId = el.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - - loading.show(); - - apiClient.ajax({ - url: apiClient.getUrl('Playlists/' + playlistId + '/Items/' + itemId + '/Move/' + newIndex), - type: 'POST' - }).then(function () { - loading.hide(); - }, function () { - loading.hide(); - itemsContainer.refreshItems(); - }); + return; } - ItemsContainerPrototype.enableDragReordering = function (enabled) { - const current = this.sortable; - if (!enabled) { - if (current) { - current.destroy(); - this.sortable = null; - } - return; - } + if (current) { + return; + } + const self = this; + import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => { + self.multiSelect = new MultiSelect({ + container: self, + bindOnClick: false + }); + }); +}; + +function onDrop(evt, itemsContainer) { + const el = evt.item; + + const newIndex = evt.newIndex; + const itemId = el.getAttribute('data-playlistitemid'); + const playlistId = el.getAttribute('data-playlistid'); + + if (!playlistId) { + const oldIndex = evt.oldIndex; + el.dispatchEvent(new CustomEvent('itemdrop', { + detail: { + oldIndex: oldIndex, + newIndex: newIndex, + playlistItemId: itemId + }, + bubbles: true, + cancelable: false + })); + return; + } + + const serverId = el.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + loading.show(); + + apiClient.ajax({ + url: apiClient.getUrl('Playlists/' + playlistId + '/Items/' + itemId + '/Move/' + newIndex), + type: 'POST' + }).then(function () { + loading.hide(); + }, function () { + loading.hide(); + itemsContainer.refreshItems(); + }); +} + +ItemsContainerPrototype.enableDragReordering = function (enabled) { + const current = this.sortable; + if (!enabled) { if (current) { - return; + current.destroy(); + this.sortable = null; } + return; + } - const self = this; - self.sortable = new Sortable(self, { - draggable: '.listItem', - handle: '.listViewDragHandle', + if (current) { + return; + } - // dragging ended - onEnd: function (evt) { - return onDrop(evt, self); - } - }); - }; + const self = this; + self.sortable = new Sortable(self, { + draggable: '.listItem', + handle: '.listViewDragHandle', - function onUserDataChanged(e, apiClient, userData) { - const itemsContainer = this; + // dragging ended + onEnd: function (evt) { + return onDrop(evt, self); + } + }); +}; - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onUserDataChanged(userData, itemsContainer); - }); +function onUserDataChanged(e, apiClient, userData) { + const itemsContainer = this; - const eventsToMonitor = getEventsToMonitor(itemsContainer); + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onUserDataChanged(userData, itemsContainer); + }); - // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1 + const eventsToMonitor = getEventsToMonitor(itemsContainer); + + // TODO: Check user data change reason? + if (eventsToMonitor.indexOf('markfavorite') !== -1 || eventsToMonitor.indexOf('markplayed') !== -1 - ) { - itemsContainer.notifyRefreshNeeded(); - } - } - - function getEventsToMonitor(itemsContainer) { - const monitor = itemsContainer.getAttribute('data-monitor'); - if (monitor) { - return monitor.split(','); - } - - return []; - } - - function onTimerCreated(e, apiClient, data) { - const itemsContainer = this; - - if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - const programId = data.ProgramId; - // This could be null, not supported by all tv providers - const newTimerId = data.Id; - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer); - }); - } - - function onSeriesTimerCreated() { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - } - } - - function onTimerCancelled(e, apiClient, data) { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onTimerCancelled(data.Id, itemsContainer); - }); - } - - function onSeriesTimerCancelled(e, apiClient, data) { - const itemsContainer = this; - if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { - itemsContainer.notifyRefreshNeeded(); - return; - } - - import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { - cardBuilder.onSeriesTimerCancelled(data.Id, itemsContainer); - }); - } - - function onLibraryChanged(e, apiClient, data) { - const itemsContainer = this; - - const eventsToMonitor = getEventsToMonitor(itemsContainer); - if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { - // yes this is an assumption - return; - } - - const itemsAdded = data.ItemsAdded || []; - const itemsRemoved = data.ItemsRemoved || []; - if (!itemsAdded.length && !itemsRemoved.length) { - return; - } - - const parentId = itemsContainer.getAttribute('data-parentid'); - if (parentId) { - const foldersAddedTo = data.FoldersAddedTo || []; - const foldersRemovedFrom = data.FoldersRemovedFrom || []; - const collectionFolders = data.CollectionFolders || []; - - if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { - return; - } - } - + ) { itemsContainer.notifyRefreshNeeded(); } +} - function onPlaybackStopped(e, stopInfo) { - const itemsContainer = this; - const state = stopInfo.state; +function getEventsToMonitor(itemsContainer) { + const monitor = itemsContainer.getAttribute('data-monitor'); + if (monitor) { + return monitor.split(','); + } - const eventsToMonitor = getEventsToMonitor(itemsContainer); - if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { - if (eventsToMonitor.indexOf('videoplayback') !== -1) { - itemsContainer.notifyRefreshNeeded(true); - return; - } - } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + return []; +} + +function onTimerCreated(e, apiClient, data) { + const itemsContainer = this; + + if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + const programId = data.ProgramId; + // This could be null, not supported by all tv providers + const newTimerId = data.Id; + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer); + }); +} + +function onSeriesTimerCreated() { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + } +} + +function onTimerCancelled(e, apiClient, data) { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onTimerCancelled(data.Id, itemsContainer); + }); +} + +function onSeriesTimerCancelled(e, apiClient, data) { + const itemsContainer = this; + if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { + itemsContainer.notifyRefreshNeeded(); + return; + } + + import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => { + cardBuilder.onSeriesTimerCancelled(data.Id, itemsContainer); + }); +} + +function onLibraryChanged(e, apiClient, data) { + const itemsContainer = this; + + const eventsToMonitor = getEventsToMonitor(itemsContainer); + if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) { + // yes this is an assumption + return; + } + + const itemsAdded = data.ItemsAdded || []; + const itemsRemoved = data.ItemsRemoved || []; + if (!itemsAdded.length && !itemsRemoved.length) { + return; + } + + const parentId = itemsContainer.getAttribute('data-parentid'); + if (parentId) { + const foldersAddedTo = data.FoldersAddedTo || []; + const foldersRemovedFrom = data.FoldersRemovedFrom || []; + const collectionFolders = data.CollectionFolders || []; + + if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) { + return; + } + } + + itemsContainer.notifyRefreshNeeded(); +} + +function onPlaybackStopped(e, stopInfo) { + const itemsContainer = this; + const state = stopInfo.state; + + const eventsToMonitor = getEventsToMonitor(itemsContainer); + if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + if (eventsToMonitor.indexOf('videoplayback') !== -1) { itemsContainer.notifyRefreshNeeded(true); return; } + } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + itemsContainer.notifyRefreshNeeded(true); + return; } +} - function addNotificationEvent(instance, name, handler, owner) { - const localHandler = handler.bind(instance); +function addNotificationEvent(instance, name, handler, owner) { + const localHandler = handler.bind(instance); + owner = owner || serverNotifications; + Events.on(owner, name, localHandler); + instance['event_' + name] = localHandler; +} + +function removeNotificationEvent(instance, name, owner) { + const handler = instance['event_' + name]; + if (handler) { owner = owner || serverNotifications; - Events.on(owner, name, localHandler); - instance['event_' + name] = localHandler; + Events.off(owner, name, handler); + instance['event_' + name] = null; } +} - function removeNotificationEvent(instance, name, owner) { - const handler = instance['event_' + name]; - if (handler) { - owner = owner || serverNotifications; - Events.off(owner, name, handler); - instance['event_' + name] = null; +ItemsContainerPrototype.createdCallback = function () { + this.classList.add('itemsContainer'); +}; + +ItemsContainerPrototype.attachedCallback = function () { + this.addEventListener('click', onClick); + + if (browser.touch) { + this.addEventListener('contextmenu', disableEvent); + } else { + if (this.getAttribute('data-contextmenu') !== 'false') { + this.addEventListener('contextmenu', onContextMenu); } } - ItemsContainerPrototype.createdCallback = function () { - this.classList.add('itemsContainer'); - }; + if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { + this.enableMultiSelect(true); + } - ItemsContainerPrototype.attachedCallback = function () { - this.addEventListener('click', onClick); + if (layoutManager.tv) { + this.classList.add('itemsContainer-tv'); + } - if (browser.touch) { - this.addEventListener('contextmenu', disableEvent); + itemShortcuts.on(this, getShortcutOptions()); + + addNotificationEvent(this, 'UserDataChanged', onUserDataChanged); + addNotificationEvent(this, 'TimerCreated', onTimerCreated); + addNotificationEvent(this, 'SeriesTimerCreated', onSeriesTimerCreated); + addNotificationEvent(this, 'TimerCancelled', onTimerCancelled); + addNotificationEvent(this, 'SeriesTimerCancelled', onSeriesTimerCancelled); + addNotificationEvent(this, 'LibraryChanged', onLibraryChanged); + addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager); + + if (this.getAttribute('data-dragreorder') === 'true') { + this.enableDragReordering(true); + } +}; + +ItemsContainerPrototype.detachedCallback = function () { + clearRefreshInterval(this); + + this.enableMultiSelect(false); + this.enableDragReordering(false); + this.removeEventListener('click', onClick); + this.removeEventListener('contextmenu', onContextMenu); + this.removeEventListener('contextmenu', disableEvent); + + itemShortcuts.off(this, getShortcutOptions()); + + removeNotificationEvent(this, 'UserDataChanged'); + removeNotificationEvent(this, 'TimerCreated'); + removeNotificationEvent(this, 'SeriesTimerCreated'); + removeNotificationEvent(this, 'TimerCancelled'); + removeNotificationEvent(this, 'SeriesTimerCancelled'); + removeNotificationEvent(this, 'LibraryChanged'); + removeNotificationEvent(this, 'playbackstop', playbackManager); + + this.fetchData = null; + this.getItemsHtml = null; + this.parentContainer = null; +}; + +ItemsContainerPrototype.pause = function () { + clearRefreshInterval(this, true); + this.paused = true; +}; + +ItemsContainerPrototype.resume = function (options) { + this.paused = false; + + const refreshIntervalEndTime = this.refreshIntervalEndTime; + if (refreshIntervalEndTime) { + const remainingMs = refreshIntervalEndTime - new Date().getTime(); + if (remainingMs > 0 && !this.needsRefresh) { + resetRefreshInterval(this, remainingMs); } else { - if (this.getAttribute('data-contextmenu') !== 'false') { - this.addEventListener('contextmenu', onContextMenu); - } + this.needsRefresh = true; + this.refreshIntervalEndTime = null; } + } - if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { - this.enableMultiSelect(true); - } + if (this.needsRefresh || (options && options.refresh)) { + return this.refreshItems(); + } - if (layoutManager.tv) { - this.classList.add('itemsContainer-tv'); - } - - itemShortcuts.on(this, getShortcutOptions()); - - addNotificationEvent(this, 'UserDataChanged', onUserDataChanged); - addNotificationEvent(this, 'TimerCreated', onTimerCreated); - addNotificationEvent(this, 'SeriesTimerCreated', onSeriesTimerCreated); - addNotificationEvent(this, 'TimerCancelled', onTimerCancelled); - addNotificationEvent(this, 'SeriesTimerCancelled', onSeriesTimerCancelled); - addNotificationEvent(this, 'LibraryChanged', onLibraryChanged); - addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager); - - if (this.getAttribute('data-dragreorder') === 'true') { - this.enableDragReordering(true); - } - }; - - ItemsContainerPrototype.detachedCallback = function () { - clearRefreshInterval(this); - - this.enableMultiSelect(false); - this.enableDragReordering(false); - this.removeEventListener('click', onClick); - this.removeEventListener('contextmenu', onContextMenu); - this.removeEventListener('contextmenu', disableEvent); - - itemShortcuts.off(this, getShortcutOptions()); - - removeNotificationEvent(this, 'UserDataChanged'); - removeNotificationEvent(this, 'TimerCreated'); - removeNotificationEvent(this, 'SeriesTimerCreated'); - removeNotificationEvent(this, 'TimerCancelled'); - removeNotificationEvent(this, 'SeriesTimerCancelled'); - removeNotificationEvent(this, 'LibraryChanged'); - removeNotificationEvent(this, 'playbackstop', playbackManager); - - this.fetchData = null; - this.getItemsHtml = null; - this.parentContainer = null; - }; - - ItemsContainerPrototype.pause = function () { - clearRefreshInterval(this, true); - this.paused = true; - }; - - ItemsContainerPrototype.resume = function (options) { - this.paused = false; - - const refreshIntervalEndTime = this.refreshIntervalEndTime; - if (refreshIntervalEndTime) { - const remainingMs = refreshIntervalEndTime - new Date().getTime(); - if (remainingMs > 0 && !this.needsRefresh) { - resetRefreshInterval(this, remainingMs); - } else { - this.needsRefresh = true; - this.refreshIntervalEndTime = null; - } - } - - if (this.needsRefresh || (options && options.refresh)) { - return this.refreshItems(); - } + return Promise.resolve(); +}; +ItemsContainerPrototype.refreshItems = function () { + if (!this.fetchData) { return Promise.resolve(); - }; + } - ItemsContainerPrototype.refreshItems = function () { - if (!this.fetchData) { - return Promise.resolve(); + if (this.paused) { + this.needsRefresh = true; + return Promise.resolve(); + } + + this.needsRefresh = false; + + return this.fetchData().then(onDataFetched.bind(this)); +}; + +ItemsContainerPrototype.notifyRefreshNeeded = function (isInForeground) { + if (this.paused) { + this.needsRefresh = true; + return; + } + + const timeout = this.refreshTimeout; + if (timeout) { + clearTimeout(timeout); + } + + if (isInForeground === true) { + this.refreshItems(); + } else { + this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000); + } +}; + +function clearRefreshInterval(itemsContainer, isPausing) { + if (itemsContainer.refreshInterval) { + clearInterval(itemsContainer.refreshInterval); + itemsContainer.refreshInterval = null; + + if (!isPausing) { + itemsContainer.refreshIntervalEndTime = null; } + } +} - if (this.paused) { - this.needsRefresh = true; - return Promise.resolve(); - } +function resetRefreshInterval(itemsContainer, intervalMs) { + clearRefreshInterval(itemsContainer); - this.needsRefresh = false; + if (!intervalMs) { + intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10); + } - return this.fetchData().then(onDataFetched.bind(this)); - }; + if (intervalMs) { + itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs); + itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs; + } +} - ItemsContainerPrototype.notifyRefreshNeeded = function (isInForeground) { - if (this.paused) { - this.needsRefresh = true; - return; - } +function onDataFetched(result) { + const items = result.Items || result; - const timeout = this.refreshTimeout; - if (timeout) { - clearTimeout(timeout); - } - - if (isInForeground === true) { - this.refreshItems(); + const parentContainer = this.parentContainer; + if (parentContainer) { + if (items.length) { + parentContainer.classList.remove('hide'); } else { - this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000); + parentContainer.classList.add('hide'); } - }; + } - function clearRefreshInterval(itemsContainer, isPausing) { - if (itemsContainer.refreshInterval) { - clearInterval(itemsContainer.refreshInterval); - itemsContainer.refreshInterval = null; + const activeElement = document.activeElement; + let focusId; + let hasActiveElement; - if (!isPausing) { - itemsContainer.refreshIntervalEndTime = null; + if (this.contains(activeElement)) { + hasActiveElement = true; + focusId = activeElement.getAttribute('data-id'); + } + + this.innerHTML = this.getItemsHtml(items); + + imageLoader.lazyChildren(this); + + if (hasActiveElement) { + setFocus(this, focusId); + } + + resetRefreshInterval(this); + + if (this.afterRefresh) { + this.afterRefresh(result); + } +} + +function setFocus(itemsContainer, focusId) { + if (focusId) { + const newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]'); + if (newElement) { + try { + focusManager.focus(newElement); + return; + } catch (err) { + console.error(err); } } } - function resetRefreshInterval(itemsContainer, intervalMs) { - clearRefreshInterval(itemsContainer); + focusManager.autoFocus(itemsContainer); +} - if (!intervalMs) { - intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10); - } +document.registerElement('emby-itemscontainer', { + prototype: ItemsContainerPrototype, + extends: 'div' +}); - if (intervalMs) { - itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs); - itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs; - } - } - - function onDataFetched(result) { - const items = result.Items || result; - - const parentContainer = this.parentContainer; - if (parentContainer) { - if (items.length) { - parentContainer.classList.remove('hide'); - } else { - parentContainer.classList.add('hide'); - } - } - - const activeElement = document.activeElement; - let focusId; - let hasActiveElement; - - if (this.contains(activeElement)) { - hasActiveElement = true; - focusId = activeElement.getAttribute('data-id'); - } - - this.innerHTML = this.getItemsHtml(items); - - imageLoader.lazyChildren(this); - - if (hasActiveElement) { - setFocus(this, focusId); - } - - resetRefreshInterval(this); - - if (this.afterRefresh) { - this.afterRefresh(result); - } - } - - function setFocus(itemsContainer, focusId) { - if (focusId) { - const newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]'); - if (newElement) { - try { - focusManager.focus(newElement); - return; - } catch (err) { - console.error(err); - } - } - } - - focusManager.autoFocus(itemsContainer); - } - - document.registerElement('emby-itemscontainer', { - prototype: ItemsContainerPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-playstatebutton/emby-playstatebutton.js b/src/elements/emby-playstatebutton/emby-playstatebutton.js index 79ceb56dd6..6e7756d902 100644 --- a/src/elements/emby-playstatebutton/emby-playstatebutton.js +++ b/src/elements/emby-playstatebutton/emby-playstatebutton.js @@ -4,151 +4,148 @@ import Events from '../../utils/events.ts'; import EmbyButtonPrototype from '../../elements/emby-button/emby-button'; import ServerConnections from '../../components/ServerConnections'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function onClick() { + const button = this; + const id = button.getAttribute('data-id'); + const serverId = button.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + if (!button.classList.contains('playstatebutton-played')) { + apiClient.markPlayed(apiClient.getCurrentUserId(), id, new Date()); + setState(button, true); + } else { + apiClient.markUnplayed(apiClient.getCurrentUserId(), id, new Date()); + setState(button, false); + } +} + +function onUserDataChanged(e, apiClient, userData) { + const button = this; + if (userData.ItemId === button.getAttribute('data-id')) { + setState(button, userData.Played); + } +} + +function setState(button, played, updateAttribute) { + let icon = button.iconElement; + if (!icon) { + button.iconElement = button.querySelector('.material-icons'); + icon = button.iconElement; } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + if (played) { + button.classList.add('playstatebutton-played'); + if (icon) { + icon.classList.add('playstatebutton-icon-played'); + icon.classList.remove('playstatebutton-icon-unplayed'); + } + } else { + button.classList.remove('playstatebutton-played'); + if (icon) { + icon.classList.remove('playstatebutton-icon-played'); + icon.classList.add('playstatebutton-icon-unplayed'); } } - function onClick() { - const button = this; - const id = button.getAttribute('data-id'); - const serverId = button.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); - - if (!button.classList.contains('playstatebutton-played')) { - apiClient.markPlayed(apiClient.getCurrentUserId(), id, new Date()); - setState(button, true); - } else { - apiClient.markUnplayed(apiClient.getCurrentUserId(), id, new Date()); - setState(button, false); - } + if (updateAttribute !== false) { + button.setAttribute('data-played', played); } - function onUserDataChanged(e, apiClient, userData) { - const button = this; - if (userData.ItemId === button.getAttribute('data-id')) { - setState(button, userData.Played); - } + setTitle(button, button.getAttribute('data-type'), played); +} + +function setTitle(button, itemType, played) { + if (itemType !== 'AudioBook' && itemType !== 'AudioPodcast') { + button.title = played ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); + } else { + button.title = played ? globalize.translate('Played') : globalize.translate('MarkPlayed'); } - function setState(button, played, updateAttribute) { - let icon = button.iconElement; - if (!icon) { - button.iconElement = button.querySelector('.material-icons'); - icon = button.iconElement; - } + const text = button.querySelector('.button-text'); + if (text) { + text.innerText = button.title; + } +} - if (played) { - button.classList.add('playstatebutton-played'); - if (icon) { - icon.classList.add('playstatebutton-icon-played'); - icon.classList.remove('playstatebutton-icon-unplayed'); - } - } else { - button.classList.remove('playstatebutton-played'); - if (icon) { - icon.classList.remove('playstatebutton-icon-played'); - icon.classList.add('playstatebutton-icon-unplayed'); - } - } +function clearEvents(button) { + button.removeEventListener('click', onClick); + removeNotificationEvent(button, 'UserDataChanged'); +} - if (updateAttribute !== false) { - button.setAttribute('data-played', played); - } +function bindEvents(button) { + clearEvents(button); - setTitle(button, button.getAttribute('data-type'), played); + button.addEventListener('click', onClick); + addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); +} + +const EmbyPlaystateButtonPrototype = Object.create(EmbyButtonPrototype); + +EmbyPlaystateButtonPrototype.createdCallback = function () { + // base method + if (EmbyButtonPrototype.createdCallback) { + EmbyButtonPrototype.createdCallback.call(this); + } +}; + +EmbyPlaystateButtonPrototype.attachedCallback = function () { + // base method + if (EmbyButtonPrototype.attachedCallback) { + EmbyButtonPrototype.attachedCallback.call(this); } - function setTitle(button, itemType, played) { - if (itemType !== 'AudioBook' && itemType !== 'AudioPodcast') { - button.title = played ? globalize.translate('Watched') : globalize.translate('MarkPlayed'); - } else { - button.title = played ? globalize.translate('Played') : globalize.translate('MarkPlayed'); - } + const itemId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); + if (itemId && serverId) { + setState(this, this.getAttribute('data-played') === 'true', false); + bindEvents(this); + } +}; - const text = button.querySelector('.button-text'); - if (text) { - text.innerText = button.title; - } +EmbyPlaystateButtonPrototype.detachedCallback = function () { + // base method + if (EmbyButtonPrototype.detachedCallback) { + EmbyButtonPrototype.detachedCallback.call(this); } - function clearEvents(button) { - button.removeEventListener('click', onClick); - removeNotificationEvent(button, 'UserDataChanged'); - } + clearEvents(this); + this.iconElement = null; +}; - function bindEvents(button) { - clearEvents(button); - - button.addEventListener('click', onClick); - addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); - } - - const EmbyPlaystateButtonPrototype = Object.create(EmbyButtonPrototype); - - EmbyPlaystateButtonPrototype.createdCallback = function () { - // base method - if (EmbyButtonPrototype.createdCallback) { - EmbyButtonPrototype.createdCallback.call(this); - } - }; - - EmbyPlaystateButtonPrototype.attachedCallback = function () { - // base method - if (EmbyButtonPrototype.attachedCallback) { - EmbyButtonPrototype.attachedCallback.call(this); - } - - const itemId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); - if (itemId && serverId) { - setState(this, this.getAttribute('data-played') === 'true', false); - bindEvents(this); - } - }; - - EmbyPlaystateButtonPrototype.detachedCallback = function () { - // base method - if (EmbyButtonPrototype.detachedCallback) { - EmbyButtonPrototype.detachedCallback.call(this); - } +EmbyPlaystateButtonPrototype.setItem = function (item) { + if (item) { + this.setAttribute('data-id', item.Id); + this.setAttribute('data-serverid', item.ServerId); + this.setAttribute('data-type', item.Type); + const played = item.UserData && item.UserData.Played; + setState(this, played); + bindEvents(this); + } else { + this.removeAttribute('data-id'); + this.removeAttribute('data-serverid'); + this.removeAttribute('data-type'); + this.removeAttribute('data-played'); clearEvents(this); - this.iconElement = null; - }; + } +}; - EmbyPlaystateButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); - this.setAttribute('data-serverid', item.ServerId); - this.setAttribute('data-type', item.Type); +document.registerElement('emby-playstatebutton', { + prototype: EmbyPlaystateButtonPrototype, + extends: 'button' +}); - const played = item.UserData && item.UserData.Played; - setState(this, played); - bindEvents(this); - } else { - this.removeAttribute('data-id'); - this.removeAttribute('data-serverid'); - this.removeAttribute('data-type'); - this.removeAttribute('data-played'); - clearEvents(this); - } - }; - - document.registerElement('emby-playstatebutton', { - prototype: EmbyPlaystateButtonPrototype, - extends: 'button' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js index cad2f4bbfa..6fb1c0e5b2 100644 --- a/src/elements/emby-progressbar/emby-progressbar.js +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -1,42 +1,40 @@ -/* eslint-disable indent */ - const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); +const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); - function onAutoTimeProgress() { - const start = parseInt(this.getAttribute('data-starttime'), 10); - const end = parseInt(this.getAttribute('data-endtime'), 10); +function onAutoTimeProgress() { + const start = parseInt(this.getAttribute('data-starttime'), 10); + const end = parseInt(this.getAttribute('data-endtime'), 10); - const now = new Date().getTime(); - const total = end - start; - let pct = 100 * ((now - start) / total); + const now = new Date().getTime(); + const total = end - start; + let pct = 100 * ((now - start) / total); - pct = Math.min(100, pct); - pct = Math.max(0, pct); + pct = Math.min(100, pct); + pct = Math.max(0, pct); - const itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); - itemProgressBarForeground.style.width = pct + '%'; + const itemProgressBarForeground = this.querySelector('.itemProgressBarForeground'); + itemProgressBarForeground.style.width = pct + '%'; +} + +ProgressBarPrototype.attachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); } - ProgressBarPrototype.attachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - } + if (this.getAttribute('data-automode') === 'time') { + this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); + } +}; - if (this.getAttribute('data-automode') === 'time') { - this.timeInterval = setInterval(onAutoTimeProgress.bind(this), 60000); - } - }; +ProgressBarPrototype.detachedCallback = function () { + if (this.timeInterval) { + clearInterval(this.timeInterval); + this.timeInterval = null; + } +}; - ProgressBarPrototype.detachedCallback = function () { - if (this.timeInterval) { - clearInterval(this.timeInterval); - this.timeInterval = null; - } - }; +document.registerElement('emby-progressbar', { + prototype: ProgressBarPrototype, + extends: 'div' +}); - document.registerElement('emby-progressbar', { - prototype: ProgressBarPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-progressring/emby-progressring.js b/src/elements/emby-progressring/emby-progressring.js index 63d9fd2221..9ef8285250 100644 --- a/src/elements/emby-progressring/emby-progressring.js +++ b/src/elements/emby-progressring/emby-progressring.js @@ -4,98 +4,95 @@ import template from './emby-progressring.template.html'; import { getCurrentDateTimeLocale } from '../../scripts/globalize'; import { toPercent } from '../../utils/number.ts'; -/* eslint-disable indent */ +const EmbyProgressRing = Object.create(HTMLDivElement.prototype); - const EmbyProgressRing = Object.create(HTMLDivElement.prototype); +EmbyProgressRing.createdCallback = function () { + this.classList.add('progressring'); + this.setAttribute('dir', 'ltr'); + const instance = this; - EmbyProgressRing.createdCallback = function () { - this.classList.add('progressring'); - this.setAttribute('dir', 'ltr'); - const instance = this; + instance.innerHTML = template; - instance.innerHTML = template; - - if (window.MutationObserver) { - // create an observer instance - const observer = new MutationObserver(function (mutations) { - mutations.forEach(function () { - instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); - }); + if (window.MutationObserver) { + // create an observer instance + const observer = new MutationObserver(function (mutations) { + mutations.forEach(function () { + instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); }); + }); - // configuration of the observer: - const config = { attributes: true, childList: false, characterData: false }; + // configuration of the observer: + const config = { attributes: true, childList: false, characterData: false }; - // pass in the target node, as well as the observer options - observer.observe(instance, config); + // pass in the target node, as well as the observer options + observer.observe(instance, config); - instance.observer = observer; - } + instance.observer = observer; + } - instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); - }; + instance.setProgress(parseFloat(instance.getAttribute('data-progress') || '0')); +}; - EmbyProgressRing.setProgress = function (progress) { - progress = Math.floor(progress); +EmbyProgressRing.setProgress = function (progress) { + progress = Math.floor(progress); - let angle; + let angle; - if (progress < 25) { - angle = -90 + (progress / 100) * 360; + if (progress < 25) { + angle = -90 + (progress / 100) * 360; - this.querySelector('.animate-0-25-b').style.transform = 'rotate(' + angle + 'deg)'; + this.querySelector('.animate-0-25-b').style.transform = 'rotate(' + angle + 'deg)'; - this.querySelector('.animate-25-50-b').style.transform = 'rotate(-90deg)'; - this.querySelector('.animate-50-75-b').style.transform = 'rotate(-90deg)'; - this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; - } else if (progress >= 25 && progress < 50) { - angle = -90 + ((progress - 25) / 100) * 360; + this.querySelector('.animate-25-50-b').style.transform = 'rotate(-90deg)'; + this.querySelector('.animate-50-75-b').style.transform = 'rotate(-90deg)'; + this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; + } else if (progress >= 25 && progress < 50) { + angle = -90 + ((progress - 25) / 100) * 360; - this.querySelector('.animate-0-25-b').style.transform = 'none'; - this.querySelector('.animate-25-50-b').style.transform = 'rotate(' + angle + 'deg)'; + this.querySelector('.animate-0-25-b').style.transform = 'none'; + this.querySelector('.animate-25-50-b').style.transform = 'rotate(' + angle + 'deg)'; - this.querySelector('.animate-50-75-b').style.transform = 'rotate(-90deg)'; - this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; - } else if (progress >= 50 && progress < 75) { - angle = -90 + ((progress - 50) / 100) * 360; + this.querySelector('.animate-50-75-b').style.transform = 'rotate(-90deg)'; + this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; + } else if (progress >= 50 && progress < 75) { + angle = -90 + ((progress - 50) / 100) * 360; - this.querySelector('.animate-0-25-b').style.transform = 'none'; - this.querySelector('.animate-25-50-b').style.transform = 'none'; - this.querySelector('.animate-50-75-b').style.transform = 'rotate(' + angle + 'deg)'; + this.querySelector('.animate-0-25-b').style.transform = 'none'; + this.querySelector('.animate-25-50-b').style.transform = 'none'; + this.querySelector('.animate-50-75-b').style.transform = 'rotate(' + angle + 'deg)'; - this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; - } else if (progress >= 75 && progress <= 100) { - angle = -90 + ((progress - 75) / 100) * 360; + this.querySelector('.animate-75-100-b').style.transform = 'rotate(-90deg)'; + } else if (progress >= 75 && progress <= 100) { + angle = -90 + ((progress - 75) / 100) * 360; - this.querySelector('.animate-0-25-b').style.transform = 'none'; - this.querySelector('.animate-25-50-b').style.transform = 'none'; - this.querySelector('.animate-50-75-b').style.transform = 'none'; - this.querySelector('.animate-75-100-b').style.transform = 'rotate(' + angle + 'deg)'; - } + this.querySelector('.animate-0-25-b').style.transform = 'none'; + this.querySelector('.animate-25-50-b').style.transform = 'none'; + this.querySelector('.animate-50-75-b').style.transform = 'none'; + this.querySelector('.animate-75-100-b').style.transform = 'rotate(' + angle + 'deg)'; + } - this.querySelector('.progressring-text').innerHTML = toPercent(progress / 100, getCurrentDateTimeLocale()); - }; + this.querySelector('.progressring-text').innerHTML = toPercent(progress / 100, getCurrentDateTimeLocale()); +}; - EmbyProgressRing.attachedCallback = function () { - // no-op - }; +EmbyProgressRing.attachedCallback = function () { + // no-op +}; - EmbyProgressRing.detachedCallback = function () { - const observer = this.observer; +EmbyProgressRing.detachedCallback = function () { + const observer = this.observer; - if (observer) { - // later, you can stop observing - observer.disconnect(); + if (observer) { + // later, you can stop observing + observer.disconnect(); - this.observer = null; - } - }; + this.observer = null; + } +}; - document.registerElement('emby-progressring', { - prototype: EmbyProgressRing, - extends: 'div' - }); +document.registerElement('emby-progressring', { + prototype: EmbyProgressRing, + extends: 'div' +}); - export default EmbyProgressRing; +export default EmbyProgressRing; -/* eslint-enable indent */ diff --git a/src/elements/emby-radio/emby-radio.js b/src/elements/emby-radio/emby-radio.js index 0dadc38f99..10820c1c1a 100644 --- a/src/elements/emby-radio/emby-radio.js +++ b/src/elements/emby-radio/emby-radio.js @@ -3,80 +3,77 @@ import browser from '../../scripts/browser'; import 'webcomponents.js/webcomponents-lite'; import './emby-radio.scss'; -/* eslint-disable indent */ +const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); - const EmbyRadioPrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + // Real (non-emulator) Tizen does nothing on Space + if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - // Real (non-emulator) Tizen does nothing on Space - if (e.keyCode === 13 || (e.keyCode === 32 && browser.tizen)) { - e.preventDefault(); + if (!this.checked) { + this.checked = true; - if (!this.checked) { - this.checked = true; - - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); - } - - return false; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); } + + return false; + } +} + +EmbyRadioPrototype.attachedCallback = function () { + const showFocus = !layoutManager.mobile; + + if (this.getAttribute('data-radio') === 'true') { + return; } - EmbyRadioPrototype.attachedCallback = function () { - const showFocus = !layoutManager.mobile; + this.setAttribute('data-radio', 'true'); - if (this.getAttribute('data-radio') === 'true') { - return; - } + this.classList.add('mdl-radio__button'); - this.setAttribute('data-radio', 'true'); + const labelElement = this.parentNode; + labelElement.classList.add('mdl-radio'); + labelElement.classList.add('mdl-js-radio'); + labelElement.classList.add('mdl-js-ripple-effect'); + if (showFocus) { + labelElement.classList.add('show-focus'); + } - this.classList.add('mdl-radio__button'); + const labelTextElement = labelElement.querySelector('span'); - const labelElement = this.parentNode; - labelElement.classList.add('mdl-radio'); - labelElement.classList.add('mdl-js-radio'); - labelElement.classList.add('mdl-js-ripple-effect'); - if (showFocus) { - labelElement.classList.add('show-focus'); - } + labelTextElement.classList.add('radioButtonLabel'); + labelTextElement.classList.add('mdl-radio__label'); - const labelTextElement = labelElement.querySelector('span'); + let html = ''; - labelTextElement.classList.add('radioButtonLabel'); - labelTextElement.classList.add('mdl-radio__label'); + html += '
'; - let html = ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; + html += ''; - html += '
'; + if (showFocus) { + html += '
'; + } - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; - html += ''; + html += '
'; - if (showFocus) { - html += '
'; - } + this.insertAdjacentHTML('afterend', html); - html += '
'; + this.addEventListener('keydown', onKeyDown); +}; - this.insertAdjacentHTML('afterend', html); +document.registerElement('emby-radio', { + prototype: EmbyRadioPrototype, + extends: 'input' +}); - this.addEventListener('keydown', onKeyDown); - }; - - document.registerElement('emby-radio', { - prototype: EmbyRadioPrototype, - extends: 'input' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js index 0cc87df3c1..1c17836bc4 100644 --- a/src/elements/emby-ratingbutton/emby-ratingbutton.js +++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js @@ -4,32 +4,119 @@ import Events from '../../utils/events.ts'; import EmbyButtonPrototype from '../emby-button/emby-button'; import ServerConnections from '../../components/ServerConnections'; -/* eslint-disable indent */ +function addNotificationEvent(instance, name, handler) { + const localHandler = handler.bind(instance); + Events.on(serverNotifications, name, localHandler); + instance[name] = localHandler; +} - function addNotificationEvent(instance, name, handler) { - const localHandler = handler.bind(instance); - Events.on(serverNotifications, name, localHandler); - instance[name] = localHandler; +function removeNotificationEvent(instance, name) { + const handler = instance[name]; + if (handler) { + Events.off(serverNotifications, name, handler); + instance[name] = null; + } +} + +function showPicker(button, apiClient, itemId, likes, isFavorite) { + return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), itemId, !isFavorite); +} + +function onClick() { + const button = this; + const id = button.getAttribute('data-id'); + const serverId = button.getAttribute('data-serverid'); + const apiClient = ServerConnections.getApiClient(serverId); + + let likes = this.getAttribute('data-likes'); + const isFavorite = this.getAttribute('data-isfavorite') === 'true'; + if (likes === 'true') { + likes = true; + } else if (likes === 'false') { + likes = false; + } else { + likes = null; } - function removeNotificationEvent(instance, name) { - const handler = instance[name]; - if (handler) { - Events.off(serverNotifications, name, handler); - instance[name] = null; + showPicker(button, apiClient, id, likes, isFavorite).then(function (userData) { + setState(button, userData.Likes, userData.IsFavorite); + }); +} + +function onUserDataChanged(e, apiClient, userData) { + const button = this; + + if (userData.ItemId === button.getAttribute('data-id')) { + setState(button, userData.Likes, userData.IsFavorite); + } +} + +function setState(button, likes, isFavorite, updateAttribute) { + const icon = button.querySelector('.material-icons'); + + if (isFavorite) { + if (icon) { + icon.classList.add('favorite'); + icon.classList.add('ratingbutton-icon-withrating'); } + + button.classList.add('ratingbutton-withrating'); + } else { + if (icon) { + icon.classList.add('favorite'); + icon.classList.remove('ratingbutton-icon-withrating'); + } + button.classList.remove('ratingbutton-withrating'); } - function showPicker(button, apiClient, itemId, likes, isFavorite) { - return apiClient.updateFavoriteStatus(apiClient.getCurrentUserId(), itemId, !isFavorite); + if (updateAttribute !== false) { + button.setAttribute('data-isfavorite', isFavorite); + + button.setAttribute('data-likes', (likes === null ? '' : likes)); } - function onClick() { - const button = this; - const id = button.getAttribute('data-id'); - const serverId = button.getAttribute('data-serverid'); - const apiClient = ServerConnections.getApiClient(serverId); + setTitle(button, isFavorite); +} +function setTitle(button, isFavorite) { + button.title = isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites'); + + const text = button.querySelector('.button-text'); + if (text) { + text.innerText = button.title; + } +} + +function clearEvents(button) { + button.removeEventListener('click', onClick); + removeNotificationEvent(button, 'UserDataChanged'); +} + +function bindEvents(button) { + clearEvents(button); + + button.addEventListener('click', onClick); + addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); +} + +const EmbyRatingButtonPrototype = Object.create(EmbyButtonPrototype); + +EmbyRatingButtonPrototype.createdCallback = function () { + // base method + if (EmbyButtonPrototype.createdCallback) { + EmbyButtonPrototype.createdCallback.call(this); + } +}; + +EmbyRatingButtonPrototype.attachedCallback = function () { + // base method + if (EmbyButtonPrototype.attachedCallback) { + EmbyButtonPrototype.attachedCallback.call(this); + } + + const itemId = this.getAttribute('data-id'); + const serverId = this.getAttribute('data-serverid'); + if (itemId && serverId) { let likes = this.getAttribute('data-likes'); const isFavorite = this.getAttribute('data-isfavorite') === 'true'; if (likes === 'true') { @@ -40,131 +127,41 @@ import ServerConnections from '../../components/ServerConnections'; likes = null; } - showPicker(button, apiClient, id, likes, isFavorite).then(function (userData) { - setState(button, userData.Likes, userData.IsFavorite); - }); + setState(this, likes, isFavorite, false); + bindEvents(this); + } else { + setTitle(this); + } +}; + +EmbyRatingButtonPrototype.detachedCallback = function () { + // base method + if (EmbyButtonPrototype.detachedCallback) { + EmbyButtonPrototype.detachedCallback.call(this); } - function onUserDataChanged(e, apiClient, userData) { - const button = this; + clearEvents(this); +}; - if (userData.ItemId === button.getAttribute('data-id')) { - setState(button, userData.Likes, userData.IsFavorite); - } - } - - function setState(button, likes, isFavorite, updateAttribute) { - const icon = button.querySelector('.material-icons'); - - if (isFavorite) { - if (icon) { - icon.classList.add('favorite'); - icon.classList.add('ratingbutton-icon-withrating'); - } - - button.classList.add('ratingbutton-withrating'); - } else { - if (icon) { - icon.classList.add('favorite'); - icon.classList.remove('ratingbutton-icon-withrating'); - } - button.classList.remove('ratingbutton-withrating'); - } - - if (updateAttribute !== false) { - button.setAttribute('data-isfavorite', isFavorite); - - button.setAttribute('data-likes', (likes === null ? '' : likes)); - } - - setTitle(button, isFavorite); - } - - function setTitle(button, isFavorite) { - button.title = isFavorite ? globalize.translate('Favorite') : globalize.translate('AddToFavorites'); - - const text = button.querySelector('.button-text'); - if (text) { - text.innerText = button.title; - } - } - - function clearEvents(button) { - button.removeEventListener('click', onClick); - removeNotificationEvent(button, 'UserDataChanged'); - } - - function bindEvents(button) { - clearEvents(button); - - button.addEventListener('click', onClick); - addNotificationEvent(button, 'UserDataChanged', onUserDataChanged); - } - - const EmbyRatingButtonPrototype = Object.create(EmbyButtonPrototype); - - EmbyRatingButtonPrototype.createdCallback = function () { - // base method - if (EmbyButtonPrototype.createdCallback) { - EmbyButtonPrototype.createdCallback.call(this); - } - }; - - EmbyRatingButtonPrototype.attachedCallback = function () { - // base method - if (EmbyButtonPrototype.attachedCallback) { - EmbyButtonPrototype.attachedCallback.call(this); - } - - const itemId = this.getAttribute('data-id'); - const serverId = this.getAttribute('data-serverid'); - if (itemId && serverId) { - let likes = this.getAttribute('data-likes'); - const isFavorite = this.getAttribute('data-isfavorite') === 'true'; - if (likes === 'true') { - likes = true; - } else if (likes === 'false') { - likes = false; - } else { - likes = null; - } - - setState(this, likes, isFavorite, false); - bindEvents(this); - } else { - setTitle(this); - } - }; - - EmbyRatingButtonPrototype.detachedCallback = function () { - // base method - if (EmbyButtonPrototype.detachedCallback) { - EmbyButtonPrototype.detachedCallback.call(this); - } +EmbyRatingButtonPrototype.setItem = function (item) { + if (item) { + this.setAttribute('data-id', item.Id); + this.setAttribute('data-serverid', item.ServerId); + const userData = item.UserData || {}; + setState(this, userData.Likes, userData.IsFavorite); + bindEvents(this); + } else { + this.removeAttribute('data-id'); + this.removeAttribute('data-serverid'); + this.removeAttribute('data-likes'); + this.removeAttribute('data-isfavorite'); clearEvents(this); - }; + } +}; - EmbyRatingButtonPrototype.setItem = function (item) { - if (item) { - this.setAttribute('data-id', item.Id); - this.setAttribute('data-serverid', item.ServerId); +document.registerElement('emby-ratingbutton', { + prototype: EmbyRatingButtonPrototype, + extends: 'button' +}); - const userData = item.UserData || {}; - setState(this, userData.Likes, userData.IsFavorite); - bindEvents(this); - } else { - this.removeAttribute('data-id'); - this.removeAttribute('data-serverid'); - this.removeAttribute('data-likes'); - this.removeAttribute('data-isfavorite'); - clearEvents(this); - } - }; - - document.registerElement('emby-ratingbutton', { - prototype: EmbyRatingButtonPrototype, - extends: 'button' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js index 3d98918d7a..770a435ef6 100644 --- a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -3,198 +3,195 @@ import 'webcomponents.js/webcomponents-lite'; import '../emby-button/paper-icon-button-light'; import globalize from '../../scripts/globalize'; -/* eslint-disable indent */ - const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); - EmbyScrollButtonsPrototype.createdCallback = function () { - // no-op - }; +EmbyScrollButtonsPrototype.createdCallback = function () { + // no-op +}; - function getScrollButtonHtml(direction) { - let html = ''; - const icon = direction === 'left' ? 'chevron_left' : 'chevron_right'; - const title = direction === 'left' ? globalize.translate('Previous') : globalize.translate('Next') ; +function getScrollButtonHtml(direction) { + let html = ''; + const icon = direction === 'left' ? 'chevron_left' : 'chevron_right'; + const title = direction === 'left' ? globalize.translate('Previous') : globalize.translate('Next') ; - html += `'; + html += `'; - return html; + return html; +} + +function getScrollPosition(parent) { + if (parent.getScrollPosition) { + return parent.getScrollPosition(); } - function getScrollPosition(parent) { - if (parent.getScrollPosition) { - return parent.getScrollPosition(); - } + return 0; +} +function getScrollWidth(parent) { + if (parent.getScrollSize) { + return parent.getScrollSize(); + } + + return 0; +} + +function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { + let localeAwarePos = scrollPos; + if (globalize.getIsElementRTL(scrollButtons)) { + localeAwarePos *= -1; + } + + // TODO: Check if hack is really needed + // hack alert add twenty for rounding errors + if (scrollWidth <= scrollSize + 20) { + scrollButtons.scrollButtonsLeft.classList.add('hide'); + scrollButtons.scrollButtonsRight.classList.add('hide'); + } else { + scrollButtons.scrollButtonsLeft.classList.remove('hide'); + scrollButtons.scrollButtonsRight.classList.remove('hide'); + } + + if (localeAwarePos > 0) { + scrollButtons.scrollButtonsLeft.disabled = false; + } else { + scrollButtons.scrollButtonsLeft.disabled = true; + } + + const scrollPosEnd = localeAwarePos + scrollSize; + if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) { + scrollButtons.scrollButtonsRight.disabled = true; + } else { + scrollButtons.scrollButtonsRight.disabled = false; + } +} + +function onScroll() { + const scrollButtons = this; + const scroller = this.scroller; + + const scrollSize = getScrollSize(scroller); + const scrollPos = getScrollPosition(scroller); + const scrollWidth = getScrollWidth(scroller); + + updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth); +} + +function getStyleValue(style, name) { + let value = style.getPropertyValue(name); + if (!value) { return 0; } - function getScrollWidth(parent) { - if (parent.getScrollSize) { - return parent.getScrollSize(); - } - + value = value.replace('px', ''); + if (!value) { return 0; } - function updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth) { - let localeAwarePos = scrollPos; - if (globalize.getIsElementRTL(scrollButtons)) { - localeAwarePos *= -1; - } - - // TODO: Check if hack is really needed - // hack alert add twenty for rounding errors - if (scrollWidth <= scrollSize + 20) { - scrollButtons.scrollButtonsLeft.classList.add('hide'); - scrollButtons.scrollButtonsRight.classList.add('hide'); - } else { - scrollButtons.scrollButtonsLeft.classList.remove('hide'); - scrollButtons.scrollButtonsRight.classList.remove('hide'); - } - - if (localeAwarePos > 0) { - scrollButtons.scrollButtonsLeft.disabled = false; - } else { - scrollButtons.scrollButtonsLeft.disabled = true; - } - - const scrollPosEnd = localeAwarePos + scrollSize; - if (scrollWidth > 0 && scrollPosEnd >= scrollWidth) { - scrollButtons.scrollButtonsRight.disabled = true; - } else { - scrollButtons.scrollButtonsRight.disabled = false; - } + value = parseInt(value, 10); + if (isNaN(value)) { + return 0; } - function onScroll() { - const scrollButtons = this; - const scroller = this.scroller; + return value; +} - const scrollSize = getScrollSize(scroller); - const scrollPos = getScrollPosition(scroller); - const scrollWidth = getScrollWidth(scroller); +function getScrollSize(elem) { + let scrollSize = elem.offsetWidth; + let style = window.getComputedStyle(elem, null); - updateScrollButtons(scrollButtons, scrollSize, scrollPos, scrollWidth); + let paddingLeft = getStyleValue(style, 'padding-left'); + if (paddingLeft) { + scrollSize -= paddingLeft; } - function getStyleValue(style, name) { - let value = style.getPropertyValue(name); - if (!value) { - return 0; - } - - value = value.replace('px', ''); - if (!value) { - return 0; - } - - value = parseInt(value, 10); - if (isNaN(value)) { - return 0; - } - - return value; + let paddingRight = getStyleValue(style, 'padding-right'); + if (paddingRight) { + scrollSize -= paddingRight; } - function getScrollSize(elem) { - let scrollSize = elem.offsetWidth; - let style = window.getComputedStyle(elem, null); + const slider = elem.getScrollSlider(); + style = window.getComputedStyle(slider, null); - let paddingLeft = getStyleValue(style, 'padding-left'); - if (paddingLeft) { - scrollSize -= paddingLeft; - } - - let paddingRight = getStyleValue(style, 'padding-right'); - if (paddingRight) { - scrollSize -= paddingRight; - } - - const slider = elem.getScrollSlider(); - style = window.getComputedStyle(slider, null); - - paddingLeft = getStyleValue(style, 'padding-left'); - if (paddingLeft) { - scrollSize -= paddingLeft; - } - - paddingRight = getStyleValue(style, 'padding-right'); - if (paddingRight) { - scrollSize -= paddingRight; - } - - return scrollSize; + paddingLeft = getStyleValue(style, 'padding-left'); + if (paddingLeft) { + scrollSize -= paddingLeft; } - function onScrollButtonClick() { - const scroller = this.parentNode.nextSibling; - - const direction = this.getAttribute('data-direction'); - const scrollSize = getScrollSize(scroller); - const scrollPos = getScrollPosition(scroller); - - let newPos; - if (direction === 'left') { - newPos = Math.max(0, scrollPos - scrollSize); - } else { - newPos = scrollPos + scrollSize; - } - - if (globalize.getIsRTL() && direction === 'left') { - newPos = scrollPos + scrollSize; - } else if (globalize.getIsRTL()) { - newPos = Math.min(0, scrollPos - scrollSize); - } - - scroller.scrollToPosition(newPos, false); + paddingRight = getStyleValue(style, 'padding-right'); + if (paddingRight) { + scrollSize -= paddingRight; } - EmbyScrollButtonsPrototype.attachedCallback = function () { - const scroller = this.nextSibling; - this.scroller = scroller; + return scrollSize; +} - const parent = this.parentNode; - parent.classList.add('emby-scroller-container'); +function onScrollButtonClick() { + const scroller = this.parentNode.nextSibling; - this.innerHTML = getScrollButtonHtml('left') + getScrollButtonHtml('right'); + const direction = this.getAttribute('data-direction'); + const scrollSize = getScrollSize(scroller); + const scrollPos = getScrollPosition(scroller); - const buttons = this.querySelectorAll('.emby-scrollbuttons-button'); - buttons[0].addEventListener('click', onScrollButtonClick); - buttons[1].addEventListener('click', onScrollButtonClick); - this.scrollButtonsLeft = buttons[0]; - this.scrollButtonsRight = buttons[1]; + let newPos; + if (direction === 'left') { + newPos = Math.max(0, scrollPos - scrollSize); + } else { + newPos = scrollPos + scrollSize; + } - const scrollHandler = onScroll.bind(this); - this.scrollHandler = scrollHandler; - scroller.addScrollEventListener(scrollHandler, { + if (globalize.getIsRTL() && direction === 'left') { + newPos = scrollPos + scrollSize; + } else if (globalize.getIsRTL()) { + newPos = Math.min(0, scrollPos - scrollSize); + } + + scroller.scrollToPosition(newPos, false); +} + +EmbyScrollButtonsPrototype.attachedCallback = function () { + const scroller = this.nextSibling; + this.scroller = scroller; + + const parent = this.parentNode; + parent.classList.add('emby-scroller-container'); + + this.innerHTML = getScrollButtonHtml('left') + getScrollButtonHtml('right'); + + const buttons = this.querySelectorAll('.emby-scrollbuttons-button'); + buttons[0].addEventListener('click', onScrollButtonClick); + buttons[1].addEventListener('click', onScrollButtonClick); + this.scrollButtonsLeft = buttons[0]; + this.scrollButtonsRight = buttons[1]; + + const scrollHandler = onScroll.bind(this); + this.scrollHandler = scrollHandler; + scroller.addScrollEventListener(scrollHandler, { + capture: false, + passive: true + }); +}; + +EmbyScrollButtonsPrototype.detachedCallback = function () { + const parent = this.scroller; + this.scroller = null; + + const scrollHandler = this.scrollHandler; + if (parent && scrollHandler) { + parent.removeScrollEventListener(scrollHandler, { capture: false, passive: true }); - }; + } - EmbyScrollButtonsPrototype.detachedCallback = function () { - const parent = this.scroller; - this.scroller = null; + this.scrollHandler = null; + this.scrollButtonsLeft = null; + this.scrollButtonsRight = null; +}; - const scrollHandler = this.scrollHandler; - if (parent && scrollHandler) { - parent.removeScrollEventListener(scrollHandler, { - capture: false, - passive: true - }); - } +document.registerElement('emby-scrollbuttons', { + prototype: EmbyScrollButtonsPrototype, + extends: 'div' +}); - this.scrollHandler = null; - this.scrollButtonsLeft = null; - this.scrollButtonsRight = null; - }; - - document.registerElement('emby-scrollbuttons', { - prototype: EmbyScrollButtonsPrototype, - extends: 'div' - }); - -/* eslint-enable indent */ diff --git a/src/elements/emby-scroller/emby-scroller.js b/src/elements/emby-scroller/emby-scroller.js index 6a4c6064ec..eb5f3abfe4 100644 --- a/src/elements/emby-scroller/emby-scroller.js +++ b/src/elements/emby-scroller/emby-scroller.js @@ -7,195 +7,192 @@ import browser from '../../scripts/browser'; import 'webcomponents.js/webcomponents-lite'; import './emby-scroller.scss'; -/* eslint-disable indent */ +const ScrollerPrototype = Object.create(HTMLDivElement.prototype); - const ScrollerPrototype = Object.create(HTMLDivElement.prototype); +ScrollerPrototype.createdCallback = function () { + this.classList.add('emby-scroller'); +}; - ScrollerPrototype.createdCallback = function () { - this.classList.add('emby-scroller'); - }; - - function initCenterFocus(elem, scrollerInstance) { - dom.addEventListener(elem, 'focus', function (e) { - const focused = focusManager.focusableParent(e.target); - if (focused) { - scrollerInstance.toCenter(focused); - } - }, { - capture: true, - passive: true - }); - } - - ScrollerPrototype.scrollToBeginning = function () { - if (this.scroller) { - this.scroller.slideTo(0, true); +function initCenterFocus(elem, scrollerInstance) { + dom.addEventListener(elem, 'focus', function (e) { + const focused = focusManager.focusableParent(e.target); + if (focused) { + scrollerInstance.toCenter(focused); } - }; - - ScrollerPrototype.toStart = function (elem, immediate) { - if (this.scroller) { - this.scroller.toStart(elem, immediate); - } - }; - - ScrollerPrototype.toCenter = function (elem, immediate) { - if (this.scroller) { - this.scroller.toCenter(elem, immediate); - } - }; - - ScrollerPrototype.scrollToPosition = function (pos, immediate) { - if (this.scroller) { - this.scroller.slideTo(pos, immediate); - } - }; - - ScrollerPrototype.getScrollPosition = function () { - if (this.scroller) { - return this.scroller.getScrollPosition(); - } - }; - - ScrollerPrototype.getScrollSize = function () { - if (this.scroller) { - return this.scroller.getScrollSize(); - } - }; - - ScrollerPrototype.getScrollEventName = function () { - if (this.scroller) { - return this.scroller.getScrollEventName(); - } - }; - - ScrollerPrototype.getScrollSlider = function () { - if (this.scroller) { - return this.scroller.getScrollSlider(); - } - }; - - ScrollerPrototype.addScrollEventListener = function (fn, options) { - if (this.scroller) { - dom.addEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options); - } - }; - - ScrollerPrototype.removeScrollEventListener = function (fn, options) { - if (this.scroller) { - dom.removeEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options); - } - }; - - function onInputCommand(e) { - const cmd = e.detail.command; - if (cmd === 'end') { - focusManager.focusLast(this, '.' + this.getAttribute('data-navcommands')); - e.preventDefault(); - e.stopPropagation(); - } else if (cmd === 'pageup') { - focusManager.moveFocus(e.target, this, '.' + this.getAttribute('data-navcommands'), -12); - e.preventDefault(); - e.stopPropagation(); - } else if (cmd === 'pagedown') { - focusManager.moveFocus(e.target, this, '.' + this.getAttribute('data-navcommands'), 12); - e.preventDefault(); - e.stopPropagation(); - } - } - - ScrollerPrototype.attachedCallback = function () { - if (this.getAttribute('data-navcommands')) { - inputManager.on(this, onInputCommand); - } - - const horizontal = this.getAttribute('data-horizontal') !== 'false'; - - const slider = this.querySelector('.scrollSlider'); - - if (horizontal) { - slider.style['white-space'] = 'nowrap'; - } - - const scrollFrame = this; - const enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; - - const options = { - horizontal: horizontal, - mouseDragging: 1, - mouseWheel: this.getAttribute('data-mousewheel') !== 'false', - touchDragging: 1, - slidee: slider, - scrollBy: 200, - speed: horizontal ? 270 : 240, - elasticBounds: 1, - dragHandle: 1, - autoImmediate: true, - skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', - dispatchScrollEvent: enableScrollButtons || this.getAttribute('data-scrollevent') === 'true', - hideScrollbar: enableScrollButtons || this.getAttribute('data-hidescrollbar') === 'true', - allowNativeSmoothScroll: this.getAttribute('data-allownativesmoothscroll') === 'true' && !enableScrollButtons, - allowNativeScroll: !enableScrollButtons, - forceHideScrollbars: enableScrollButtons, - // In edge, with the native scroll, the content jumps around when hovering over the buttons - requireAnimation: enableScrollButtons && browser.edge - }; - - // If just inserted it might not have any height yet - yes this is a hack - this.scroller = new scroller(scrollFrame, options); - this.scroller.init(); - this.scroller.reload(); - - if (layoutManager.tv && this.getAttribute('data-centerfocus')) { - initCenterFocus(this, this.scroller); - } - - if (enableScrollButtons) { - loadScrollButtons(this); - } - }; - - function loadScrollButtons(buttonsScroller) { - import('../emby-scrollbuttons/emby-scrollbuttons').then(() => { - buttonsScroller.insertAdjacentHTML('beforebegin', '
'); - }); - } - - ScrollerPrototype.pause = function () { - const headroom = this.headroom; - if (headroom) { - headroom.pause(); - } - }; - - ScrollerPrototype.resume = function () { - const headroom = this.headroom; - if (headroom) { - headroom.resume(); - } - }; - - ScrollerPrototype.detachedCallback = function () { - if (this.getAttribute('data-navcommands')) { - inputManager.off(this, onInputCommand); - } - - const headroom = this.headroom; - if (headroom) { - headroom.destroy(); - this.headroom = null; - } - - const scrollerInstance = this.scroller; - if (scrollerInstance) { - scrollerInstance.destroy(); - this.scroller = null; - } - }; - - document.registerElement('emby-scroller', { - prototype: ScrollerPrototype, - extends: 'div' + }, { + capture: true, + passive: true }); +} + +ScrollerPrototype.scrollToBeginning = function () { + if (this.scroller) { + this.scroller.slideTo(0, true); + } +}; + +ScrollerPrototype.toStart = function (elem, immediate) { + if (this.scroller) { + this.scroller.toStart(elem, immediate); + } +}; + +ScrollerPrototype.toCenter = function (elem, immediate) { + if (this.scroller) { + this.scroller.toCenter(elem, immediate); + } +}; + +ScrollerPrototype.scrollToPosition = function (pos, immediate) { + if (this.scroller) { + this.scroller.slideTo(pos, immediate); + } +}; + +ScrollerPrototype.getScrollPosition = function () { + if (this.scroller) { + return this.scroller.getScrollPosition(); + } +}; + +ScrollerPrototype.getScrollSize = function () { + if (this.scroller) { + return this.scroller.getScrollSize(); + } +}; + +ScrollerPrototype.getScrollEventName = function () { + if (this.scroller) { + return this.scroller.getScrollEventName(); + } +}; + +ScrollerPrototype.getScrollSlider = function () { + if (this.scroller) { + return this.scroller.getScrollSlider(); + } +}; + +ScrollerPrototype.addScrollEventListener = function (fn, options) { + if (this.scroller) { + dom.addEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options); + } +}; + +ScrollerPrototype.removeScrollEventListener = function (fn, options) { + if (this.scroller) { + dom.removeEventListener(this.scroller.getScrollFrame(), this.scroller.getScrollEventName(), fn, options); + } +}; + +function onInputCommand(e) { + const cmd = e.detail.command; + if (cmd === 'end') { + focusManager.focusLast(this, '.' + this.getAttribute('data-navcommands')); + e.preventDefault(); + e.stopPropagation(); + } else if (cmd === 'pageup') { + focusManager.moveFocus(e.target, this, '.' + this.getAttribute('data-navcommands'), -12); + e.preventDefault(); + e.stopPropagation(); + } else if (cmd === 'pagedown') { + focusManager.moveFocus(e.target, this, '.' + this.getAttribute('data-navcommands'), 12); + e.preventDefault(); + e.stopPropagation(); + } +} + +ScrollerPrototype.attachedCallback = function () { + if (this.getAttribute('data-navcommands')) { + inputManager.on(this, onInputCommand); + } + + const horizontal = this.getAttribute('data-horizontal') !== 'false'; + + const slider = this.querySelector('.scrollSlider'); + + if (horizontal) { + slider.style['white-space'] = 'nowrap'; + } + + const scrollFrame = this; + const enableScrollButtons = layoutManager.desktop && horizontal && this.getAttribute('data-scrollbuttons') !== 'false'; + + const options = { + horizontal: horizontal, + mouseDragging: 1, + mouseWheel: this.getAttribute('data-mousewheel') !== 'false', + touchDragging: 1, + slidee: slider, + scrollBy: 200, + speed: horizontal ? 270 : 240, + elasticBounds: 1, + dragHandle: 1, + autoImmediate: true, + skipSlideToWhenVisible: this.getAttribute('data-skipfocuswhenvisible') === 'true', + dispatchScrollEvent: enableScrollButtons || this.getAttribute('data-scrollevent') === 'true', + hideScrollbar: enableScrollButtons || this.getAttribute('data-hidescrollbar') === 'true', + allowNativeSmoothScroll: this.getAttribute('data-allownativesmoothscroll') === 'true' && !enableScrollButtons, + allowNativeScroll: !enableScrollButtons, + forceHideScrollbars: enableScrollButtons, + // In edge, with the native scroll, the content jumps around when hovering over the buttons + requireAnimation: enableScrollButtons && browser.edge + }; + + // If just inserted it might not have any height yet - yes this is a hack + this.scroller = new scroller(scrollFrame, options); + this.scroller.init(); + this.scroller.reload(); + + if (layoutManager.tv && this.getAttribute('data-centerfocus')) { + initCenterFocus(this, this.scroller); + } + + if (enableScrollButtons) { + loadScrollButtons(this); + } +}; + +function loadScrollButtons(buttonsScroller) { + import('../emby-scrollbuttons/emby-scrollbuttons').then(() => { + buttonsScroller.insertAdjacentHTML('beforebegin', '
'); + }); +} + +ScrollerPrototype.pause = function () { + const headroom = this.headroom; + if (headroom) { + headroom.pause(); + } +}; + +ScrollerPrototype.resume = function () { + const headroom = this.headroom; + if (headroom) { + headroom.resume(); + } +}; + +ScrollerPrototype.detachedCallback = function () { + if (this.getAttribute('data-navcommands')) { + inputManager.off(this, onInputCommand); + } + + const headroom = this.headroom; + if (headroom) { + headroom.destroy(); + this.headroom = null; + } + + const scrollerInstance = this.scroller; + if (scrollerInstance) { + scrollerInstance.destroy(); + this.scroller = null; + } +}; + +document.registerElement('emby-scroller', { + prototype: ScrollerPrototype, + extends: 'div' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index b576042821..e4950ebf6c 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -4,152 +4,149 @@ import actionsheet from '../../components/actionSheet/actionSheet'; import './emby-select.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); - const EmbySelectPrototype = Object.create(HTMLSelectElement.prototype); - - function enableNativeMenu() { - if (browser.edgeUwp || browser.xboxOne) { - return true; - } - - // Doesn't seem to work at all - if (browser.tizen || browser.orsay || browser.web0s) { - return false; - } - - // Take advantage of the native input methods - if (browser.tv) { - return true; - } - - return !layoutManager.tv; +function enableNativeMenu() { + if (browser.edgeUwp || browser.xboxOne) { + return true; } - function triggerChange(select) { - const evt = document.createEvent('HTMLEvents'); - evt.initEvent('change', false, true); - select.dispatchEvent(evt); + // Doesn't seem to work at all + if (browser.tizen || browser.orsay || browser.web0s) { + return false; } - function setValue(select, value) { - select.value = value; + // Take advantage of the native input methods + if (browser.tv) { + return true; } - function showActionSheet(select) { - const labelElem = getLabel(select); - const title = labelElem ? (labelElem.textContent || labelElem.innerText) : null; + return !layoutManager.tv; +} - actionsheet.show({ - items: select.options, - positionTo: select, - title: title +function triggerChange(select) { + const evt = document.createEvent('HTMLEvents'); + evt.initEvent('change', false, true); + select.dispatchEvent(evt); +} - }).then(function (value) { - setValue(select, value); - triggerChange(select); - }); - } +function setValue(select, value) { + select.value = value; +} - function getLabel(select) { - let elem = select.previousSibling; - while (elem && elem.tagName !== 'LABEL') { - elem = elem.previousSibling; - } - return elem; - } +function showActionSheet(select) { + const labelElem = getLabel(select); + const title = labelElem ? (labelElem.textContent || labelElem.innerText) : null; - function onFocus() { - const label = getLabel(this); - if (label) { - label.classList.add('selectLabelFocused'); - } - } + actionsheet.show({ + items: select.options, + positionTo: select, + title: title - function onBlur() { - const label = getLabel(this); - if (label) { - label.classList.remove('selectLabelFocused'); - } - } - - function onMouseDown(e) { - // e.button=0 for primary (left) mouse button click - if (!e.button && !enableNativeMenu()) { - e.preventDefault(); - showActionSheet(this); - } - } - - function onKeyDown(e) { - switch (e.keyCode) { - case 13: - if (!enableNativeMenu()) { - e.preventDefault(); - showActionSheet(this); - } - return; - case 37: - case 38: - case 39: - case 40: - if (layoutManager.tv) { - e.preventDefault(); - } - return; - default: - break; - } - } - - let inputId = 0; - - EmbySelectPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embyselect' + inputId; - inputId++; - } - - this.classList.add('emby-select-withcolor'); - - if (layoutManager.tv) { - this.classList.add('emby-select-focusscale'); - } - - this.addEventListener('mousedown', onMouseDown); - this.addEventListener('keydown', onKeyDown); - - this.addEventListener('focus', onFocus); - this.addEventListener('blur', onBlur); - }; - - EmbySelectPrototype.attachedCallback = function () { - if (this.classList.contains('emby-select')) { - return; - } - - this.classList.add('emby-select'); - - const label = this.ownerDocument.createElement('label'); - label.innerText = this.getAttribute('label') || ''; - label.classList.add('selectLabel'); - label.htmlFor = this.id; - this.parentNode?.insertBefore(label, this); - - if (this.classList.contains('emby-select-withcolor')) { - this.parentNode?.insertAdjacentHTML('beforeend', '
0
'); - } - }; - - EmbySelectPrototype.setLabel = function (text) { - const label = this.parentNode?.querySelector('label'); - - label.innerText = text; - }; - - document.registerElement('emby-select', { - prototype: EmbySelectPrototype, - extends: 'select' + }).then(function (value) { + setValue(select, value); + triggerChange(select); }); +} + +function getLabel(select) { + let elem = select.previousSibling; + while (elem && elem.tagName !== 'LABEL') { + elem = elem.previousSibling; + } + return elem; +} + +function onFocus() { + const label = getLabel(this); + if (label) { + label.classList.add('selectLabelFocused'); + } +} + +function onBlur() { + const label = getLabel(this); + if (label) { + label.classList.remove('selectLabelFocused'); + } +} + +function onMouseDown(e) { + // e.button=0 for primary (left) mouse button click + if (!e.button && !enableNativeMenu()) { + e.preventDefault(); + showActionSheet(this); + } +} + +function onKeyDown(e) { + switch (e.keyCode) { + case 13: + if (!enableNativeMenu()) { + e.preventDefault(); + showActionSheet(this); + } + return; + case 37: + case 38: + case 39: + case 40: + if (layoutManager.tv) { + e.preventDefault(); + } + return; + default: + break; + } +} + +let inputId = 0; + +EmbySelectPrototype.createdCallback = function () { + if (!this.id) { + this.id = 'embyselect' + inputId; + inputId++; + } + + this.classList.add('emby-select-withcolor'); + + if (layoutManager.tv) { + this.classList.add('emby-select-focusscale'); + } + + this.addEventListener('mousedown', onMouseDown); + this.addEventListener('keydown', onKeyDown); + + this.addEventListener('focus', onFocus); + this.addEventListener('blur', onBlur); +}; + +EmbySelectPrototype.attachedCallback = function () { + if (this.classList.contains('emby-select')) { + return; + } + + this.classList.add('emby-select'); + + const label = this.ownerDocument.createElement('label'); + label.innerText = this.getAttribute('label') || ''; + label.classList.add('selectLabel'); + label.htmlFor = this.id; + this.parentNode?.insertBefore(label, this); + + if (this.classList.contains('emby-select-withcolor')) { + this.parentNode?.insertAdjacentHTML('beforeend', '
0
'); + } +}; + +EmbySelectPrototype.setLabel = function (text) { + const label = this.parentNode?.querySelector('label'); + + label.innerText = text; +}; + +document.registerElement('emby-select', { + prototype: EmbySelectPrototype, + extends: 'select' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index 7ce7b02ab6..bdba41638c 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -7,567 +7,564 @@ import 'webcomponents.js/webcomponents-lite'; import '../emby-input/emby-input'; import globalize from '../../scripts/globalize'; -/* eslint-disable indent */ +const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); - const EmbySliderPrototype = Object.create(HTMLInputElement.prototype); +let supportsValueSetOverride = false; - let supportsValueSetOverride = false; - - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - supportsValueSetOverride = true; - } +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + supportsValueSetOverride = true; } +} - /** +/** * Returns slider fraction corresponding to client position. * * @param {Object} range slider itself * @param {number} clientX client X-coordinate * @return {number} slider fraction */ - function mapClientToFraction(range, clientX) { - const rect = range.sliderBubbleTrack.getBoundingClientRect(); +function mapClientToFraction(range, clientX) { + const rect = range.sliderBubbleTrack.getBoundingClientRect(); - let fraction = (clientX - rect.left) / rect.width; - if (globalize.getIsElementRTL(range)) - fraction = (rect.right - clientX) / rect.width; + let fraction = (clientX - rect.left) / rect.width; + if (globalize.getIsElementRTL(range)) + fraction = (rect.right - clientX) / rect.width; - // Snap to step - const valueRange = range.max - range.min; - if (range.step !== 'any' && valueRange !== 0) { - const step = (range.step || 1) / valueRange; - fraction = Math.round(fraction / step) * step; - } - - return Math.min(Math.max(fraction, 0), 1); + // Snap to step + const valueRange = range.max - range.min; + if (range.step !== 'any' && valueRange !== 0) { + const step = (range.step || 1) / valueRange; + fraction = Math.round(fraction / step) * step; } - /** + return Math.min(Math.max(fraction, 0), 1); +} + +/** * Returns slider value corresponding to slider fraction. * * @param {Object} range slider itself * @param {number} fraction slider fraction * @return {number} slider value */ - function mapFractionToValue(range, fraction) { - let value = (range.max - range.min) * fraction; +function mapFractionToValue(range, fraction) { + let value = (range.max - range.min) * fraction; - // Snap to step - if (range.step !== 'any') { - const step = range.step || 1; - value = Math.round(value / step) * step; - } - - value += parseFloat(range.min); - - return Math.min(Math.max(value, range.min), range.max); + // Snap to step + if (range.step !== 'any') { + const step = range.step || 1; + value = Math.round(value / step) * step; } - /** + value += parseFloat(range.min); + + return Math.min(Math.max(value, range.min), range.max); +} + +/** * Returns slider fraction corresponding to slider value. * * @param {Object} range slider itself * @param {number} value slider value (snapped to step) * @return {number} slider fraction */ - function mapValueToFraction(range, value) { - const valueRange = range.max - range.min; - const fraction = valueRange !== 0 ? (value - range.min) / valueRange : 0; - return Math.min(Math.max(fraction, 0), 1); - } +function mapValueToFraction(range, value) { + const valueRange = range.max - range.min; + const fraction = valueRange !== 0 ? (value - range.min) / valueRange : 0; + return Math.min(Math.max(fraction, 0), 1); +} - /** +/** * Updates progress bar. * * @param {boolean} [isValueSet] update by 'valueset' event or by timer */ - function updateValues(isValueSet) { - // Do not update values by 'valueset' in case of soft-implemented dragging - if (!!isValueSet && (!!this.keyboardDragging || !!this.touched)) { +function updateValues(isValueSet) { + // Do not update values by 'valueset' in case of soft-implemented dragging + if (!!isValueSet && (!!this.keyboardDragging || !!this.touched)) { + return; + } + + const range = this; + const value = range.value; + + // put this on a callback. Doing it within the event sometimes causes the slider to get hung up and not respond + // Keep only one per slider frame request + cancelAnimationFrame(range.updateValuesFrame); + range.updateValuesFrame = requestAnimationFrame(function () { + const backgroundLower = range.backgroundLower; + + if (backgroundLower) { + let fraction = (value - range.min) / (range.max - range.min); + + fraction *= 100; + backgroundLower.style.width = fraction + '%'; + } + + if (range.markerContainerElement) { + if (!range.triedAddingMarkers) { + addMarkers(range); + } + updateMarkers(range, value); + } + }); +} + +function updateBubble(range, value, bubble) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const bubbleRect = bubble.getBoundingClientRect(); + + let bubblePos = bubbleTrackRect.width * value / 100; + if (globalize.getIsElementRTL(range)) { + bubblePos = bubbleTrackRect.width - bubblePos; + } + bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2); + + bubble.style.left = bubblePos + 'px'; + + if (range.getBubbleHtml) { + value = range.getBubbleHtml(value); + } else { + if (range.getBubbleText) { + value = range.getBubbleText(value); + } else { + value = mapFractionToValue(range, value / 100).toLocaleString(); + } + value = '

' + value + '

'; + } + + bubble.innerHTML = value; + }); +} + +function setMarker(range, valueMarker, marker, valueProgress) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const markerRect = marker.getBoundingClientRect(); + + if (!bubbleTrackRect.width || !markerRect.width) { + // width is not set, most probably because the OSD is currently hidden return; } - const range = this; - const value = range.value; + let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; + markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); - // put this on a callback. Doing it within the event sometimes causes the slider to get hung up and not respond - // Keep only one per slider frame request - cancelAnimationFrame(range.updateValuesFrame); - range.updateValuesFrame = requestAnimationFrame(function () { - const backgroundLower = range.backgroundLower; + marker.style.left = markerPos + 'px'; - if (backgroundLower) { - let fraction = (value - range.min) / (range.max - range.min); + if (valueProgress >= valueMarker) { + marker.classList.remove('unwatched'); + marker.classList.add('watched'); + } else { + marker.classList.add('unwatched'); + marker.classList.remove('watched'); + } + }); +} - fraction *= 100; - backgroundLower.style.width = fraction + '%'; - } - - if (range.markerContainerElement) { - if (!range.triedAddingMarkers) { - addMarkers(range); - } - updateMarkers(range, value); - } - }); - } - - function updateBubble(range, value, bubble) { - requestAnimationFrame(function () { - const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); - const bubbleRect = bubble.getBoundingClientRect(); - - let bubblePos = bubbleTrackRect.width * value / 100; - if (globalize.getIsElementRTL(range)) { - bubblePos = bubbleTrackRect.width - bubblePos; - } - bubblePos = Math.min(Math.max(bubblePos, bubbleRect.width / 2), bubbleTrackRect.width - bubbleRect.width / 2); - - bubble.style.left = bubblePos + 'px'; - - if (range.getBubbleHtml) { - value = range.getBubbleHtml(value); - } else { - if (range.getBubbleText) { - value = range.getBubbleText(value); - } else { - value = mapFractionToValue(range, value / 100).toLocaleString(); - } - value = '

' + value + '

'; - } - - bubble.innerHTML = value; - }); - } - - function setMarker(range, valueMarker, marker, valueProgress) { - requestAnimationFrame(function () { - const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); - const markerRect = marker.getBoundingClientRect(); - - if (!bubbleTrackRect.width || !markerRect.width) { - // width is not set, most probably because the OSD is currently hidden - return; - } - - let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; - markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); - - marker.style.left = markerPos + 'px'; - - if (valueProgress >= valueMarker) { - marker.classList.remove('unwatched'); - marker.classList.add('watched'); - } else { - marker.classList.add('unwatched'); - marker.classList.remove('watched'); - } - }); - } - - function updateMarkers(range, currentValue) { - if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { - for (let i = 0, length = range.markerElements.length; i < length; i++) { - if (range.markerInfo.length > i) { - setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); - } +function updateMarkers(range, currentValue) { + if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { + for (let i = 0, length = range.markerElements.length; i < length; i++) { + if (range.markerInfo.length > i) { + setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); } } } +} - function addMarkers(range) { - range.markerInfo = []; - if (range.getMarkerInfo) { - range.markerInfo = range.getMarkerInfo(); - } - - function getMarkerHtml(markerInfo) { - let markerTypeSpecificClasses = ''; - - if (markerInfo.className === 'chapterMarker') { - markerTypeSpecificClasses = markerInfo.className; - - if (typeof markerInfo.name === 'string' && markerInfo.name.length) { - // limit the class length in case the name contains half a novel - markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; - } - } - - return ``; - } - - range.markerInfo.forEach(info => { - range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); - }); - - range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); - range.triedAddingMarkers = true; +function addMarkers(range) { + range.markerInfo = []; + if (range.getMarkerInfo) { + range.markerInfo = range.getMarkerInfo(); } - EmbySliderPrototype.attachedCallback = function () { - if (this.getAttribute('data-embyslider') === 'true') { - return; - } + function getMarkerHtml(markerInfo) { + let markerTypeSpecificClasses = ''; - this.setAttribute('data-embyslider', 'true'); + if (markerInfo.className === 'chapterMarker') { + markerTypeSpecificClasses = markerInfo.className; - this.classList.add('mdl-slider'); - this.classList.add('mdl-js-slider'); - - if (browser.edge) { - this.classList.add('slider-browser-edge'); - } - if (!layoutManager.mobile) { - this.classList.add('mdl-slider-hoverthumb'); - } - if (layoutManager.tv) { - this.classList.add('show-focus'); - } - - const topContainer = dom.parentWithClass(this, 'sliderContainer-settings'); - - if (topContainer && this.getAttribute('label')) { - const label = this.ownerDocument.createElement('label'); - label.innerText = this.getAttribute('label'); - label.classList.add('sliderLabel'); - label.htmlFor = this.id; - topContainer.insertBefore(label, topContainer.firstChild); - } - - const containerElement = this.parentNode; - containerElement.classList.add('mdl-slider-container'); - - let htmlToInsert = ''; - - htmlToInsert += '
'; - htmlToInsert += '
'; - htmlToInsert += '
'; - - // the more of these, the more ranges we can display - htmlToInsert += '
'; - - htmlToInsert += '
'; - - htmlToInsert += '
'; - htmlToInsert += '
'; - htmlToInsert += '
'; - - htmlToInsert += '
'; - - containerElement.insertAdjacentHTML('beforeend', htmlToInsert); - - this.sliderBubbleTrack = containerElement.querySelector('.sliderBubbleTrack'); - this.backgroundLower = containerElement.querySelector('.mdl-slider-background-lower'); - this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); - const sliderBubble = containerElement.querySelector('.sliderBubble'); - - let hasHideBubbleClass = sliderBubble.classList.contains('hide'); - - this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); - - dom.addEventListener(this, 'input', function () { - this.dragging = true; - - if (this.dataset.sliderKeepProgress !== 'true') { - updateValues.call(this); + if (typeof markerInfo.name === 'string' && markerInfo.name.length) { + // limit the class length in case the name contains half a novel + markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; } + } + + return ``; + } + + range.markerInfo.forEach(info => { + range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); + }); + + range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); + range.triedAddingMarkers = true; +} + +EmbySliderPrototype.attachedCallback = function () { + if (this.getAttribute('data-embyslider') === 'true') { + return; + } + + this.setAttribute('data-embyslider', 'true'); + + this.classList.add('mdl-slider'); + this.classList.add('mdl-js-slider'); + + if (browser.edge) { + this.classList.add('slider-browser-edge'); + } + if (!layoutManager.mobile) { + this.classList.add('mdl-slider-hoverthumb'); + } + if (layoutManager.tv) { + this.classList.add('show-focus'); + } + + const topContainer = dom.parentWithClass(this, 'sliderContainer-settings'); + + if (topContainer && this.getAttribute('label')) { + const label = this.ownerDocument.createElement('label'); + label.innerText = this.getAttribute('label'); + label.classList.add('sliderLabel'); + label.htmlFor = this.id; + topContainer.insertBefore(label, topContainer.firstChild); + } + + const containerElement = this.parentNode; + containerElement.classList.add('mdl-slider-container'); + + let htmlToInsert = ''; + + htmlToInsert += '
'; + htmlToInsert += '
'; + htmlToInsert += '
'; + + // the more of these, the more ranges we can display + htmlToInsert += '
'; + + htmlToInsert += '
'; + + htmlToInsert += '
'; + htmlToInsert += '
'; + htmlToInsert += '
'; + + htmlToInsert += '
'; + + containerElement.insertAdjacentHTML('beforeend', htmlToInsert); + + this.sliderBubbleTrack = containerElement.querySelector('.sliderBubbleTrack'); + this.backgroundLower = containerElement.querySelector('.mdl-slider-background-lower'); + this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); + const sliderBubble = containerElement.querySelector('.sliderBubble'); + + let hasHideBubbleClass = sliderBubble.classList.contains('hide'); + + this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); + + dom.addEventListener(this, 'input', function () { + this.dragging = true; + + if (this.dataset.sliderKeepProgress !== 'true') { + updateValues.call(this); + } + + const bubbleValue = mapValueToFraction(this, this.value) * 100; + updateBubble(this, bubbleValue, sliderBubble); + + if (hasHideBubbleClass) { + sliderBubble.classList.remove('hide'); + hasHideBubbleClass = false; + } + }, { + passive: true + }); + + dom.addEventListener(this, 'change', function () { + this.dragging = false; + + if (this.dataset.sliderKeepProgress === 'true') { + updateValues.call(this); + } + + sliderBubble.classList.add('hide'); + hasHideBubbleClass = true; + }, { + passive: true + }); + + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { + if (!this.dragging) { + const bubbleValue = mapClientToFraction(this, e.clientX) * 100; - const bubbleValue = mapValueToFraction(this, this.value) * 100; updateBubble(this, bubbleValue, sliderBubble); if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); hasHideBubbleClass = false; } - }, { - passive: true - }); + } + }, { + passive: true + }); - dom.addEventListener(this, 'change', function () { - this.dragging = false; + /* eslint-disable-next-line compat/compat */ + dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { + sliderBubble.classList.add('hide'); + hasHideBubbleClass = true; + }, { + passive: true + }); - if (this.dataset.sliderKeepProgress === 'true') { - updateValues.call(this); + // HACK: iPhone/iPad do not change input by touch + if (browser.iOS) { + dom.addEventListener(this, 'touchstart', function (e) { + if (e.targetTouches.length !== 1) { + return; } - sliderBubble.classList.add('hide'); - hasHideBubbleClass = true; + this.touched = true; + + const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); + this.value = mapFractionToValue(this, fraction); + + this.dispatchEvent(new Event('input', { + bubbles: true, + cancelable: false + })); + + // Prevent 'pointermove' and 'click' after 'touch*' + // FIXME: Still have some 'pointermove' and 'click' that bypass 'touchstart' + e.preventDefault(); }, { - passive: true + capture: true }); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(this, (window.PointerEvent ? 'pointermove' : 'mousemove'), function (e) { - if (!this.dragging) { - const bubbleValue = mapClientToFraction(this, e.clientX) * 100; - - updateBubble(this, bubbleValue, sliderBubble); - - if (hasHideBubbleClass) { - sliderBubble.classList.remove('hide'); - hasHideBubbleClass = false; - } + dom.addEventListener(this, 'touchmove', function (e) { + if (!this.touched || e.targetTouches.length !== 1) { + return; } + + const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); + this.value = mapFractionToValue(this, fraction); + + this.dispatchEvent(new Event('input', { + bubbles: true, + cancelable: false + })); }, { passive: true }); - /* eslint-disable-next-line compat/compat */ - dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { - sliderBubble.classList.add('hide'); - hasHideBubbleClass = true; - }, { - passive: true - }); + dom.addEventListener(this, 'touchend', function () { + const range = this; - // HACK: iPhone/iPad do not change input by touch - if (browser.iOS) { - dom.addEventListener(this, 'touchstart', function (e) { - if (e.targetTouches.length !== 1) { - return; - } + setTimeout(function () { + range.touched = false; - this.touched = true; - - const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); - this.value = mapFractionToValue(this, fraction); - - this.dispatchEvent(new Event('input', { + range.dispatchEvent(new Event('change', { bubbles: true, cancelable: false })); + }, 0); + }, { + passive: true + }); + } - // Prevent 'pointermove' and 'click' after 'touch*' - // FIXME: Still have some 'pointermove' and 'click' that bypass 'touchstart' - e.preventDefault(); - }, { - capture: true - }); + if (supportsValueSetOverride) { + this.addEventListener('valueset', updateValues.bind(this, true)); + } else { + startInterval(this); + } +}; - dom.addEventListener(this, 'touchmove', function (e) { - if (!this.touched || e.targetTouches.length !== 1) { - return; - } - - const fraction = mapClientToFraction(this, e.targetTouches[0].clientX); - this.value = mapFractionToValue(this, fraction); - - this.dispatchEvent(new Event('input', { - bubbles: true, - cancelable: false - })); - }, { - passive: true - }); - - dom.addEventListener(this, 'touchend', function () { - const range = this; - - setTimeout(function () { - range.touched = false; - - range.dispatchEvent(new Event('change', { - bubbles: true, - cancelable: false - })); - }, 0); - }, { - passive: true - }); - } - - if (supportsValueSetOverride) { - this.addEventListener('valueset', updateValues.bind(this, true)); - } else { - startInterval(this); - } - }; - - /** +/** * Keyboard dragging timeout. * After this delay "change" event will be fired. */ - const KeyboardDraggingTimeout = 1000; +const KeyboardDraggingTimeout = 1000; - /** +/** * Keyboard dragging timer. */ - let keyboardDraggingTimer; +let keyboardDraggingTimer; - /** +/** * Start keyboard dragging. * * @param {Object} elem slider itself */ - function startKeyboardDragging(elem) { - elem.keyboardDragging = true; +function startKeyboardDragging(elem) { + elem.keyboardDragging = true; - clearTimeout(keyboardDraggingTimer); - keyboardDraggingTimer = setTimeout(function () { - finishKeyboardDragging(elem); - }, KeyboardDraggingTimeout); - } + clearTimeout(keyboardDraggingTimer); + keyboardDraggingTimer = setTimeout(function () { + finishKeyboardDragging(elem); + }, KeyboardDraggingTimeout); +} - /** +/** * Finish keyboard dragging. * * @param {Object} elem slider itself */ - function finishKeyboardDragging(elem) { - clearTimeout(keyboardDraggingTimer); - keyboardDraggingTimer = undefined; +function finishKeyboardDragging(elem) { + clearTimeout(keyboardDraggingTimer); + keyboardDraggingTimer = undefined; - elem.keyboardDragging = false; + elem.keyboardDragging = false; - const event = new Event('change', { - bubbles: true, - cancelable: false - }); - elem.dispatchEvent(event); - } + const event = new Event('change', { + bubbles: true, + cancelable: false + }); + elem.dispatchEvent(event); +} - /** +/** * Do step by delta. * * @param {Object} elem slider itself * @param {number} delta step amount */ - function stepKeyboard(elem, delta) { - startKeyboardDragging(elem); +function stepKeyboard(elem, delta) { + startKeyboardDragging(elem); - elem.value = Math.max(elem.min, Math.min(elem.max, parseFloat(elem.value) + delta)); + elem.value = Math.max(elem.min, Math.min(elem.max, parseFloat(elem.value) + delta)); - const event = new Event('input', { - bubbles: true, - cancelable: false - }); - elem.dispatchEvent(event); - } + const event = new Event('input', { + bubbles: true, + cancelable: false + }); + elem.dispatchEvent(event); +} - /** +/** * Handle KeyDown event */ - function onKeyDown(e) { - switch (keyboardnavigation.getKeyName(e)) { - case 'ArrowLeft': - case 'Left': - stepKeyboard(this, -this.keyboardStepDown || -1); - e.preventDefault(); - e.stopPropagation(); - break; - case 'ArrowRight': - case 'Right': - stepKeyboard(this, this.keyboardStepUp || 1); - e.preventDefault(); - e.stopPropagation(); - break; - } +function onKeyDown(e) { + switch (keyboardnavigation.getKeyName(e)) { + case 'ArrowLeft': + case 'Left': + stepKeyboard(this, -this.keyboardStepDown || -1); + e.preventDefault(); + e.stopPropagation(); + break; + case 'ArrowRight': + case 'Right': + stepKeyboard(this, this.keyboardStepUp || 1); + e.preventDefault(); + e.stopPropagation(); + break; } +} - /** +/** * Enable keyboard dragging. */ - EmbySliderPrototype.enableKeyboardDragging = function () { - if (!this.keyboardDraggingEnabled) { - this.addEventListener('keydown', onKeyDown); - this.keyboardDraggingEnabled = true; - } - }; +EmbySliderPrototype.enableKeyboardDragging = function () { + if (!this.keyboardDraggingEnabled) { + this.addEventListener('keydown', onKeyDown); + this.keyboardDraggingEnabled = true; + } +}; - /** +/** * Set steps for keyboard input. * * @param {number} stepDown step to reduce * @param {number} stepUp step to increase */ - EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { - this.keyboardStepDown = stepDown || stepUp || 1; - this.keyboardStepUp = stepUp || stepDown || 1; - }; +EmbySliderPrototype.setKeyboardSteps = function (stepDown, stepUp) { + this.keyboardStepDown = stepDown || stepUp || 1; + this.keyboardStepUp = stepUp || stepDown || 1; +}; - function setRange(elem, startPercent, endPercent) { - const style = elem.style; - if (globalize.getIsRTL()) - style.right = Math.max(startPercent, 0) + '%'; - else - style.left = Math.max(startPercent, 0) + '%'; +function setRange(elem, startPercent, endPercent) { + const style = elem.style; + if (globalize.getIsRTL()) + style.right = Math.max(startPercent, 0) + '%'; + else + style.left = Math.max(startPercent, 0) + '%'; - const widthPercent = endPercent - startPercent; - style.width = Math.max(Math.min(widthPercent, 100), 0) + '%'; + const widthPercent = endPercent - startPercent; + style.width = Math.max(Math.min(widthPercent, 100), 0) + '%'; +} + +function mapRangesFromRuntimeToPercent(ranges, runtime) { + if (!runtime) { + return []; } - function mapRangesFromRuntimeToPercent(ranges, runtime) { - if (!runtime) { - return []; - } - - return ranges.map(function (r) { - return { - start: (r.start / runtime) * 100, - end: (r.end / runtime) * 100 - }; - }); - } - - EmbySliderPrototype.setBufferedRanges = function (ranges, runtime, position) { - const elem = this.backgroundUpper; - if (!elem) { - return; - } - - if (runtime != null) { - ranges = mapRangesFromRuntimeToPercent(ranges, runtime); - - position = (position / runtime) * 100; - } - - for (const range of ranges) { - if (position != null && position >= range.end) { - continue; - } - - setRange(elem, range.start, range.end); - return; - } - - setRange(elem, 0, 0); - }; - - EmbySliderPrototype.setIsClear = function (isClear) { - const backgroundLower = this.backgroundLower; - if (backgroundLower) { - if (isClear) { - backgroundLower.classList.add('mdl-slider-background-lower-clear'); - } else { - backgroundLower.classList.remove('mdl-slider-background-lower-clear'); - } - } - }; - - function startInterval(range) { - const interval = range.interval; - if (interval) { - clearInterval(interval); - } - range.interval = setInterval(updateValues.bind(range, true), 100); - } - - EmbySliderPrototype.detachedCallback = function () { - const interval = this.interval; - if (interval) { - clearInterval(interval); - } - this.interval = null; - this.backgroundUpper = null; - this.backgroundLower = null; - }; - - document.registerElement('emby-slider', { - prototype: EmbySliderPrototype, - extends: 'input' + return ranges.map(function (r) { + return { + start: (r.start / runtime) * 100, + end: (r.end / runtime) * 100 + }; }); +} + +EmbySliderPrototype.setBufferedRanges = function (ranges, runtime, position) { + const elem = this.backgroundUpper; + if (!elem) { + return; + } + + if (runtime != null) { + ranges = mapRangesFromRuntimeToPercent(ranges, runtime); + + position = (position / runtime) * 100; + } + + for (const range of ranges) { + if (position != null && position >= range.end) { + continue; + } + + setRange(elem, range.start, range.end); + return; + } + + setRange(elem, 0, 0); +}; + +EmbySliderPrototype.setIsClear = function (isClear) { + const backgroundLower = this.backgroundLower; + if (backgroundLower) { + if (isClear) { + backgroundLower.classList.add('mdl-slider-background-lower-clear'); + } else { + backgroundLower.classList.remove('mdl-slider-background-lower-clear'); + } + } +}; + +function startInterval(range) { + const interval = range.interval; + if (interval) { + clearInterval(interval); + } + range.interval = setInterval(updateValues.bind(range, true), 100); +} + +EmbySliderPrototype.detachedCallback = function () { + const interval = this.interval; + if (interval) { + clearInterval(interval); + } + this.interval = null; + this.backgroundUpper = null; + this.backgroundLower = null; +}; + +document.registerElement('emby-slider', { + prototype: EmbySliderPrototype, + extends: 'input' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index 7c07796f74..22d12e75af 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -7,334 +7,332 @@ import layoutManager from '../../components/layoutManager'; import './emby-tabs.scss'; import '../../styles/scrollstyles.scss'; -/* eslint-disable indent */ - const EmbyTabs = Object.create(HTMLDivElement.prototype); - const buttonClass = 'emby-tab-button'; - const activeButtonClass = buttonClass + '-active'; +const EmbyTabs = Object.create(HTMLDivElement.prototype); +const buttonClass = 'emby-tab-button'; +const activeButtonClass = buttonClass + '-active'; - function setActiveTabButton(newButton) { - newButton.classList.add(activeButtonClass); +function setActiveTabButton(newButton) { + newButton.classList.add(activeButtonClass); +} + +function getTabPanel() { + return null; +} + +function removeActivePanelClass() { + const tabPanel = getTabPanel(); + if (tabPanel) { + tabPanel.classList.remove('is-active'); + } +} + +function fadeInRight(elem) { + const pct = browser.mobile ? '4%' : '0.5%'; + + const keyframes = [ + { opacity: '0', transform: 'translate3d(' + pct + ', 0, 0)', offset: 0 }, + { opacity: '1', transform: 'none', offset: 1 }]; + + elem.animate(keyframes, { + duration: 160, + iterations: 1, + easing: 'ease-out' + }); +} + +function triggerBeforeTabChange(tabs, index, previousIndex) { + tabs.dispatchEvent(new CustomEvent('beforetabchange', { + detail: { + selectedTabIndex: index, + previousIndex: previousIndex + } + })); + if (previousIndex != null && previousIndex !== index) { + removeActivePanelClass(); } - function getTabPanel() { - return null; + const newPanel = getTabPanel(); + + if (newPanel) { + // animate new panel ? + if (newPanel.animate) { + fadeInRight(newPanel); + } + + newPanel.classList.add('is-active'); } +} - function removeActivePanelClass() { - const tabPanel = getTabPanel(); - if (tabPanel) { - tabPanel.classList.remove('is-active'); - } - } +function onClick(e) { + const tabs = this; - function fadeInRight(elem) { - const pct = browser.mobile ? '4%' : '0.5%'; + const current = tabs.querySelector('.' + activeButtonClass); + const tabButton = dom.parentWithClass(e.target, buttonClass); - const keyframes = [ - { opacity: '0', transform: 'translate3d(' + pct + ', 0, 0)', offset: 0 }, - { opacity: '1', transform: 'none', offset: 1 }]; - - elem.animate(keyframes, { - duration: 160, - iterations: 1, - easing: 'ease-out' - }); - } - - function triggerBeforeTabChange(tabs, index, previousIndex) { - tabs.dispatchEvent(new CustomEvent('beforetabchange', { - detail: { - selectedTabIndex: index, - previousIndex: previousIndex - } - })); - if (previousIndex != null && previousIndex !== index) { - removeActivePanelClass(); + if (tabButton && tabButton !== current) { + if (current) { + current.classList.remove(activeButtonClass); } - const newPanel = getTabPanel(); + const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null; - if (newPanel) { - // animate new panel ? - if (newPanel.animate) { - fadeInRight(newPanel); - } + setActiveTabButton(tabButton); - newPanel.classList.add('is-active'); - } - } + const index = parseInt(tabButton.getAttribute('data-index'), 10); - function onClick(e) { - const tabs = this; + triggerBeforeTabChange(tabs, index, previousIndex); - const current = tabs.querySelector('.' + activeButtonClass); - const tabButton = dom.parentWithClass(e.target, buttonClass); - - if (tabButton && tabButton !== current) { - if (current) { - current.classList.remove(activeButtonClass); - } - - const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null; - - setActiveTabButton(tabButton); - - const index = parseInt(tabButton.getAttribute('data-index'), 10); - - triggerBeforeTabChange(tabs, index, previousIndex); - - // If toCenter is called syncronously within the click event, it sometimes ends up canceling it - setTimeout(function () { - tabs.selectedTabIndex = index; - - tabs.dispatchEvent(new CustomEvent('tabchange', { - detail: { - selectedTabIndex: index, - previousIndex: previousIndex - } - })); - }, 120); - - if (tabs.scroller) { - tabs.scroller.toCenter(tabButton, false); - } - } - } - - function onFocusIn(e) { - const tabs = this; - const tabButton = dom.parentWithClass(e.target, buttonClass); - if (tabButton && tabs.scroller) { - tabs.scroller.toCenter(tabButton, false); - } - } - - function onFocusOut(e) { - const parentContainer = e.target.parentNode; - const previousFocus = parentContainer.querySelector('.lastFocused'); - if (previousFocus) { - previousFocus.classList.remove('lastFocused'); - } - e.target.classList.add('lastFocused'); - } - - function initScroller(tabs) { - if (tabs.scroller) { - return; - } - - const contentScrollSlider = tabs.querySelector('.emby-tabs-slider'); - if (contentScrollSlider) { - tabs.scroller = new scroller(tabs, { - horizontal: 1, - itemNav: 0, - mouseDragging: 1, - touchDragging: 1, - slidee: contentScrollSlider, - smart: true, - releaseSwing: true, - scrollBy: 200, - speed: 120, - elasticBounds: 1, - dragHandle: 1, - dynamicHandle: 1, - clickBar: 1, - hiddenScroll: true, - - // In safari the transform is causing the headers to occasionally disappear or flicker - requireAnimation: !browser.safari, - allowNativeSmoothScroll: true - }); - tabs.scroller.init(); - } else { - tabs.classList.add('scrollX'); - tabs.classList.add('hiddenScrollX'); - tabs.classList.add('smoothScrollX'); - } - } - - EmbyTabs.createdCallback = function () { - if (this.classList.contains('emby-tabs')) { - return; - } - this.classList.add('emby-tabs'); - this.classList.add('focusable'); - - dom.addEventListener(this, 'click', onClick, { - passive: true - }); - - if (layoutManager.tv) { - dom.addEventListener(this, 'focusin', onFocusIn, { passive: true }); - } - - dom.addEventListener(this, 'focusout', onFocusOut); - }; - - EmbyTabs.focus = function () { - const selectedTab = this.querySelector('.' + activeButtonClass); - const lastFocused = this.querySelector('.lastFocused'); - - if (lastFocused) { - focusManager.focus(lastFocused); - } else if (selectedTab) { - focusManager.focus(selectedTab); - } else { - focusManager.autoFocus(this); - } - }; - - EmbyTabs.refresh = function () { - if (this.scroller) { - this.scroller.reload(); - } - }; - - EmbyTabs.attachedCallback = function () { - initScroller(this); - - const current = this.querySelector('.' + activeButtonClass); - const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10); - - if (currentIndex !== -1) { - this.selectedTabIndex = currentIndex; - - const tabButtons = this.querySelectorAll('.' + buttonClass); - - const newTabButton = tabButtons[currentIndex]; - - if (newTabButton) { - setActiveTabButton(newTabButton); - } - } - - if (!this.readyFired) { - this.readyFired = true; - this.dispatchEvent(new CustomEvent('ready', {})); - } - }; - - EmbyTabs.detachedCallback = function () { - if (this.scroller) { - this.scroller.destroy(); - this.scroller = null; - } - - dom.removeEventListener(this, 'click', onClick, { - passive: true - }); - - if (layoutManager.tv) { - dom.removeEventListener(this, 'focusin', onFocusIn, { passive: true }); - } - }; - - function getSelectedTabButton(elem) { - return elem.querySelector('.' + activeButtonClass); - } - - EmbyTabs.selectedIndex = function (selected, triggerEvent) { - const tabs = this; - - if (selected == null) { - return tabs.selectedTabIndex || 0; - } - - const current = tabs.selectedIndex(); - - tabs.selectedTabIndex = selected; - - const tabButtons = tabs.querySelectorAll('.' + buttonClass); - - if (current === selected || triggerEvent === false) { - triggerBeforeTabChange(tabs, selected, current); + // If toCenter is called syncronously within the click event, it sometimes ends up canceling it + setTimeout(function () { + tabs.selectedTabIndex = index; tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { - selectedTabIndex: selected + selectedTabIndex: index, + previousIndex: previousIndex } })); + }, 120); - const currentTabButton = tabButtons[current]; - setActiveTabButton(tabButtons[selected]); - - if (current !== selected && currentTabButton) { - currentTabButton.classList.remove(activeButtonClass); - } - } else { - onClick.call(tabs, { - target: tabButtons[selected] - }); + if (tabs.scroller) { + tabs.scroller.toCenter(tabButton, false); } - }; + } +} - function getSibling(elem, method) { - let sibling = elem[method]; +function onFocusIn(e) { + const tabs = this; + const tabButton = dom.parentWithClass(e.target, buttonClass); + if (tabButton && tabs.scroller) { + tabs.scroller.toCenter(tabButton, false); + } +} - while (sibling) { - if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) { - return sibling; - } +function onFocusOut(e) { + const parentContainer = e.target.parentNode; + const previousFocus = parentContainer.querySelector('.lastFocused'); + if (previousFocus) { + previousFocus.classList.remove('lastFocused'); + } + e.target.classList.add('lastFocused'); +} - sibling = sibling[method]; - } - - return null; +function initScroller(tabs) { + if (tabs.scroller) { + return; } - EmbyTabs.selectNext = function () { - const current = getSelectedTabButton(this); + const contentScrollSlider = tabs.querySelector('.emby-tabs-slider'); + if (contentScrollSlider) { + tabs.scroller = new scroller(tabs, { + horizontal: 1, + itemNav: 0, + mouseDragging: 1, + touchDragging: 1, + slidee: contentScrollSlider, + smart: true, + releaseSwing: true, + scrollBy: 200, + speed: 120, + elasticBounds: 1, + dragHandle: 1, + dynamicHandle: 1, + clickBar: 1, + hiddenScroll: true, - const sibling = getSibling(current, 'nextSibling'); + // In safari the transform is causing the headers to occasionally disappear or flicker + requireAnimation: !browser.safari, + allowNativeSmoothScroll: true + }); + tabs.scroller.init(); + } else { + tabs.classList.add('scrollX'); + tabs.classList.add('hiddenScrollX'); + tabs.classList.add('smoothScrollX'); + } +} - if (sibling) { - onClick.call(this, { - target: sibling - }); +EmbyTabs.createdCallback = function () { + if (this.classList.contains('emby-tabs')) { + return; + } + this.classList.add('emby-tabs'); + this.classList.add('focusable'); + + dom.addEventListener(this, 'click', onClick, { + passive: true + }); + + if (layoutManager.tv) { + dom.addEventListener(this, 'focusin', onFocusIn, { passive: true }); + } + + dom.addEventListener(this, 'focusout', onFocusOut); +}; + +EmbyTabs.focus = function () { + const selectedTab = this.querySelector('.' + activeButtonClass); + const lastFocused = this.querySelector('.lastFocused'); + + if (lastFocused) { + focusManager.focus(lastFocused); + } else if (selectedTab) { + focusManager.focus(selectedTab); + } else { + focusManager.autoFocus(this); + } +}; + +EmbyTabs.refresh = function () { + if (this.scroller) { + this.scroller.reload(); + } +}; + +EmbyTabs.attachedCallback = function () { + initScroller(this); + + const current = this.querySelector('.' + activeButtonClass); + const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10); + + if (currentIndex !== -1) { + this.selectedTabIndex = currentIndex; + + const tabButtons = this.querySelectorAll('.' + buttonClass); + + const newTabButton = tabButtons[currentIndex]; + + if (newTabButton) { + setActiveTabButton(newTabButton); } - }; + } - EmbyTabs.selectPrevious = function () { - const current = getSelectedTabButton(this); + if (!this.readyFired) { + this.readyFired = true; + this.dispatchEvent(new CustomEvent('ready', {})); + } +}; - const sibling = getSibling(current, 'previousSibling'); +EmbyTabs.detachedCallback = function () { + if (this.scroller) { + this.scroller.destroy(); + this.scroller = null; + } - if (sibling) { - onClick.call(this, { - target: sibling - }); - } - }; + dom.removeEventListener(this, 'click', onClick, { + passive: true + }); - EmbyTabs.triggerBeforeTabChange = function () { - const tabs = this; + if (layoutManager.tv) { + dom.removeEventListener(this, 'focusin', onFocusIn, { passive: true }); + } +}; - triggerBeforeTabChange(tabs, tabs.selectedIndex()); - }; +function getSelectedTabButton(elem) { + return elem.querySelector('.' + activeButtonClass); +} - EmbyTabs.triggerTabChange = function () { - const tabs = this; +EmbyTabs.selectedIndex = function (selected, triggerEvent) { + const tabs = this; + + if (selected == null) { + return tabs.selectedTabIndex || 0; + } + + const current = tabs.selectedIndex(); + + tabs.selectedTabIndex = selected; + + const tabButtons = tabs.querySelectorAll('.' + buttonClass); + + if (current === selected || triggerEvent === false) { + triggerBeforeTabChange(tabs, selected, current); tabs.dispatchEvent(new CustomEvent('tabchange', { detail: { - selectedTabIndex: tabs.selectedIndex() + selectedTabIndex: selected } })); - }; - EmbyTabs.setTabEnabled = function (index, enabled) { - const btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]'); + const currentTabButton = tabButtons[current]; + setActiveTabButton(tabButtons[selected]); - if (enabled) { - btn.classList.remove('hide'); - } else { - btn.classList.remove('add'); + if (current !== selected && currentTabButton) { + currentTabButton.classList.remove(activeButtonClass); } - }; + } else { + onClick.call(tabs, { + target: tabButtons[selected] + }); + } +}; - document.registerElement('emby-tabs', { - prototype: EmbyTabs, - extends: 'div' - }); +function getSibling(elem, method) { + let sibling = elem[method]; + + while (sibling) { + if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) { + return sibling; + } + + sibling = sibling[method]; + } + + return null; +} + +EmbyTabs.selectNext = function () { + const current = getSelectedTabButton(this); + + const sibling = getSibling(current, 'nextSibling'); + + if (sibling) { + onClick.call(this, { + target: sibling + }); + } +}; + +EmbyTabs.selectPrevious = function () { + const current = getSelectedTabButton(this); + + const sibling = getSibling(current, 'previousSibling'); + + if (sibling) { + onClick.call(this, { + target: sibling + }); + } +}; + +EmbyTabs.triggerBeforeTabChange = function () { + const tabs = this; + + triggerBeforeTabChange(tabs, tabs.selectedIndex()); +}; + +EmbyTabs.triggerTabChange = function () { + const tabs = this; + + tabs.dispatchEvent(new CustomEvent('tabchange', { + detail: { + selectedTabIndex: tabs.selectedIndex() + } + })); +}; + +EmbyTabs.setTabEnabled = function (index, enabled) { + const btn = this.querySelector('.emby-tab-button[data-index="' + index + '"]'); + + if (enabled) { + btn.classList.remove('hide'); + } else { + btn.classList.remove('add'); + } +}; + +document.registerElement('emby-tabs', { + prototype: EmbyTabs, + extends: 'div' +}); -/* eslint-enable indent */ diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index ed48f415ff..4acf38b594 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -19,119 +19,117 @@ function calculateOffset(textarea) { return offset; } -/* eslint-disable indent */ - function autoGrow(textarea, maxLines) { - const self = this; +function autoGrow(textarea, maxLines) { + const self = this; - if (maxLines === undefined) { - maxLines = 999; - } - - let offset; - function reset() { - textarea.rows = 1; - offset = calculateOffset(textarea); - self.rows = textarea.rows || 1; - self.lineHeight = (textarea.scrollHeight / self.rows) - (offset / self.rows); - self.maxAllowedHeight = (self.lineHeight * maxLines) - offset; - } - - function autogrowFn() { - if (!self.lineHeight || self.lineHeight <= 0) { - reset(); - } - if (self.lineHeight <= 0) { - textarea.style.overflowY = 'scroll'; - textarea.style.height = 'auto'; - textarea.rows = 3; - return; - } - let newHeight = 0; - - if ((textarea.scrollHeight - offset) > self.maxAllowedHeight) { - textarea.style.overflowY = 'scroll'; - newHeight = self.maxAllowedHeight; - } else { - textarea.style.overflowY = 'hidden'; - textarea.style.height = 'auto'; - newHeight = textarea.scrollHeight/* - offset*/; - } - textarea.style.height = newHeight + 'px'; - } - - // Call autogrowFn() when textarea's value is changed - textarea.addEventListener('input', autogrowFn); - textarea.addEventListener('focus', autogrowFn); - textarea.addEventListener('valueset', autogrowFn); - - autogrowFn(); + if (maxLines === undefined) { + maxLines = 999; } - const EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype); - - let elementId = 0; - - if (Object.getOwnPropertyDescriptor && Object.defineProperty) { - const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); - - // descriptor returning null in webos - if (descriptor && descriptor.configurable) { - const baseSetMethod = descriptor.set; - descriptor.set = function (value) { - baseSetMethod.call(this, value); - - this.dispatchEvent(new CustomEvent('valueset', { - bubbles: false, - cancelable: false - })); - }; - - Object.defineProperty(HTMLTextAreaElement.prototype, 'value', descriptor); - } + let offset; + function reset() { + textarea.rows = 1; + offset = calculateOffset(textarea); + self.rows = textarea.rows || 1; + self.lineHeight = (textarea.scrollHeight / self.rows) - (offset / self.rows); + self.maxAllowedHeight = (self.lineHeight * maxLines) - offset; } - EmbyTextAreaPrototype.createdCallback = function () { - if (!this.id) { - this.id = 'embytextarea' + elementId; - elementId++; + function autogrowFn() { + if (!self.lineHeight || self.lineHeight <= 0) { + reset(); } - }; - - EmbyTextAreaPrototype.attachedCallback = function () { - if (this.classList.contains('emby-textarea')) { + if (self.lineHeight <= 0) { + textarea.style.overflowY = 'scroll'; + textarea.style.height = 'auto'; + textarea.rows = 3; return; } + let newHeight = 0; - this.rows = 1; - this.classList.add('emby-textarea'); + if ((textarea.scrollHeight - offset) > self.maxAllowedHeight) { + textarea.style.overflowY = 'scroll'; + newHeight = self.maxAllowedHeight; + } else { + textarea.style.overflowY = 'hidden'; + textarea.style.height = 'auto'; + newHeight = textarea.scrollHeight/* - offset*/; + } + textarea.style.height = newHeight + 'px'; + } - const parentNode = this.parentNode; - const label = this.ownerDocument.createElement('label'); - label.innerText = this.getAttribute('label') || ''; - label.classList.add('textareaLabel'); + // Call autogrowFn() when textarea's value is changed + textarea.addEventListener('input', autogrowFn); + textarea.addEventListener('focus', autogrowFn); + textarea.addEventListener('valueset', autogrowFn); - label.htmlFor = this.id; - parentNode.insertBefore(label, this); + autogrowFn(); +} - this.addEventListener('focus', function () { - label.classList.add('textareaLabelFocused'); - label.classList.remove('textareaLabelUnfocused'); - }); - this.addEventListener('blur', function () { - label.classList.remove('textareaLabelFocused'); - label.classList.add('textareaLabelUnfocused'); - }); +const EmbyTextAreaPrototype = Object.create(HTMLTextAreaElement.prototype); - this.label = function (text) { - label.innerText = text; +let elementId = 0; + +if (Object.getOwnPropertyDescriptor && Object.defineProperty) { + const descriptor = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value'); + + // descriptor returning null in webos + if (descriptor && descriptor.configurable) { + const baseSetMethod = descriptor.set; + descriptor.set = function (value) { + baseSetMethod.call(this, value); + + this.dispatchEvent(new CustomEvent('valueset', { + bubbles: false, + cancelable: false + })); }; - new autoGrow(this); - }; + Object.defineProperty(HTMLTextAreaElement.prototype, 'value', descriptor); + } +} - document.registerElement('emby-textarea', { - prototype: EmbyTextAreaPrototype, - extends: 'textarea' +EmbyTextAreaPrototype.createdCallback = function () { + if (!this.id) { + this.id = 'embytextarea' + elementId; + elementId++; + } +}; + +EmbyTextAreaPrototype.attachedCallback = function () { + if (this.classList.contains('emby-textarea')) { + return; + } + + this.rows = 1; + this.classList.add('emby-textarea'); + + const parentNode = this.parentNode; + const label = this.ownerDocument.createElement('label'); + label.innerText = this.getAttribute('label') || ''; + label.classList.add('textareaLabel'); + + label.htmlFor = this.id; + parentNode.insertBefore(label, this); + + this.addEventListener('focus', function () { + label.classList.add('textareaLabelFocused'); + label.classList.remove('textareaLabelUnfocused'); + }); + this.addEventListener('blur', function () { + label.classList.remove('textareaLabelFocused'); + label.classList.add('textareaLabelUnfocused'); }); -/* eslint-enable indent */ + this.label = function (text) { + label.innerText = text; + }; + + new autoGrow(this); +}; + +document.registerElement('emby-textarea', { + prototype: EmbyTextAreaPrototype, + extends: 'textarea' +}); + diff --git a/src/elements/emby-toggle/emby-toggle.js b/src/elements/emby-toggle/emby-toggle.js index bcc90724bb..e259c8f366 100644 --- a/src/elements/emby-toggle/emby-toggle.js +++ b/src/elements/emby-toggle/emby-toggle.js @@ -1,51 +1,48 @@ import './emby-toggle.scss'; import 'webcomponents.js/webcomponents-lite'; -/* eslint-disable indent */ +const EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); - const EmbyTogglePrototype = Object.create(HTMLInputElement.prototype); +function onKeyDown(e) { + // Don't submit form on enter + if (e.keyCode === 13) { + e.preventDefault(); - function onKeyDown(e) { - // Don't submit form on enter - if (e.keyCode === 13) { - e.preventDefault(); + this.checked = !this.checked; - this.checked = !this.checked; + this.dispatchEvent(new CustomEvent('change', { + bubbles: true + })); - this.dispatchEvent(new CustomEvent('change', { - bubbles: true - })); + return false; + } +} - return false; - } +EmbyTogglePrototype.attachedCallback = function () { + if (this.getAttribute('data-embytoggle') === 'true') { + return; } - EmbyTogglePrototype.attachedCallback = function () { - if (this.getAttribute('data-embytoggle') === 'true') { - return; - } + this.setAttribute('data-embytoggle', 'true'); - this.setAttribute('data-embytoggle', 'true'); + this.classList.add('mdl-switch__input'); - this.classList.add('mdl-switch__input'); + const labelElement = this.parentNode; + labelElement.classList.add('mdl-switch'); + labelElement.classList.add('mdl-js-switch'); - const labelElement = this.parentNode; - labelElement.classList.add('mdl-switch'); - labelElement.classList.add('mdl-js-switch'); + const labelTextElement = labelElement.querySelector('span'); - const labelTextElement = labelElement.querySelector('span'); + labelElement.insertAdjacentHTML('beforeend', '
'); - labelElement.insertAdjacentHTML('beforeend', '
'); + labelTextElement.classList.add('toggleButtonLabel'); + labelTextElement.classList.add('mdl-switch__label'); - labelTextElement.classList.add('toggleButtonLabel'); - labelTextElement.classList.add('mdl-switch__label'); + this.addEventListener('keydown', onKeyDown); +}; - this.addEventListener('keydown', onKeyDown); - }; +document.registerElement('emby-toggle', { + prototype: EmbyTogglePrototype, + extends: 'input' +}); - document.registerElement('emby-toggle', { - prototype: EmbyTogglePrototype, - extends: 'input' - }); - -/* eslint-enable indent */ diff --git a/src/index.html b/src/index.html index 6d58a3dcd1..e406f11343 100644 --- a/src/index.html +++ b/src/index.html @@ -159,16 +159,8 @@ -
-
-
-
-
-
-
+
-
-
diff --git a/src/index.jsx b/src/index.jsx index 08b3bfada3..87e4fde803 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -23,9 +23,7 @@ import packageManager from './components/packageManager'; import { appRouter, history } from './components/appRouter'; import './elements/emby-button/emby-button'; import './scripts/autoThemes'; -import './scripts/libraryMenu'; import './components/themeMediaPlayer'; -import './scripts/autoBackdrops'; import { pageClassOn, serverAddress } from './utils/dashboard'; import './scripts/screensavermanager'; import './scripts/serverNotifications'; @@ -145,13 +143,17 @@ async function onAppReady() { ServerConnections.currentApiClient()?.ensureWebSocket(); }); + const root = document.getElementById('reactRoot'); + // Remove the splash logo + root.innerHTML = ''; + await appRouter.start(); ReactDOM.render( , - document.getElementById('reactRoot') + root ); if (!browser.tv && !browser.xboxOne && !browser.ps4) { diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 670a46f4b9..abfa30cdf0 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -509,8 +509,6 @@ const scrollerFactory = function (frame, options) { } } - //event.preventDefault(); - // Disable click on a source element, as it is unwelcome when dragging if (!dragging.locked && dragging.path > dragging.pathToLock) { dragging.locked = 1; @@ -604,11 +602,6 @@ const scrollerFactory = function (frame, options) { let delta = normalizeWheelDelta(event); if (transform) { - // Trap scrolling only when necessary and/or requested - if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) { - //stopDefault(event, 1); - } - self.slideBy(o.scrollBy * delta); } else { if (isSmoothScrollSupported) { diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index bc4f435fed..3cccc8db7b 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -1,4 +1,4 @@ -/* eslint-disable indent */ + import ServerConnections from '../../components/ServerConnections'; import { PluginType } from '../../types/plugin.ts'; @@ -9,44 +9,43 @@ class BackdropScreensaver { this.id = 'backdropscreensaver'; this.supportsAnonymous = false; } - show() { - const query = { - ImageTypes: 'Backdrop', - EnableImageTypes: 'Backdrop', - IncludeItemTypes: 'Movie,Series,MusicArtist', - SortBy: 'Random', - Recursive: true, - Fields: 'Taglines', - ImageTypeLimit: 1, - StartIndex: 0, - Limit: 200 - }; + show() { + const query = { + ImageTypes: 'Backdrop', + EnableImageTypes: 'Backdrop', + IncludeItemTypes: 'Movie,Series,MusicArtist', + SortBy: 'Random', + Recursive: true, + Fields: 'Taglines', + ImageTypeLimit: 1, + StartIndex: 0, + Limit: 200 + }; - const apiClient = ServerConnections.currentApiClient(); - apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { - if (result.Items.length) { - import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => { - const newSlideShow = new Slideshow({ - showTitle: true, - cover: true, - items: result.Items - }); + const apiClient = ServerConnections.currentApiClient(); + apiClient.getItems(apiClient.getCurrentUserId(), query).then((result) => { + if (result.Items.length) { + import('../../components/slideshow/slideshow').then(({ default: Slideshow }) => { + const newSlideShow = new Slideshow({ + showTitle: true, + cover: true, + items: result.Items + }); - newSlideShow.show(); - this.currentSlideshow = newSlideShow; - }).catch(console.error); - } - }); - } - - hide() { - if (this.currentSlideshow) { - this.currentSlideshow.hide(); - this.currentSlideshow = null; + newSlideShow.show(); + this.currentSlideshow = newSlideShow; + }).catch(console.error); } - return Promise.resolve(); - } + }); } -/* eslint-enable indent */ + + hide() { + if (this.currentSlideshow) { + this.currentSlideshow.hide(); + this.currentSlideshow = null; + } + return Promise.resolve(); + } +} export default BackdropScreensaver; diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 95bee3838b..a656337d63 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -7,6 +7,7 @@ import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; +import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; // Based on https://github.com/googlecast/CastVideos-chrome/blob/master/CastVideos.js @@ -482,7 +483,7 @@ function getItemsForPlayback(apiClient, query) { query.ExcludeLocationTypes = 'Virtual'; query.EnableTotalRecordCount = false; - return apiClient.getItems(userId, query); + return getItems(apiClient, userId, query); } } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 30e1cb4880..fb6afc72e7 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -48,7 +48,7 @@ function supportsFade() { } function requireHlsPlayer(callback) { - import('hls.js').then(({ default: hls }) => { + import('hls.js/dist/hls.js').then(({ default: hls }) => { hls.DefaultConfig.lowLatencyMode = false; hls.DefaultConfig.backBufferLength = Infinity; hls.DefaultConfig.liveBackBufferLength = 90; diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index cabf6cb3dc..8789d5dad2 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -55,1248 +55,1234 @@ function resolveUrl(url) { }); } -/* eslint-disable indent */ - function tryRemoveElement(elem) { - const parentNode = elem.parentNode; - if (parentNode) { - // Seeing crashes in edge webview - try { - parentNode.removeChild(elem); - } catch (err) { - console.error(`error removing dialog element: ${err}`); - } + const parentNode = elem.parentNode; + if (parentNode) { + // Seeing crashes in edge webview + try { + parentNode.removeChild(elem); + } catch (err) { + console.error(`error removing dialog element: ${err}`); } } +} - function enableNativeTrackSupport(currentSrc, track) { - if (track?.DeliveryMethod === 'Embed') { - return true; - } - - if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { - return false; - } - - if (browser.ps4) { - return false; - } - - if (browser.web0s) { - return false; - } - - // Edge is randomly not rendering subtitles - if (browser.edge) { - return false; - } - - if (browser.iOS && (browser.iosVersion || 10) < 10) { - // works in the browser but not the native app - return false; - } - - if (track) { - const format = (track.Codec || '').toLowerCase(); - if (format === 'ssa' || format === 'ass') { - return false; - } - } - +function enableNativeTrackSupport(currentSrc, track) { + if (track?.DeliveryMethod === 'Embed') { return true; } - function requireHlsPlayer(callback) { - import('hls.js').then(({ default: hls }) => { - hls.DefaultConfig.lowLatencyMode = false; - hls.DefaultConfig.backBufferLength = Infinity; - hls.DefaultConfig.liveBackBufferLength = 90; - window.Hls = hls; - callback(); - }); + if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { + return false; } - function getMediaStreamAudioTracks(mediaSource) { - return mediaSource.MediaStreams.filter(function (s) { - return s.Type === 'Audio'; - }); + if (browser.ps4) { + return false; } - function getMediaStreamTextTracks(mediaSource) { - return mediaSource.MediaStreams.filter(function (s) { - return s.Type === 'Subtitle'; - }); + if (browser.web0s) { + return false; } - function zoomIn(elem) { - return new Promise(resolve => { - const duration = 240; - elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; - dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { - once: true - }); - }); + // Edge is randomly not rendering subtitles + if (browser.edge) { + return false; } - function normalizeTrackEventText(text, useHtml) { - const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); - return useHtml ? result.replace(/\n/gi, '
') : result; + if (browser.iOS && (browser.iosVersion || 10) < 10) { + // works in the browser but not the native app + return false; } - function getTextTrackUrl(track, item, format) { - if (itemHelper.isLocalItem(item) && track.Path) { - return track.Path; + if (track) { + const format = (track.Codec || '').toLowerCase(); + if (format === 'ssa' || format === 'ass') { + return false; } - - let url = playbackManager.getSubtitleUrl(track, item.ServerId); - if (format) { - url = url.replace('.vtt', format); - } - - return url; } - function getDefaultProfile() { - return profileBuilder({}); + return true; +} + +function requireHlsPlayer(callback) { + import('hls.js/dist/hls.js').then(({ default: hls }) => { + hls.DefaultConfig.lowLatencyMode = false; + hls.DefaultConfig.backBufferLength = Infinity; + hls.DefaultConfig.liveBackBufferLength = 90; + window.Hls = hls; + callback(); + }); +} + +function getMediaStreamAudioTracks(mediaSource) { + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Audio'; + }); +} + +function getMediaStreamTextTracks(mediaSource) { + return mediaSource.MediaStreams.filter(function (s) { + return s.Type === 'Subtitle'; + }); +} + +function zoomIn(elem) { + return new Promise(resolve => { + const duration = 240; + elem.style.animation = `htmlvideoplayer-zoomin ${duration}ms ease-in normal`; + dom.addEventListener(elem, dom.whichAnimationEvent(), resolve, { + once: true + }); + }); +} + +function normalizeTrackEventText(text, useHtml) { + const result = text.replace(/\\N/gi, '\n').replace(/\r/gi, ''); + return useHtml ? result.replace(/\n/gi, '
') : result; +} + +function getTextTrackUrl(track, item, format) { + if (itemHelper.isLocalItem(item) && track.Path) { + return track.Path; } - const PRIMARY_TEXT_TRACK_INDEX = 0; - const SECONDARY_TEXT_TRACK_INDEX = 1; + let url = playbackManager.getSubtitleUrl(track, item.ServerId); + if (format) { + url = url.replace('.vtt', format); + } - export class HtmlVideoPlayer { - /** + return url; +} + +function getDefaultProfile() { + return profileBuilder({}); +} + +const PRIMARY_TEXT_TRACK_INDEX = 0; +const SECONDARY_TEXT_TRACK_INDEX = 1; + +export class HtmlVideoPlayer { + /** * @type {string} */ - name; - /** + name; + /** * @type {string} */ - type = PluginType.MediaPlayer; - /** + type = PluginType.MediaPlayer; + /** * @type {string} */ - id = 'htmlvideoplayer'; - /** + id = 'htmlvideoplayer'; + /** * Let any players created by plugins take priority * * @type {number} */ - priority = 1; - /** + priority = 1; + /** * @type {boolean} */ - isFetching = false; - /** + isFetching = false; + /** * @type {HTMLDivElement | null | undefined} */ - #videoDialog; - /** + #videoDialog; + /** * @type {number | undefined} */ - #subtitleTrackIndexToSetOnPlaying; - /** + #subtitleTrackIndexToSetOnPlaying; + /** * @type {number | undefined} */ - #secondarySubtitleTrackIndexToSetOnPlaying; - /** + #secondarySubtitleTrackIndexToSetOnPlaying; + /** * @type {number | null} */ - #audioTrackIndexToSetOnPlaying; - /** + #audioTrackIndexToSetOnPlaying; + /** * @type {null | undefined} */ - #currentClock; - /** + #currentClock; + /** * @type {any | null | undefined} */ - #currentSubtitlesOctopus; - /** + #currentAssRenderer; + /** * @type {null | undefined} */ - #currentAssRenderer; - /** + #customTrackIndex; + /** * @type {number | undefined} */ - #customTrackIndex; - /** - * @type {number | undefined} - */ - #customSecondaryTrackIndex; - /** + #customSecondaryTrackIndex; + /** * @type {boolean | undefined} */ - #showTrackOffset; - /** + #showTrackOffset; + /** * @type {number | undefined} */ - #currentTrackOffset; - /** + #currentTrackOffset; + /** * @type {HTMLElement | null | undefined} */ - #secondaryTrackOffset; - /** + #secondaryTrackOffset; + /** * @type {HTMLElement | null | undefined} */ - #videoSubtitlesElem; - /** + #videoSubtitlesElem; + /** * @type {HTMLElement | null | undefined} */ - #videoSecondarySubtitlesElem; - /** + #videoSecondarySubtitlesElem; + /** * @type {any | null | undefined} */ - #currentTrackEvents; - /** + #currentTrackEvents; + /** * @type {any | null | undefined} */ - #currentSecondaryTrackEvents; - /** + #currentSecondaryTrackEvents; + /** * @type {string[] | undefined} */ - #supportedFeatures; - /** + #supportedFeatures; + /** * @type {HTMLVideoElement | null | undefined} */ - #mediaElement; - /** + #mediaElement; + /** * @type {number} */ - #fetchQueue = 0; - /** + #fetchQueue = 0; + /** * @type {string | undefined} */ - #currentSrc; - /** + #currentSrc; + /** * @type {boolean | undefined} */ - #started; - /** + #started; + /** * @type {boolean | undefined} */ - #timeUpdated; - /** + #timeUpdated; + /** * @type {number | null | undefined} */ - #currentTime; - /** + #currentTime; + /** * @type {any | undefined} */ - #flvPlayer; - /** + #flvPlayer; + /** * @private (used in other files) * @type {any | undefined} */ - _hlsPlayer; - /** + _hlsPlayer; + /** * @private (used in other files) * @type {any | null | undefined} */ - _castPlayer; - /** + _castPlayer; + /** * @private (used in other files) * @type {any | undefined} */ - _currentPlayOptions; - /** + _currentPlayOptions; + /** * @type {any | undefined} */ - #lastProfile; + #lastProfile; - constructor() { - if (browser.edgeUwp) { - this.name = 'Windows Video Player'; - } else { - this.name = 'Html Video Player'; - } + constructor() { + if (browser.edgeUwp) { + this.name = 'Windows Video Player'; + } else { + this.name = 'Html Video Player'; } + } - currentSrc() { - return this.#currentSrc; - } + currentSrc() { + return this.#currentSrc; + } - /** + /** * @private */ - incrementFetchQueue() { - if (this.#fetchQueue <= 0) { - this.isFetching = true; - Events.trigger(this, 'beginFetch'); - } - - this.#fetchQueue++; + incrementFetchQueue() { + if (this.#fetchQueue <= 0) { + this.isFetching = true; + Events.trigger(this, 'beginFetch'); } - /** + this.#fetchQueue++; + } + + /** * @private */ - decrementFetchQueue() { - this.#fetchQueue--; + decrementFetchQueue() { + this.#fetchQueue--; - if (this.#fetchQueue <= 0) { - this.isFetching = false; - Events.trigger(this, 'endFetch'); - } + if (this.#fetchQueue <= 0) { + this.isFetching = false; + Events.trigger(this, 'endFetch'); } + } - /** + /** * @private */ - updateVideoUrl(streamInfo) { - const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); + updateVideoUrl(streamInfo) { + const isHls = streamInfo.url.toLowerCase().includes('.m3u8'); - const mediaSource = streamInfo.mediaSource; - const item = streamInfo.item; + const mediaSource = streamInfo.mediaSource; + const item = streamInfo.item; - // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts - // This will start the transcoding process before actually feeding the video url into the player - // Edit: Also seeing stalls from hls.js - if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { - const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); + // Huge hack alert. Safari doesn't seem to like if the segments aren't available right away when playback starts + // This will start the transcoding process before actually feeding the video url into the player + // Edit: Also seeing stalls from hls.js + if (mediaSource && item && !mediaSource.RunTimeTicks && isHls && streamInfo.playMethod === 'Transcode' && (browser.iOS || browser.osx)) { + const hlsPlaylistUrl = streamInfo.url.replace('master.m3u8', 'live.m3u8'); - loading.show(); + loading.show(); - console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); + console.debug(`prefetching hls playlist: ${hlsPlaylistUrl}`); - return ServerConnections.getApiClient(item.ServerId).ajax({ + return ServerConnections.getApiClient(item.ServerId).ajax({ - type: 'GET', - url: hlsPlaylistUrl + type: 'GET', + url: hlsPlaylistUrl - }).then(function () { - console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); + }).then(function () { + console.debug(`completed prefetching hls playlist: ${hlsPlaylistUrl}`); - loading.hide(); - streamInfo.url = hlsPlaylistUrl; - }, function () { - console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); + loading.hide(); + streamInfo.url = hlsPlaylistUrl; + }, function () { + console.error(`error prefetching hls playlist: ${hlsPlaylistUrl}`); - loading.hide(); - }); - } else { - return Promise.resolve(); - } - } - - play(options) { - this.#started = false; - this.#timeUpdated = false; - - this.#currentTime = null; - - if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset(); - - return this.createMediaElement(options).then(elem => { - return this.updateVideoUrl(options).then(() => { - return this.setCurrentSrc(elem, options); - }); + loading.hide(); }); + } else { + return Promise.resolve(); } + } - /** + play(options) { + this.#started = false; + this.#timeUpdated = false; + + this.#currentTime = null; + + if (options.resetSubtitleOffset !== false) this.resetSubtitleOffset(); + + return this.createMediaElement(options).then(elem => { + return this.updateVideoUrl(options).then(() => { + return this.setCurrentSrc(elem, options); + }); + }); + } + + /** * @private */ - setSrcWithFlvJs(elem, options, url) { - return import('flv.js').then(({ default: flvjs }) => { - const flvPlayer = flvjs.createPlayer({ - type: 'flv', - url: url - }, - { - seekType: 'range', - lazyLoad: false - }); + setSrcWithFlvJs(elem, options, url) { + return import('flv.js').then(({ default: flvjs }) => { + const flvPlayer = flvjs.createPlayer({ + type: 'flv', + url: url + }, + { + seekType: 'range', + lazyLoad: false + }); - flvPlayer.attachMediaElement(elem); - flvPlayer.load(); + flvPlayer.attachMediaElement(elem); + flvPlayer.load(); - this.#flvPlayer = flvPlayer; + this.#flvPlayer = flvPlayer; + + // This is needed in setCurrentTrackElement + this.#currentSrc = url; + + return flvPlayer.play(); + }); + } + + /** + * @private + */ + setSrcWithHlsJs(elem, options, url) { + return new Promise((resolve, reject) => { + requireHlsPlayer(async () => { + let maxBufferLength = 30; + + // Some browsers cannot handle huge fragments in high bitrate. + // This issue usually happens when using HWA encoders with a high bitrate setting. + // Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome. + // https://github.com/video-dev/hls.js/issues/876 + if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) { + maxBufferLength = 6; + } + + const includeCorsCredentials = await getIncludeCorsCredentials(); + + const hls = new Hls({ + manifestLoadingTimeOut: 20000, + maxBufferLength: maxBufferLength, + xhrSetup(xhr) { + xhr.withCredentials = includeCorsCredentials; + } + }); + hls.loadSource(url); + hls.attachMedia(elem); + + bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); + + this._hlsPlayer = hls; // This is needed in setCurrentTrackElement this.#currentSrc = url; - - return flvPlayer.play(); }); - } + }); + } - /** + /** * @private */ - setSrcWithHlsJs(elem, options, url) { - return new Promise((resolve, reject) => { - requireHlsPlayer(async () => { - let maxBufferLength = 30; + async setCurrentSrc(elem, options) { + elem.removeEventListener('error', this.onError); - // Some browsers cannot handle huge fragments in high bitrate. - // This issue usually happens when using HWA encoders with a high bitrate setting. - // Limit the BufferLength to 6s, it works fine when playing 4k 120Mbps over HLS on chrome. - // https://github.com/video-dev/hls.js/issues/876 - if ((browser.chrome || browser.edgeChromium || browser.firefox) && playbackManager.getMaxStreamingBitrate(this) >= 25000000) { - maxBufferLength = 6; - } + let val = options.url; + console.debug(`playing url: ${val}`); - const includeCorsCredentials = await getIncludeCorsCredentials(); - - const hls = new Hls({ - manifestLoadingTimeOut: 20000, - maxBufferLength: maxBufferLength, - xhrSetup(xhr) { - xhr.withCredentials = includeCorsCredentials; - } - }); - hls.loadSource(url); - hls.attachMedia(elem); - - bindEventsToHlsPlayer(this, hls, elem, this.onError, resolve, reject); - - this._hlsPlayer = hls; - - // This is needed in setCurrentTrackElement - this.#currentSrc = url; - }); - }); + // Convert to seconds + const seconds = (options.playerStartPositionTicks || 0) / 10000000; + if (seconds) { + val += `#t=${seconds}`; } - /** - * @private - */ - async setCurrentSrc(elem, options) { - elem.removeEventListener('error', this.onError); + destroyHlsPlayer(this); + destroyFlvPlayer(this); + destroyCastPlayer(this); - let val = options.url; - console.debug(`playing url: ${val}`); + let secondaryTrackValid = true; - // Convert to seconds - const seconds = (options.playerStartPositionTicks || 0) / 10000000; - if (seconds) { - val += `#t=${seconds}`; - } - - destroyHlsPlayer(this); - destroyFlvPlayer(this); - destroyCastPlayer(this); - - let secondaryTrackValid = true; - - this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; - if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { - const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; - if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { - this.#subtitleTrackIndexToSetOnPlaying = -1; - secondaryTrackValid = false; - } - // secondary track should not be shown if primary track is no longer a valid pair - if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) { - secondaryTrackValid = false; - } - } else { + this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; + if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { + const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; + if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { + this.#subtitleTrackIndexToSetOnPlaying = -1; secondaryTrackValid = false; } + // secondary track should not be shown if primary track is no longer a valid pair + if (initialSubtitleStream && !playbackManager.trackHasSecondarySubtitleSupport(initialSubtitleStream, this)) { + secondaryTrackValid = false; + } + } else { + secondaryTrackValid = false; + } - this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; + this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - this._currentPlayOptions = options; + this._currentPlayOptions = options; - if (secondaryTrackValid) { - this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; - if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { - const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; - if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) { - this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; - } + if (secondaryTrackValid) { + this.#secondarySubtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSecondarySubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSecondarySubtitleStreamIndex; + if (this.#secondarySubtitleTrackIndexToSetOnPlaying != null && this.#secondarySubtitleTrackIndexToSetOnPlaying >= 0) { + const initialSecondarySubtitleStream = options.mediaSource.MediaStreams[this.#secondarySubtitleTrackIndexToSetOnPlaying]; + if (!initialSecondarySubtitleStream || !playbackManager.trackHasSecondarySubtitleSupport(initialSecondarySubtitleStream, this)) { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; } - } else { - this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } + } else { + this.#secondarySubtitleTrackIndexToSetOnPlaying = -1; + } + + const crossOrigin = getCrossOriginValue(options.mediaSource); + if (crossOrigin) { + elem.crossOrigin = crossOrigin; + } + + if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { + return this.setSrcWithHlsJs(elem, options, val); + } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { + return this.setSrcWithFlvJs(elem, options, val); + } else { + elem.autoplay = true; + + const includeCorsCredentials = await getIncludeCorsCredentials(); + if (includeCorsCredentials) { + // Safari will not send cookies without this + elem.crossOrigin = 'use-credentials'; } - const crossOrigin = getCrossOriginValue(options.mediaSource); - if (crossOrigin) { - elem.crossOrigin = crossOrigin; - } + return applySrc(elem, val, options).then(() => { + this.#currentSrc = val; - if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && val.includes('.m3u8')) { - return this.setSrcWithHlsJs(elem, options, val); - } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { - return this.setSrcWithFlvJs(elem, options, val); - } else { - elem.autoplay = true; + return playWithPromise(elem, this.onError); + }); + } + } - const includeCorsCredentials = await getIncludeCorsCredentials(); - if (includeCorsCredentials) { - // Safari will not send cookies without this - elem.crossOrigin = 'use-credentials'; - } + setSubtitleStreamIndex(index) { + this.setCurrentTrackElement(index); + } - return applySrc(elem, val, options).then(() => { - this.#currentSrc = val; + setSecondarySubtitleStreamIndex(index) { + this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX); + } - return playWithPromise(elem, this.onError); + resetSubtitleOffset() { + this.#currentTrackOffset = 0; + this.#secondaryTrackOffset = 0; + this.#showTrackOffset = false; + } + + enableShowingSubtitleOffset() { + this.#showTrackOffset = true; + } + + disableShowingSubtitleOffset() { + this.#showTrackOffset = false; + } + + isShowingSubtitleOffsetEnabled() { + return this.#showTrackOffset; + } + + /** + * @private + */ + getTextTracks() { + const videoElement = this.#mediaElement; + if (videoElement) { + return Array.from(videoElement.textTracks) + .filter(function (trackElement) { + // get showing .vtt textTack + return trackElement.mode === 'showing'; }); - } + } else { + return null; } + } - setSubtitleStreamIndex(index) { - this.setCurrentTrackElement(index); - } + setSubtitleOffset = debounce(this._setSubtitleOffset, 100); - setSecondarySubtitleStreamIndex(index) { - this.setCurrentTrackElement(index, SECONDARY_TEXT_TRACK_INDEX); - } - - resetSubtitleOffset() { - this.#currentTrackOffset = 0; - this.#secondaryTrackOffset = 0; - this.#showTrackOffset = false; - } - - enableShowingSubtitleOffset() { - this.#showTrackOffset = true; - } - - disableShowingSubtitleOffset() { - this.#showTrackOffset = false; - } - - isShowingSubtitleOffsetEnabled() { - return this.#showTrackOffset; - } - - /** + /** * @private */ - getTextTracks() { - const videoElement = this.#mediaElement; - if (videoElement) { - return Array.from(videoElement.textTracks) - .filter(function (trackElement) { - // get showing .vtt textTack - return trackElement.mode === 'showing'; - }); + _setSubtitleOffset(offset) { + const offsetValue = parseFloat(offset); + + // if .ass currently rendering + if (this.#currentAssRenderer) { + this.updateCurrentTrackOffset(offsetValue); + this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; + } else { + const trackElements = this.getTextTracks(); + // if .vtt currently rendering + if (trackElements?.length > 0) { + trackElements.forEach((trackElement, index) => { + this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); + }); + } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { + this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); + this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); } else { - return null; + console.debug('No available track, cannot apply offset: ', offsetValue); } } + } - setSubtitleOffset = debounce(this._setSubtitleOffset, 100); - - /** + /** * @private */ - _setSubtitleOffset(offset) { - const offsetValue = parseFloat(offset); - - // if .ass currently rendering - if (this.#currentSubtitlesOctopus) { - this.updateCurrentTrackOffset(offsetValue); - this.#currentSubtitlesOctopus.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + offsetValue; - } else { - const trackElements = this.getTextTracks(); - // if .vtt currently rendering - if (trackElements?.length > 0) { - trackElements.forEach((trackElement, index) => { - this.setTextTrackSubtitleOffset(trackElement, offsetValue, index); - }); - } else if (this.#currentTrackEvents || this.#currentSecondaryTrackEvents) { - this.#currentTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentTrackEvents, offsetValue, PRIMARY_TEXT_TRACK_INDEX); - this.#currentSecondaryTrackEvents && this.setTrackEventsSubtitleOffset(this.#currentSecondaryTrackEvents, offsetValue, SECONDARY_TEXT_TRACK_INDEX); - } else { - console.debug('No available track, cannot apply offset: ', offsetValue); - } - } + updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { + let offsetToCompare = this.#currentTrackOffset; + if (this.isSecondaryTrack(currentTrackIndex)) { + offsetToCompare = this.#secondaryTrackOffset; } - /** - * @private - */ - updateCurrentTrackOffset(offsetValue, currentTrackIndex = PRIMARY_TEXT_TRACK_INDEX) { - let offsetToCompare = this.#currentTrackOffset; - if (this.isSecondaryTrack(currentTrackIndex)) { - offsetToCompare = this.#secondaryTrackOffset; - } + let relativeOffset = offsetValue; + const newTrackOffset = offsetValue; - let relativeOffset = offsetValue; - const newTrackOffset = offsetValue; - - if (offsetToCompare) { - relativeOffset -= offsetToCompare; - } - - if (this.isSecondaryTrack(currentTrackIndex)) { - this.#secondaryTrackOffset = newTrackOffset; - } else { - this.#currentTrackOffset = newTrackOffset; - } - - // relative to currentTrackOffset - return relativeOffset; + if (offsetToCompare) { + relativeOffset -= offsetToCompare; } - /** + if (this.isSecondaryTrack(currentTrackIndex)) { + this.#secondaryTrackOffset = newTrackOffset; + } else { + this.#currentTrackOffset = newTrackOffset; + } + + // relative to currentTrackOffset + return relativeOffset; + } + + /** * @private * These browsers will not clear the existing active cue when setting an offset * for native TextTracks. * Any previous text tracks that are on the screen when the offset changes will remain next * to the new tracks until they reach the end time of the new offset's instance of the track. */ - requiresHidingActiveCuesOnOffsetChange() { - return !!browser.firefox; - } + requiresHidingActiveCuesOnOffsetChange() { + return !!browser.firefox; + } - /** + /** * @private */ - hideTextTrackWithActiveCues(currentTrack) { - if (currentTrack.activeCues) { - currentTrack.mode = 'hidden'; - } + hideTextTrackWithActiveCues(currentTrack) { + if (currentTrack.activeCues) { + currentTrack.mode = 'hidden'; } + } - /** + /** * Forces the active cue to clear by disabling then re-enabling the track. * The track mode is reverted inside of a 0ms timeout to free up the track * and allow it to disable and clear the active cue. * @private */ - forceClearTextTrackActiveCues(currentTrack) { - if (currentTrack.activeCues) { - currentTrack.mode = 'disabled'; - setTimeout(() => { - currentTrack.mode = 'showing'; - }, 0); - } + forceClearTextTrackActiveCues(currentTrack) { + if (currentTrack.activeCues) { + currentTrack.mode = 'disabled'; + setTimeout(() => { + currentTrack.mode = 'showing'; + }, 0); } + } - /** + /** * @private */ - setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { - if (currentTrack.cues) { - offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); - if (offsetValue === 0) { - return; - } - - const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange(); - if (shouldClearActiveCues) { - this.hideTextTrackWithActiveCues(currentTrack); - } - - Array.from(currentTrack.cues) - .forEach(function (cue) { - cue.startTime -= offsetValue; - cue.endTime -= offsetValue; - }); - - if (shouldClearActiveCues) { - this.forceClearTextTrackActiveCues(currentTrack); - } + setTextTrackSubtitleOffset(currentTrack, offsetValue, currentTrackIndex) { + if (currentTrack.cues) { + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex); + if (offsetValue === 0) { + return; } - } - /** - * @private - */ - setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { - if (Array.isArray(trackEvents)) { - offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks - if (offsetValue === 0) { - return; - } - trackEvents.forEach(function (trackEvent) { - trackEvent.StartPositionTicks -= offsetValue; - trackEvent.EndPositionTicks -= offsetValue; + const shouldClearActiveCues = this.requiresHidingActiveCuesOnOffsetChange(); + if (shouldClearActiveCues) { + this.hideTextTrackWithActiveCues(currentTrack); + } + + Array.from(currentTrack.cues) + .forEach(function (cue) { + cue.startTime -= offsetValue; + cue.endTime -= offsetValue; }); + + if (shouldClearActiveCues) { + this.forceClearTextTrackActiveCues(currentTrack); } } + } - getSubtitleOffset() { - return this.#currentTrackOffset; - } - - isPrimaryTrack(textTrackIndex) { - return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX; - } - - isSecondaryTrack(textTrackIndex) { - return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX; - } - - /** + /** * @private */ - isAudioStreamSupported(stream, deviceProfile, container) { - const codec = (stream.Codec || '').toLowerCase(); - - if (!codec) { - return true; + setTrackEventsSubtitleOffset(trackEvents, offsetValue, currentTrackIndex) { + if (Array.isArray(trackEvents)) { + offsetValue = this.updateCurrentTrackOffset(offsetValue, currentTrackIndex) * 1e7; // ticks + if (offsetValue === 0) { + return; } + trackEvents.forEach(function (trackEvent) { + trackEvent.StartPositionTicks -= offsetValue; + trackEvent.EndPositionTicks -= offsetValue; + }); + } + } - if (!deviceProfile) { - // This should never happen - return true; - } + getSubtitleOffset() { + return this.#currentTrackOffset; + } - const profiles = deviceProfile.DirectPlayProfiles || []; + isPrimaryTrack(textTrackIndex) { + return textTrackIndex === PRIMARY_TEXT_TRACK_INDEX; + } - return profiles.some(function (p) { - return p.Type === 'Video' + isSecondaryTrack(textTrackIndex) { + return textTrackIndex === SECONDARY_TEXT_TRACK_INDEX; + } + + /** + * @private + */ + isAudioStreamSupported(stream, deviceProfile, container) { + const codec = (stream.Codec || '').toLowerCase(); + + if (!codec) { + return true; + } + + if (!deviceProfile) { + // This should never happen + return true; + } + + const profiles = deviceProfile.DirectPlayProfiles || []; + + return profiles.some(function (p) { + return p.Type === 'Video' && includesAny((p.Container || '').toLowerCase(), container) && includesAny((p.AudioCodec || '').toLowerCase(), codec); - }); - } + }); + } - /** + /** * @private */ - getSupportedAudioStreams() { - const profile = this.#lastProfile; + getSupportedAudioStreams() { + const profile = this.#lastProfile; - const mediaSource = this._currentPlayOptions.mediaSource; - const container = mediaSource.Container.toLowerCase(); + const mediaSource = this._currentPlayOptions.mediaSource; + const container = mediaSource.Container.toLowerCase(); - return getMediaStreamAudioTracks(mediaSource).filter((stream) => { - return this.isAudioStreamSupported(stream, profile, container); - }); + return getMediaStreamAudioTracks(mediaSource).filter((stream) => { + return this.isAudioStreamSupported(stream, profile, container); + }); + } + + setAudioStreamIndex(index) { + const streams = this.getSupportedAudioStreams(); + + if (streams.length < 2) { + // If there's only one supported stream then trust that the player will handle it on it's own + return; } - setAudioStreamIndex(index) { - const streams = this.getSupportedAudioStreams(); + let audioIndex = -1; - if (streams.length < 2) { - // If there's only one supported stream then trust that the player will handle it on it's own - return; + for (const stream of streams) { + audioIndex++; + + if (stream.Index === index) { + break; } + } - let audioIndex = -1; + if (audioIndex === -1) { + return; + } - for (const stream of streams) { - audioIndex++; + const elem = this.#mediaElement; + if (!elem) { + return; + } - if (stream.Index === index) { - break; - } - } + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - if (audioIndex === -1) { - return; - } - - const elem = this.#mediaElement; - if (!elem) { - return; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/audioTracks - - /** + /** * @type {ArrayLike|any[]} */ - const elemAudioTracks = elem.audioTracks || []; - console.debug(`found ${elemAudioTracks.length} audio tracks`); + const elemAudioTracks = elem.audioTracks || []; + console.debug(`found ${elemAudioTracks.length} audio tracks`); - for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { - if (audioIndex === i) { - console.debug(`setting audio track ${i} to enabled`); - audioTrack.enabled = true; - } else { - console.debug(`setting audio track ${i} to disabled`); - audioTrack.enabled = false; - } - } - } - - stop(destroyPlayer) { - const elem = this.#mediaElement; - const src = this.#currentSrc; - - if (elem) { - if (src) { - elem.pause(); - } - - onEndedInternal(this, elem, this.onError); - } - - this.destroyCustomTrack(elem); - - if (destroyPlayer) { - this.destroy(); - } - - return Promise.resolve(); - } - - destroy() { - this.setSubtitleOffset.cancel(); - - destroyHlsPlayer(this); - destroyFlvPlayer(this); - - setBackdropTransparency(TRANSPARENCY_LEVEL.None); - document.body.classList.remove('hide-scroll'); - - const videoElement = this.#mediaElement; - - if (videoElement) { - this.#mediaElement = null; - - this.destroyCustomTrack(videoElement); - videoElement.removeEventListener('timeupdate', this.onTimeUpdate); - videoElement.removeEventListener('ended', this.onEnded); - videoElement.removeEventListener('volumechange', this.onVolumeChange); - videoElement.removeEventListener('pause', this.onPause); - videoElement.removeEventListener('playing', this.onPlaying); - videoElement.removeEventListener('play', this.onPlay); - videoElement.removeEventListener('click', this.onClick); - videoElement.removeEventListener('dblclick', this.onDblClick); - videoElement.removeEventListener('waiting', this.onWaiting); - videoElement.removeEventListener('error', this.onError); // bound in htmlMediaHelper - - resetSrc(videoElement); - - videoElement.parentNode.removeChild(videoElement); - } - - const dlg = this.#videoDialog; - if (dlg) { - this.#videoDialog = null; - dlg.parentNode.removeChild(dlg); - } - - if (Screenfull.isEnabled) { - Screenfull.exit(); + for (const [i, audioTrack] of Array.from(elemAudioTracks).entries()) { + if (audioIndex === i) { + console.debug(`setting audio track ${i} to enabled`); + audioTrack.enabled = true; } else { - // iOS Safari - if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } + console.debug(`setting audio track ${i} to disabled`); + audioTrack.enabled = false; } } + } + + stop(destroyPlayer) { + const elem = this.#mediaElement; + const src = this.#currentSrc; + + if (elem) { + if (src) { + elem.pause(); + } - /** - * @private - * @param e {Event} The event received from the `